Laden...

Unerklärlicher SQL-Fehler "Ungültiger Spaltenname"

Letzter Beitrag vor 11 Monaten 9 Posts 867 Views
Unerklärlicher SQL-Fehler "Ungültiger Spaltenname"

Verwendetes Datenbanksystem: SQL Server Express, NET v7

Moin-moin,

ich habe hier ein Problem mit einem ungültigen Spaltennamen, auf das ich mir absolut keinen Reim machen kann.

Und zwar erscheint der Fehler beim Binding in ein Listenobjekt nach einer simplen Tabellenabfrage. Nur dass die besagte Spalte gar keine Eigenschaft meines Models ist, und in der Db-Tabelle existiert sie natürlich auch nicht.

So sieht mein Modell aus:

public class DangerousGoods : DbModel
{
    public int Position { get; set; }
    public int TransportContainerId { get; set; }
    public bool? IndicatorTank { get; set; }
    public string EmsNumbers { get; set; }
    public string ProperShippingName { get; set; }
    public string UNNumber { get; set; }
    public string Class { get; set; }
    public string PackingGroup { get; set; }
    public bool? TransportProhibited { get; set; }
    public double? DangerWeightNet { get; set; }
    public int PackingNumber { get; set; }
    public string PackingCode { get; set; }
    public double? DangerWeightNetExplosive { get; set; }
    public string LegalNorm { get; set; }
}

Von dieser Klasse erbt es:

public abstract class DbModel
{
    bool Modified;

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public int Version { get; set; }
    public string ModifiedBy { get; set; }
    public DateTime? ModifiedOn { get; set; }

    protected DbModel()
    {
    }

    public virtual void Update(string modifier = null, DateTime? date = null)
    {
        if (!this.Modified)
            this.Version++;
        this.ModifiedBy = modifier ?? ApiUser.UserName;
        this.ModifiedOn = date ?? DateTime.Now;

        this.Modified = true;
    }
}

Hier wird der Aufruf ausgeführt:

public IEnumerable<GoodsItem> GetFullGoodsDescription(ContainerTransportOrder order)
{
    if (order != null)
    {
        var id = order.TransportContainerId;
        var goodsItems = this.GoodsItems
            .Include(gi => gi.GoodsItemCustomsNumbers)
            .AsSingleQuery()
            .Where(gi => gi.TransportContainerId == id).ToList();

        if (order.ContainsDangerousGoods == true)
        {
            var dangerousGoods = this.DangerousGoods.Where(dg => dg.TransportContainerId == id).ToList(); // hier erscheint die Exception

            foreach (var item in goodsItems)
                item.DangerousGoods = dangerousGoods.Where(dg => dg.Position == item.Position).ToList();
        }

        return goodsItems;
    }

    return null;
}

Die Exception lautet:

Ungültiger Spaltenname "GoodsItemId".

Es ist mir schleierhaft, warum NET hier diesen Spaltennamen mokiert. Auf der Basisklasse "DbModel" basieren Dutzende weitere Klassen zu Tabellen, wo es noch nie vergleichbare Probleme gab.

Hat hier irgendwer eine Idee? Ich bin völlig ratlos und habe schon alles mögliche ausprobiert, inkl. Umbenennungen und Neuanlage der beteiligten Klassen u. Tabellen.

Du hast ja sehr offenbar Entitäten, die GoodsItem heissen - also wird da irgendwo die Quelle sein.

Generell: Software erfindet nichts. Irgendwo in Deinem Schema oder im Kontext wird die Spalte vorhanden sein.
Das kann auch ein Folgefehler sein, zB weil irgendwas in Deiner Entity Config falsch ist; also Dein Schema etwas enthält / nicht enthält, was dann automatisch gewisse Spalten erzeugt. Vielleicht hast Du eine Relation und vergessen einen FK zu definieren ⇒ er wird automatisch erzeugt und der Name passt nicht / Du hast das Schema vergessen.

Das passiert oft, wenn man mit EF nicht so umgeht, wie es empfohlen ist (und wenn man den Code sieht, dann befolgst Du gewisse Ratschläge auf alle Fälle nicht, das sieht man schon am Snippet).

Meine Vermutung aufgrund von Erfahrung eben: Deine Entity GoodsItem oder ein FK ist fehlkonfiguriert.


PS: was direkt auffällt: Du hast ein Zeitzonenbug in Deiner Software.
[FAQ] DateTime vs. DateTimeOffset und der Umgang mit Zeiten in .NET

Hi,

in dem Fall sieht es ganz Stark nach FK aus. Wie sieht denn das Model aus? Fluent-API Nutzung und dort ggf. eine Reference aufgebaut?

Es gibt zwei weitere Tabellen, die eine Fremdschlüsselbeziehung haben. Dort gibt es auch die Spalte "GoodsItemId":

public class GoodsItem : DbModel
{
    public int TransportContainerId { get; set; }
    public int Position { get; set; }
    public string Contents { get; set; }
    public double? MonetaryValueEUR { get; set; }
    public string PackagingCode { get; set; }
    public double PackagingGrossWeight { get; set; }
    public int PackagingNumberUnits { get; set; }
    public string CustomsTariffNumber { get; set; }

    public IEnumerable<GoodsItemCustomsNumber> GoodsItemCustomsNumbers { get; set; }
    public IEnumerable<DangerousGoods> DangerousGoods {  get; internal set; }
}
public class GoodsItemCustomsNumber : DbModel
{
    public int GoodsItemId { get; set; }
    public string RegistrationNumber { get; set; }

    public GoodsItem GoodsItem { get; set; }
}

Und so wird die Relation abgebildet:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<GoodsItemCustomsNumber>()
        .HasOne<GoodsItem>(gicn => gicn.GoodsItem)
        .WithMany(gi => gi.GoodsItemCustomsNumbers)
        .HasForeignKey(gicn => gicn.GoodsItemId);
    ...

Diese Tabellen frage ich in der Methode im oberen Beitrag auch problemlos ab. Die Exception kommt erst bei der Abfrage von "DangerousGoods", und dort gibt es diese Spalte eben nicht und auch keine Fremdschlüsselbeziehung zu "GoodsItems". Die Relation von "DangerousGoods" zeigt auf eine andere Tabelle, die aber ebenfalls keine Spalte "GoodsItemId" enthält.

@Abt: Nein, es gibt keinen Zeitzonen-Bug. Hier läuft alles nach MEZ, und auch die Server stehen in Deutschland. Datenbank und frühere Software stammen allerdings auch nicht von mir. Ich hätte alles per UTC + Zeitzone gemacht.

Edit: Screenshot Tabelle hinzugefügt.

Zeig mal die Entität DangerousGoods sowie dessen Config.

So wie ich das sehe existiert hier eine Relation; aber Du hast die FK Spalte vergessen/falsch konfiguriert.


@Abt: Nein, es gibt keinen Zeitzonen-Bug. Hier läuft alles nach MEZ, und auch die Server stehen in Deutschland. Datenbank und frühere Software stammen allerdings auch nicht von mir. Ich hätte alles per UTC + Zeitzone gemacht.

Das ist .NET und dem Verhalten, wie der Provider mit DateTimeKind umgeht egal. Das ist eine by-design Einschränkung des DateTime-Datentyps, dem Du nicht entkommen kannst, egal was Du tust und das erst mit DateTimeOffset behoben ist. Das kann man nur mit Workarounds beheben.
Siehe Sample im referenzierten Thema.

Entität "DangerousGoods":

