0

I have two tables in SQL InvoiceHeader and InvoiceLines that have a one to many relationship where four columns are used as the foreign key. I have no trouble generating entity objects from the database, and can query InvoiceHeader objects without issue.

When I either include the InvoiceLines in the query or try to query them separately, the resulting SQL generated by EF includes in the select statement the InvoiceHeader table name and each of the key columns as a fieldName (example InvoiceHeader.StoreID is included in the select statement as InvoiceHeaderStoreID).

Scaffold generated objects and context build:

InvoiceHeader

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BackOfficeSQL.Data.Models
{
    public partial class InvoiceHeader
    {
        public InvoiceHeader()
        {
            InvoiceLines = new HashSet<InvoiceLines>();
        }

        [Key]
        [Column("StoreID")]
        public short StoreId { get; set; }
        [Key]
        [Column("CashRegisterID")]
        public short CashRegisterId { get; set; }
        [Key]
        [Column("ShiftCounterID")]
        public short ShiftCounterId { get; set; }
        [Key]
        [Column("InvoiceID")]
        public short InvoiceId { get; set; }
        [Column("TransactionTypeID")]
        public short TransactionTypeId { get; set; }
        [Column("CashierID")]
        public short? CashierId { get; set; }
        [Column(TypeName = "datetime")]
        public DateTime? SetupDate { get; set; }
        public bool? Audited { get; set; }
        [StringLength(50)]
        public string AuditedBy { get; set; }
        [Column("VendorID")]
        public int? VendorId { get; set; }
        [Column("POHeadID")]
        public int? PoheadId { get; set; }
        [StringLength(50)]
        public string VendorInvoice { get; set; }
        [Column(TypeName = "datetime")]
        public DateTime? InvoiceDate { get; set; }
        [Column("upsize_ts")]
        public byte[] UpsizeTs { get; set; }

        [ForeignKey(nameof(TransactionTypeId))]
        [InverseProperty(nameof(TransType.InvoiceHeader))]
        public virtual TransType TransactionType { get; set; }
        [InverseProperty("InvoiceHeader")]
        public virtual InvoiceTruckInfo InvoiceTruckInfo { get; set; }
        [InverseProperty("InvoiceHeader")]
        public virtual ICollection<InvoiceLines> InvoiceLines { get; set; }
    }
}

InvoiceLines

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BackOfficeSQL.Data.Models
{
    public partial class InvoiceLines
    {
        [Key]
        [Column("StoreID")]
        public short StoreId { get; set; }
        [Key]
        [Column("CashRegisterID")]
        public short CashRegisterId { get; set; }
        [Key]
        [Column("ShiftCounterID")]
        public short ShiftCounterId { get; set; }
        [Key]
        [Column("InvoiceID")]
        public short InvoiceId { get; set; }
        [Key]
        [Column("InvoiceLineID")]
        public short InvoiceLineId { get; set; }
        [Key]
        [Column("DepartmentTypeID")]
        public short DepartmentTypeId { get; set; }
        [Key]
        public int ItemId { get; set; }
        [Column("DepartmentID")]
        public short? DepartmentId { get; set; }
        [Column("MeterID")]
        public short? MeterId { get; set; }
        [StringLength(100)]
        public string Description { get; set; }
        public double Quantity { get; set; }
        [Column(TypeName = "money")]
        public decimal ExtendedPrice { get; set; }
        public bool? Taxable { get; set; }
        public double? TaxRate { get; set; }
        public short? PriceLevel { get; set; }
        public double? AfterQuantity { get; set; }
        [Column(TypeName = "money")]
        public decimal? UnitCost { get; set; }
        [Column("UPCCode")]
        [StringLength(15)]
        public string Upccode { get; set; }
        public short? UpdateOnhand { get; set; }
        [Column("TableID")]
        public int? TableId { get; set; }
        [Column("upsize_ts")]
        public byte[] UpsizeTs { get; set; }

        [ForeignKey("ShiftCounterId,CashRegisterId,InvoiceId,StoreId")]
        [InverseProperty("InvoiceLines")]
        public virtual InvoiceHeader InvoiceHeader { get; set; }
    }
}

InvoiceHeader DBContext

public virtual DbSet<InvoiceHeader> InvoiceHeader { get; set; }

modelBuilder.Entity<InvoiceHeader>(entity =>
    {
        entity.HasKey(e => new { e.ShiftCounterId, e.CashRegisterId, e.InvoiceId, e.StoreId })
            .HasName("aaaaaInvoiceHeader_PK");

        entity.HasIndex(e => e.PoheadId)
            .HasName("POHeadID");

        entity.HasIndex(e => e.VendorId)
            .HasName("VendorID");

        entity.HasIndex(e => new { e.InvoiceDate, e.TransactionTypeId })
            .HasName("IX_InvoiceHeader_InvoiceDate");

        entity.HasIndex(e => new { e.ShiftCounterId, e.CashRegisterId, e.InvoiceId, e.TransactionTypeId, e.StoreId })
            .HasName("TransactionTypeID")
            .IsUnique();

        entity.Property(e => e.ShiftCounterId).HasDefaultValueSql("(0)");
        entity.Property(e => e.CashRegisterId).HasDefaultValueSql("(0)");
        entity.Property(e => e.InvoiceId).HasDefaultValueSql("(0)");
        entity.Property(e => e.StoreId).HasDefaultValueSql("(0)");
        entity.Property(e => e.Audited).HasDefaultValueSql("(0)");
        entity.Property(e => e.AuditedBy).IsUnicode(false);
        entity.Property(e => e.CashierId).HasDefaultValueSql("(0)");
        entity.Property(e => e.InvoiceDate).HasDefaultValueSql("(getdate())");
        entity.Property(e => e.PoheadId).HasDefaultValueSql("(0)");
        entity.Property(e => e.SetupDate).HasDefaultValueSql("(getdate())");
        entity.Property(e => e.TransactionTypeId).HasDefaultValueSql("(1)");
        entity.Property(e => e.UpsizeTs)
            .IsRowVersion()
            .IsConcurrencyToken();

        entity.Property(e => e.VendorId).HasDefaultValueSql("(0)");
        entity.Property(e => e.VendorInvoice).IsUnicode(false);

        entity.HasOne(d => d.TransactionType)
            .WithMany(p => p.InvoiceHeader)
            .HasForeignKey(d => d.TransactionTypeId)
            .OnDelete(DeleteBehavior.ClientSetNull)
            .HasConstraintName("FK_InvoiceHeader_TransType");
    });

DbContext

    public virtual DbSet<InvoiceLines> InvoiceLines { get; set; }
    
    modelBuilder.Entity<InvoiceLines>(entity =>
                {
                    entity.HasKey(e => new { e.ShiftCounterId, e.CashRegisterId, e.InvoiceId, e.InvoiceLineId, e.DepartmentTypeId, e.StoreId, e.ItemId });
    
                    entity.HasIndex(e => e.ItemId)
                        .HasName("ItemId");
    
                    entity.HasIndex(e => e.MeterId)
                        .HasName("IX_MeterID_ServerID");
    
                    entity.HasIndex(e => e.Upccode)
                        .HasName("UPCCode");
    
                    entity.HasIndex(e => e.UpdateOnhand)
                        .HasName("IX_UpdateOnhand");
    
                    entity.HasIndex(e => new { e.ShiftCounterId, e.CashRegisterId, e.InvoiceId, e.StoreId })
                        .HasName("IX_InvoiceLines");
    
                    entity.HasIndex(e => new { e.StoreId, e.ShiftCounterId, e.CashRegisterId, e.InvoiceId, e.InvoiceLineId })
                        .HasName("UK_InvoiceLines")
                        .IsUnique();
    
                    entity.Property(e => e.ShiftCounterId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.CashRegisterId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.InvoiceId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.InvoiceLineId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.DepartmentTypeId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.StoreId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.ItemId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.AfterQuantity).HasDefaultValueSql("((-1))");
                    entity.Property(e => e.DepartmentId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.Description).IsUnicode(false);
                    entity.Property(e => e.ExtendedPrice).HasDefaultValueSql("(0)");
                    entity.Property(e => e.MeterId).HasDefaultValueSql("(0)");
                    entity.Property(e => e.PriceLevel).HasDefaultValueSql("(0)");
                    entity.Property(e => e.Quantity).HasDefaultValueSql("(0)");
                    entity.Property(e => e.TaxRate).HasDefaultValueSql("(0)");
                    entity.Property(e => e.Taxable).HasDefaultValueSql("(0)");
                    entity.Property(e => e.UnitCost).HasDefaultValueSql("(0)");
                    entity.Property(e => e.Upccode).IsUnicode(false);
                    entity.Property(e => e.UpdateOnhand).HasDefaultValueSql("(0)");
                    entity.Property(e => e.UpsizeTs)
                        .IsRowVersion()
                        .IsConcurrencyToken();
    
                    entity.HasOne(d => d.InvoiceHeader)
                        .WithMany(p => p.InvoiceLines)
                        .HasForeignKey(d => new { d.ShiftCounterId, d.CashRegisterId, d.InvoiceId, d.StoreId })
                        .HasConstraintName("FK_InvoiceLines_InvoiceHeader");
                });

Testing query of entities:

Invoice = context.InvoiceHeader.Where(x => x.StoreId == 1 && x.CashRegisterId == 54 && x.ShiftCounterId == 1 && x.InvoiceId == 24)
                               // .Include(l => l.InvoiceLines) //Causes An exception
                               .Include(t => t.InvoiceTruckInfo)
                               .FirstOrDefault();

To isolate the problem I was able to query the InvoiceLines entity and use a reflection method to find the EF generated SQL.

var query = context.InvoiceLines.Where(x => x.StoreId == 1 && x.CashRegisterId == 54 && x.ShiftCounterId == 1 && x.InvoiceId == 24);

var sql = query.ToSql();

The resulting SQL statement is:

SELECT [i].[ShiftCounterID], 
       [i].[CashRegisterID], 
       [i].[InvoiceID], 
       [i].[InvoiceLineID], 
       [i].[DepartmentTypeID], 
       [i].[StoreID], 
       [i].[ItemId], 
       [i].[AfterQuantity], 
       [i].[DepartmentID], 
       [i].[Description], 
       [i].[ExtendedPrice], 
       [i].[InvoiceHeaderCashRegisterId], 
       [i].[InvoiceHeaderInvoiceId], 
       [i].[InvoiceHeaderShiftCounterId], 
       [i].[InvoiceHeaderStoreId], 
       [i].[MeterID], 
       [i].[PriceLevel], 
       [i].[Quantity], 
       [i].[TableID], 
       [i].[TaxRate], 
       [i].[Taxable], 
       [i].[UnitCost], 
       [i].[UPCCode], 
       [i].[UpdateOnhand], 
       [i].[upsize_ts]
FROM [InvoiceLines] AS [i]
WHERE ((([i].[StoreID] = CAST(1 AS smallint)) 
AND ([i].[CashRegisterID] = CAST(54 AS smallint))) 
AND ([i].[ShiftCounterID] = CAST(1 AS smallint))) 
AND ([i].[InvoiceID] = CAST(24 AS smallint))

The following 4 columns are invalid and if I remove them from the SQL above I'm able to run the SQL query without issue:

   [i].[InvoiceHeaderCashRegisterId], 
   [i].[InvoiceHeaderInvoiceId], 
   [i].[InvoiceHeaderShiftCounterId], 
   [i].[InvoiceHeaderStoreId], 

I've tried various modifications to the EF utility generated entities even though everything I've looked at example and documentation wise makes the entities and context build up seem correct. I'm not sure why or where this combined Table and field name is coming into play being inserted into the resulting SQL select statement.

When the code is executed to query InvoiceHeader and include the InvoiceLines or the InvoiceLines query executed, a SQL exception is thrown, unsurprisingly:

Invalid column name 'InvoiceHeaderCashRegisterId'.
Invalid column name 'InvoiceHeaderInvoiceId'.
Invalid column name 'InvoiceHeaderShiftCounterId'.
Invalid column name 'InvoiceHeaderStoreId'.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • What do you mean by "InvoiceHeader DBContext" and "InvoiceLines DBContext"? Are you using 2 separate contexts, one for each entity in question? – Ivan Stoev Aug 20 '20 at 18:36
  • It looks like it's creating those fields as foreign key fields to connect to the InvoiceHeader table. Take a look at this link for some guidance on creating multiple column foreign key references, and see if it helps to fix the problem. https://stackoverflow.com/questions/5436731/composite-key-as-foreign-key – basodre Aug 20 '20 at 19:05
  • @Ivan Stoev Nope, it's the same context object, but its a decent sized database with quite a few entities and I was trying to show in a clear way the context generated code for each table. – Jeremy Swartz Aug 20 '20 at 19:06
  • Ok. Another question: are you are sure the shown fluent configuration is executed and there is no hidden relevant code? Such column names (which are the default conventional FP property/column names) usually are caused by improper relationship mapping. – Ivan Stoev Aug 20 '20 at 19:18
  • 1
    @Ivan Stoev - Okay now I feel foolish. I had added some partial classes to the project before attempting to query the InvoiceLines and after commenting out these partial classes the problem stopped. So it wasn't an issue with EF or the tooling or something weird with the database schema, but something I did in the partial classes, which gets me going in the right direction, thanks! – Jeremy Swartz Aug 20 '20 at 19:56
  • 1
    @Ivan Stoev - I had tried something similar with previous .Net Framework and EF versions back when they used TT4 templates and ran into problems with composite key relationships, so I was thinking it was an EF issue, your question really helped as now I can focus on what I did wrong instead of chasing what I incorrectly assumed was an EF problem, if you want to post something as a solution please do so so I can mark it as correct, thanks again! – Jeremy Swartz Aug 20 '20 at 20:13

0 Answers0