Search code examples
c#sqllinqentity-frameworkentity-framework-6.1

EF Code First: Relationships with differing key names


I have two tables:

DocumentSet:
setId (int)
name (varchar(50))

DocIdentifier:
docIdId (int)
docSetId (int) <-- (setId from DocumentSet)
customId (char(9))

The typical queries done on the two tables look like this in SQL:

SELECT ds.name
FROM DocumentSet ds
INNER JOIN DocIdentifiers di ON ds.setId = di.docSetId
WHERE di.customId == 'someId'

Notice the naming difference: setId and docSetId are the columns used for the foreign key. The problem comes when I try to replicated this in Entity Framework. I have the obvious models:

class DocumentSet {
    public int Id {get;set;}
    public string Name {get;set;}
    public virtual ICollection<DocumentIdentifier> Identifiers {get;set;}
}

class DocumentIdentifier {
    public int Id {get;set;}
    public int SetId {get;set;}
    public string CustomId {get;set;}
    public DocumentSet DocSet {get;set;}
}

Again, some naming differences for clarity in the code. So I create EntityTypeConfigurations for both:

internal class DocumentSetTypeConfiguration : EntityTypeConfiguration<DocumentSet> {
    public DocumentSetTypeConfiguration () {
        HasKey(f => f.Id);
        Property(f => f.Id).HasColumnName("setId");
        HasMany(f => f.DocumentIdentifiers);
    }
}

internal class DocumentIdentifierTypeConfiguration : EntityTypeConfiguration<DocumentIdentifier> {
    public DocumentIdentifierTypeConfiguration () {
        ToTable("DocIdentifier");
        HasKey(f => f.Id);
        Property(f => f.Id).HasColumnName("docIdId");
        Property(f => f.SetId).HasColumnName("docSetId");
    }
}

Here's the problem: When I send a LINQ query like this:

_context.Set<DocumentSet>().Where(d => d.DocumentIdentifiers.Any(pt=>pt.CustomId == customId)).Single();

What's generated gets a little botched:

SELECT TOP (2) 
    [Extent1].[SetId] AS [SetId], 
    [Extent1].[Name] AS [Name]
    FROM [dbo].[DocumentSet] AS [Extent1]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[DocIdentifier] AS [Extent2]
        WHERE ([Extent1].[SetId] = [Extent2].[DocumentSet_Id]) AND (([Extent2].CustomId = @p__linq__0) OR (([Extent2].[CustomId] IS NULL) AND (@p__linq__0 IS NULL)))
    )

My guess that the problem lies in the fact that setId and docSetId are different names, but I can't seem to figure out how to get EF to see that [Extent2].[DocumentSet_Id] should be [Extent2].[docSet_Id]. Is there something I'm missing in DocumentIdentifierTypeConfiguration that will help EF understand this? I thought the line Property(f => f.SetId).HasColumnName("docSetId"); would do this, but apparently not.

In short, how do I say

`DocumentSet has many DocumentIdentifiers with a foreign key relationship of DocumentSet.setId = DocIdentifier.docSetId`?

Solution

  • Try adding WithRequired and HasForeignKey.

    HasMany(f => f.Identifiers).WithRequired().HasForeignKey(x => x.SetId);
    

    Full Code

    public class AppContext : DbContext
    {
        public DbSet<DocumentSet> DocumentSets { get; set; }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new DocumentSetTypeConfiguration());
            modelBuilder.Configurations.Add(new DocumentIdentifierTypeConfiguration());
        }
    }
    public class DocumentSetTypeConfiguration : EntityTypeConfiguration<DocumentSet>
    {
        public DocumentSetTypeConfiguration()
        {
            HasKey(f => f.Id);
            Property(f => f.Id).HasColumnName("setId");
            HasMany(f => f.DocumentIdentifiers).WithRequired().HasForeignKey(x => x.SetId);
        }
    }
    public class DocumentIdentifierTypeConfiguration : EntityTypeConfiguration<DocumentIdentifier>
    {
        public DocumentIdentifierTypeConfiguration()
        {
            ToTable("DocIdentifier");
            HasKey(f => f.Id);
            Property(f => f.Id).HasColumnName("docIdId");
            Property(f => f.SetId).HasColumnName("docSetId");
        }
    }
    public class DocumentSet
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public virtual ICollection<DocumentIdentifier> DocumentIdentifiers { get; set; }
    }
    public class DocumentIdentifier
    {
        public int Id { get; set; }
        public int SetId { get; set; }
        public string CustomId { get; set; }
    }
    

    Database Result

    Database Result

    Query Result

    SELECT TOP (2) 
        [Extent1].[setId] AS [setId], 
        [Extent1].[Name] AS [Name]
    FROM [dbo].[DocumentSets] AS [Extent1]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[DocIdentifier] AS [Extent2]
        WHERE ([Extent1].[setId] = [Extent2].[docSetId])
        AND   (([Extent2].[CustomId] = @p__linq__0) 
              OR (([Extent2].[CustomId] IS NULL) AND (@p__linq__0 IS NULL)))
    )