public class DangerousGoods : DbModel
{
    public int Position { get; set; }
    public int TransportContainerId { get; set; }
    public bool? IndicatorTank { get; set; }
    public string EmsNumbers { get; set; }
    public string ProperShippingName { get; set; }
    public string UNNumber { get; set; }
    public string Class { get; set; }
    public string PackingGroup { get; set; }
    public bool? TransportProhibited { get; set; }
    public double? DangerWeightNet { get; set; }
    public int PackingNumber { get; set; }
    public string PackingCode { get; set; }
    public double? DangerWeightNetExplosive { get; set; }
    public string LegalNorm { get; set; }

    public TransportContainer TransportContainer { get; set; }
}

Entität "TransportContainer":

public class TransportContainer : DbModel
{
    public string ContainerNumber { get; set; }
    public string OceanVoyageBookingNumber { get; set; }
    public double NetWeight { get; set; }
    public double GrossWeight { get; set; }
    public double? TaraWeight { get; set; }
    public int? OceanVoyagePortId { get; set; }
    public bool? IsForOceanVoyage { get; set; }
    public string Description { get; set; }
    public string OriginCountryCode { get; set; }
    public string DestinationCountryCode { get; set; }
    public int? CustomsId { get; set; }
    public bool IsFullContainer { get; set; }
    public bool? IsActiveReefer { get; set; }
    public string OceanVoyageShip { get; set; }
    public string Comments { get; set; }
    //public string OceanVoyage { get; set; }
    public string TransportPaperReference { get; set; }
    public double? MaxTemperature { get; set; }
    public string SealNumbers { get; set; }

    public IEnumerable<DangerousGoods> DangerousGoods { get; set; }
}

Builder:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<GoodsItemCustomsNumber>()
        .HasOne<GoodsItem>(gicn => gicn.GoodsItem)
        .WithMany(gi => gi.GoodsItemCustomsNumbers)
        .HasForeignKey(gicn => gicn.GoodsItemId);
    builder.Entity<DangerousGoods>()
        .HasOne<TransportContainer>(dg => dg.TransportContainer)
        .WithMany(tc => tc.DangerousGoods)
        .HasForeignKey(dg => dg.TransportContainerId);
    ...

Tabellen und Relationen s. Anhang.

Dein GoodsItemhat eine Many Navigation zu DangerousGoods

public class GoodsItem : DbModel
{
    ...
    public IEnumerable<DangerousGoods> DangerousGoods {  get; internal set; }
}

Aber Dein DangerousGoods hat keine Navigation zu GoodsItem. Dir fehlt der FK auf der Seite von DangerousGoods. Irgendwo muss die Relation ja hinterlegt werden. Gibst Du das nicht explizit an, dann macht EF Core by design implizit in einer Spalte die heisst wie die Entität + Id ⇒ GoodsItemId.
Das One-To-Many Setup erfordert immer ein FK auf der One-Seite.

Deine FK Konfiguration ist also falsch (bzw. hast Du sie einfach vergessen), die Fehlermeldung absolut korrekt.

PS: gibt nen Grund wieso man in EFCore ICollection verwenden sollte und nicht IEnumerable.
Relationship navigations

Jepp, das war der richtige Hinweis - vielen Dank!

Eigentlich sollte diese Enumeration gar kein Teil einer Relation sein, sondern der Wert händisch gesetzt werden, weshalb der Setter auch internal ist. Aber ich hatte absolut keinen Plan, dass das Framework die Eigenschaft dennoch entsprechend interpretieren und sogar ergänzen würde.

Habe jetzt einfach ein NotMapped-Attribut drüber gesetzt, und nun arbeitet der Code auch wie gewünscht.

Eine Entität sollte prinzipiell nur Eigenschaften haben, die die Datenstruktur repräsentieren.

Alle weiteren Dinge solltest Du als Extensions oder als Methoden umsetzen - zumindest wenn es nach Empfehlung geht.
Dadurch ist für jeden Dev auch sichtbar, dass dies nicht zur Datenstruktur gehört.

Das NotMapped gibt es aus Kompatibilitätsgründen; gibt aber die beiden genannten Alternativen sollten bevorzugt werden, wenn möglich.