简体   繁体   中英

EF Core one-to-many mapping to tables with no keys

I'm trying to map tables from an old IBM Db2 database to entities using EntityFramework Core 2.2 but these tables don't have any keys at all (yes I was shocked too) and I'm not sure if I'm even allowed add them to this ancient beast that has been running since the stone age.

So far I've gotten things to work by reverse engineering the relationships and finding potential keys to use so that EF can happily track these entities, but I'm facing an issue with a one-to-many mapping.

I want to make a one-to-many mapping from the Invoice table to the SellerAccountDetails table. My configuration:

Invoice entity:

public class FinvoiceConfiguration : IEntityTypeConfiguration<Finvoice>
{
    public void Configure(EntityTypeBuilder<Finvoice> builder)
    {
        // PK for Invoice
        builder.HasKey(f => new { f.FinvoiceBatchNumber, f.FinvoiceBatchRowNumber });

        // One-to-many to SellerAccountDetails table
        builder.HasMany(f => f.SellersAccountDetails)
            .WithOne(s => s.Invoice)
            .HasForeignKey(s => new
            {
                s.FinvoiceBatchNumber,
                s.FinvoiceBatchRowNumber,
                s.SellerAccountPartyIdentifier
            })
            .HasPrincipalKey(f => new
            {
                f.FinvoiceBatchNumber,
                f.FinvoiceBatchRowNumber,
                f.SellerPartyIdentifier
            });
    }
}

SellerAccountDetails entity:

public class SellerAccountDetailsConfiguration : IEntityTypeConfiguration<SellerAccountDetails>
{
    public void Configure(EntityTypeBuilder<SellerAccountDetails> builder)
    {
        // PK for SellerAccountDetails
        builder.HasKey(s => new
        {
            s.FinvoiceBatchNumber,
            s.FinvoiceBatchRowNumber,
            s.SellerAccountPartyIdentifier,
            s.SellerAccountID // <-- Needed for uniqueness of this entity
        });

        // Many-to-one to Invoice table
        builder.HasOne(s => s.Invoice)
            .WithMany(i => i.SellersAccountDetails)
            .HasForeignKey(s => new
            {
                s.FinvoiceBatchNumber,
                s.FinvoiceBatchRowNumber,
                s.SellerAccountPartyIdentifier
            })
            .HasPrincipalKey(f => new
            {
                f.FinvoiceBatchNumber,
                f.FinvoiceBatchRowNumber,
                f.SellerPartyIdentifier
            });
    }
}

Now this works when I make a query and then call ToList() , in my test code I'm supposed to get 3 SellerAccountDetails entities for one Finvoice , but if I don't materialize the IQueryable , I get 6 SellerAccountDetails for one Finvoice , and if I enumerate again, they increase to 9, and so on...

Shouldn't EF know from the configured primary key on SellerAccountDetails what makes this entity unique?

I added the HasPrincipalKey because the property SellerPartyIdentifier (from Finvoice) corresponds to the property SellerAccountPartyIdentifier on the accounts table, but it's not a primary key. If I remove it and keep just the 2 foreign keys, the result is the same.

How do I make this work? I might be missing something obvious here, but I don't see it.

Here's my example query:

var invoices = _dbContext.Invoices.Include(i => i.SellersAccountDetails).ThenInclude(s => s.Invoice).
                Where(i => i.FinvoiceBatchNumber == 13491).OrderBy(i => i.FinvoiceBatchRowNumber).Take(1);//.ToList();

Thank you!

You cannot use "Include" linq without key/relationship in Db2.

You have to use below

var invoices = (from a in _dbContext.Invoices.Where(x => x.FinvoiceBatchNumber == 13491) 
                from b in _dbContext.SellersAccountDetails.Where(x => x.InvoiceId = a.Id).DefaultOrEmpty()
                select a, b).Take(1);

Solved! The problem was that the entity properties' getters were trimming the field:

private string _sellerAccountID;

[Column("FV77A", TypeName = "char(35)")]
[Required, DefaultValue("")]
[MinLength(0), MaxLength(35)]
public string SellerAccountID
{
    get { return _sellerAccountID.Trim(); } // <-- Trim() is the culprit
    set { _sellerAccountID = value; }
}

For some reason this was confusing the EF tracker, because if I also disable tracking by using AsNoTracking (or setting it globally by doing optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); then it would also work correctly.

I removed the trim (and the private fields), and did the trimming using AutoMapper on the DTO level like so: CreateMap<string, string>().ConvertUsing((s, d) => s.Trim());

Now I always get 3 results as I should no matter how many times I enumerate the IQueryable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM