简体   繁体   中英

EF Core 3.1 - DDD and inheritance

I am working on a legacy app that mainly manages employees and contractors. Below is an excerpt of the EF Core 3.1 legacy model. The complete source code is available here .

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? ManagerId { get; set; }
    public virtual Person Manager { get; set; }
}

public class Employee : Person
{
    public int Grade { get; set; }
}

public class Contractor : Person
{
    public int ContractorId { get; set; }
    public virtual ContractingCompany Company { get; set; }
}

public class ContractingCompany
{
    public int Id { get; private set; }
    public string Name { get; private set; }
    private readonly List<Contractor> _contractors = new List<Contractor>();
    public virtual IReadOnlyList<Contractor> Contractors => _contractors;

    protected ContractingCompany()
    {
    }

    public ContractingCompany(string name) : this()
    {
        Name = name;
    }

    public void Add(Contractor contractor)
    {
        _contractors.Add(contractor);
    }
}

在此处输入图像描述

These entities pull data from the same table as we are using TPH strategy.

We are extending our application and renaming the contractors to Partners instead. We decided to go with DDD this time and have new models read existing data using table splitting . As we move things to the new model, we need to keep the app working, so we can't remove the legacy model altogether until all use cases have moved to the new DDD model.

The DDD model is as follows and will pull data from the existing database:

public class Partner /* Pulls data from the Contracting Company */
{
    public int Id { get; set; }
    public string Name { get; set; }
    private readonly List<PartnerEmployee> _employees = new List<PartnerEmployee>();
    public virtual IReadOnlyList<PartnerEmployee> Employees => _employees;
    protected Partner(){}
}

public class PartnerEmployee /* Pulls data from the Contractor table */
{
    public int Id { get; set; }
    public int ContractorId { get; set; }
}

The EF mappings are as follows:

public class PartnerConfiguration : IEntityTypeConfiguration<Partner>
{
    public void Configure(EntityTypeBuilder<Partner> builder)
    {
        builder.ToTable("ContractingCompany");
        builder.HasKey(c => c.Id);
        builder.Property(c => c.Id).HasColumnName("Id");
        builder.Property(c => c.Name).HasColumnName("Name");
        builder.HasOne<ContractingCompany>().WithOne().HasForeignKey<Partner>(c => c.Id);
    }
}

public class PartnerEmployeeConfiguration : IEntityTypeConfiguration<PartnerEmployee>
{
    public void Configure(EntityTypeBuilder<PartnerEmployee> builder)
    {
        builder.ToTable("Person");
        builder.HasKey(c => c.Id);
        builder.Property(c => c.Id).HasColumnName("Id");
        builder.Property(c => c.ContractorId).HasColumnName("ContractorId");
        builder.Property<int?>("PartnerId").HasColumnName("CompanyId");
        builder.HasOne<Contractor>().WithOne().HasForeignKey<PartnerEmployee>(c => c.Id);
    }
}

Problem : we are trying to read the existing data from the database:

var contractingCompany = context.ContractingCompanies.First(); <-- Works fine
var partner = context.Partners.First(); <-- Crashes

The second line above throws an exception:

Microsoft.Data.SqlClient.SqlException:
Invalid column name 'Contractor_CompanyId'.
Invalid column name 'ContractorId1'.'

Can anyone help me understand why EF looks up columns Contractor_CompanyId ContractorId1 ?

Looks like configuring a column name for a property of an entity participating in table splitting (other than PK) invalidates the conventional column names for the other participating entity/entities.

Unfortunately this behavior is not explained in the table splitting documentation (it should), only a small text to to accompanying example saying

In addition to the required configuration we call Property(o => o.Status).HasColumnName("Status") to map DetailedOrder.Status to the same column as Order.Status .

and then you can see in the sample fluent configuration that Property(o => o.Status).HasColumnName("Status") is called for both Order and DetailedOrder .

Shortly, you must explicitly configure column names for shared columns for both (all if more then one) entities.

In your case, the minimal configuration needed (in addition of what you have currently) is like this (using modelBuilder fluent API directly, but you can put them in separate entity configuration classes if you wish):

modelBuilder.Entity<ContractingCompany>(builder =>
{
    builder.Property(c => c.Name).HasColumnName("Name");
});

modelBuilder.Entity<Contractor>(builder =>
{
    builder.Property(c => c.ContractorId).HasColumnName("ContractorId");
    builder.Property("CompanyId").HasColumnName("CompanyId");
});

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