entity-framework - EF6 无法为表拆分/共享主键 + 基类构建模型?

标签 entity-framework inheritance domain-driven-design shared-primary-key table-splitting

问题

我正在尝试使用根据 my previous question 的表拆分来共享大约 7 个实体的大表(200 多个字段) .

EF6 不仅需要从主模型到子模型的导航属性,还需要所有子模型之间的导航属性(这很糟糕)。

手动解决方案

这可以手动完成:

public class Franchise
{
    [Key]
    public int Id { get; set; }
    public virtual FranchiseEntity Entity { get; set; }
    public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
}

[Table("Franchise")]
public class FranchiseEntity
{
    [Key]
    public int Id { get; set; }
    public virtual FranchiseEntity Entity { get; set; } // Ignored, but relevant when inheritance involved, below...
    public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
}

[Table("Franchise")]
public class FranchiseMiscellaneous
{
    [Key]
    public int Id { get; set; }
    public virtual FranchiseEntity Entity { get; set;
    public virtual FranchiseMiscellaneous Miscellaneous { get; set; }  // Ignored, but relevant when inheritance involved, below...
}

使用流畅的映射:
public class FranchiseMapping : EntityTypeConfiguration<Franchise>
{
    public FranchiseMapping()
    {
        HasRequired(x => x.Entity).WithRequiredPrincipal();
        HasRequired(x => x.Miscellaneous).WithRequiredPrincipal();
    }
}

public class FranchiseEntityMapping : EntityTypeConfiguration<FranchiseEntity>
{
    public FranchiseEntityMapping()
    {
        Ignore(x => x.Entity);
        HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.Entity);
    }
}

public class FranchiseMiscellaneousMapping : EntityTypeConfiguration<FranchiseMiscellaneous>
{
    public FranchiseMiscellaneousMapping()
    {
        Ignore(x => x.Miscellaneous);
    }
}

这行得通。但这不会不适用于 7+ 模型。

尝试改进#1

我想通过继承+ DRY原则改进:
public abstract class SharedFranchiseIdBase
{
    [Key]
    public int Id { get; set; }
    public virtual FranchiseEntity Entity { get; set; }
    public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
}

public class Franchise : SharedFranchiseIdBase { ... }

public class FranchiseEntity : SharedFranchiseIdBase { ... }

public class FranchiseMiscellaneous : SharedFranchiseIdBase { ... }

// Maybe generalize the mapping code too...

但是在第一次请求时失败,“序列包含多个匹配元素”:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: Sequence contains more than one matching element
Result StackTrace:  
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
   at System.Data.Entity.ModelConfiguration.Configuration.Properties.Navigation.NavigationPropertyConfiguration.ConfigureDependentBehavior(AssociationType associationType, EdmModel model, EntityTypeConfiguration entityTypeConfiguration)
   at System.Data.Entity.ModelConfiguration.Configuration.Properties.Navigation.NavigationPropertyConfiguration.Configure(NavigationProperty navigationProperty, EdmModel model, EntityTypeConfiguration entityTypeConfiguration)
   at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.ConfigureAssociations(EntityType entityType, EdmModel model)
   at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Configure(EntityType entityType, EdmModel model)
   at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.ConfigureEntities(EdmModel model)
   at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo)
   at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
   at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
   at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
   at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext()
   at System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider()
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
   ... // my test query function

尝试改进#2

我以为我可以将它们声明为抽象的,因此至少程序员被迫实现正确的成员(仍然很糟糕在每个派生类上重新声明):
public abstract class SharedFranchiseIdBase
{
    [Key]
    public int Id { get; set; }
    public abstract FranchiseEntity Entity { get; set; }
    public abstract FranchiseMiscellaneous Miscellaneous { get; set; }
}

public class Franchise : SharedFranchiseIdBase
{
    [Key]
    public int Id { get; set; }
    public override FranchiseEntity Entity { get; set; }
    public override FranchiseMiscellaneous Miscellaneous { get; set; }
}
//etc for other classes

但这失败时同样的错误。啊??类定义与工作副本相同,只是它们被声明为“覆盖”而不是“虚拟”。就好像 E/F 正在索引 PropertyInfos 或不考虑 PropertyInfo.ReflectedType 的东西

尝试改进#3

我可以使用接口(interface)强制执行该模式,但这不太可取,因为必须在每个类上声明接口(interface),这开始看起来很奇怪:
public class Franchise : SharedFranchiseIdBase, ISharedFranchiseId { ... }

public class FranchiseEntity : SharedFranchiseIdBase, ISharedFranchiseId { ... }

public class FranchiseMiscellaneous : SharedFranchiseIdBase, ISharedFranchiseId { ... }

嗯?

这是 E/F 中的一个错误,它很难将基类上的属性与派生类上的属性相同吗?

抱歉冗长的解释,这是今天上午整个调查的总结。

最佳答案

最后,我决定采用手动解决方案,因为我无法进行任何改进尝试。

代码和模型并不优雅,但归根结底它表现还不错。我已经在 3 个区域中实现了该模式,并且它在域和 SQL 层中按要求执行。

为了减轻痛苦并为开发人员提供使用这种模式的一致方式,我创建了这个接口(interface)来强制执行所有关系:

public interface ISharedFranchiseId
{
    FranchiseBilling Billing { get; set; }
    FranchiseCompliance Compliance { get; set; }
    FranchiseLeadAllocation LeadAllocation { get; set; }
    FranchiseMessaging Messaging { get; set; }
    FranchiseMiscellaneous Miscellaneous { get; set; }
    FranchiseSignup Signup { get; set; }
}

所以每个共享主键的模型都有这些属性(烦人的一点):
public class FranchiseBilling/Compliance/etc : ISharedFranchiseId
{
    // Properties implemented on this model
    #region Navigations to other entities sharing primary key
    public virtual FranchiseBilling Billing { get; set; }
    public virtual FranchiseCompliance Compliance { get; set; }
    public virtual FranchiseLeadAllocation LeadAllocation { get; set; }
    public virtual FranchiseMessaging Messaging { get; set; }
    public virtual FranchiseMiscellaneous Miscellaneous { get; set; }
    public virtual FranchiseSignup Signup { get; set; }
    #endregion
}

并通过 Fluent API 进行如下配置(痛苦的一点):
// Franchise = the "primary/parent" model
public class FranchiseMapping : EntityTypeConfiguration<Franchise>
{
    public FranchiseMapping()
    {
        HasRequired(x => x.Billing).WithRequiredPrincipal();
        HasRequired(x => x.Compliance).WithRequiredPrincipal();
        HasRequired(x => x.LeadAllocation).WithRequiredPrincipal();
        HasRequired(x => x.Miscellaneous).WithRequiredPrincipal();
        HasRequired(x => x.Messaging).WithRequiredPrincipal();
        HasRequired(x => x.Signup).WithRequiredPrincipal();
    }
}

// Now each "child" model gets link to all the others. We only need links going one way,
// So each model links to the ones listed below.
// This makes it easy to implement an extra child model down the track as we just
// insert the configuration it here and copy from the next one.
public class FranchiseBillingMapping : EntityTypeConfiguration<FranchiseBilling>
{
    public FranchiseBillingMapping()
    {
        Ignore(x => x.Billing);
        HasRequired(x => x.Compliance).WithRequiredDependent(x => x.Billing);
        HasRequired(x => x.LeadAllocation).WithRequiredPrincipal(x => x.Billing);
        HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.Billing);
        HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.Billing);
        HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Billing);
    }
}

public class FranchiseComplianceMapping : EntityTypeConfiguration<FranchiseCompliance>
{
    public FranchiseComplianceMapping()
    {
        Ignore(x => x.Compliance);
        HasRequired(x => x.LeadAllocation).WithRequiredPrincipal(x => x.Compliance);
        HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.Compliance);
        HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.Compliance);
        HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Compliance);
    }
}

public class FranchiseLeadAllocationMapping : EntityTypeConfiguration<FranchiseLeadAllocation>
{
    public FranchiseLeadAllocationMapping()
    {
        Ignore(x => x.LeadAllocation);
        HasRequired(x => x.Miscellaneous).WithRequiredPrincipal(x => x.LeadAllocation);
        HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.LeadAllocation);
        HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.LeadAllocation);
    }
}

public class FranchiseeMiscellaneousMapping : EntityTypeConfiguration<FranchiseeMiscellaneous>
{
    public FranchiseeMiscellaneousMapping()
    {
        Ignore(x => x.Miscellaneous);
        HasRequired(x => x.Messaging).WithRequiredPrincipal(x => x.Miscellaneous);
        HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Miscellaneous);
    }
}

public class FranchiseMessagingMapping : EntityTypeConfiguration<FranchiseMessaging>
{
    public FranchiseMessagingMapping()
    {
        Ignore(x => x.Messaging);
        HasRequired(x => x.Signup).WithRequiredPrincipal(x => x.Messaging);
    }
}

public class FranchiseSignupMapping : EntityTypeConfiguration<FranchiseSignup>
{
    public FranchiseSignupMapping()
    {
        Ignore(x => x.Signup);
    }
}

关于entity-framework - EF6 无法为表拆分/共享主键 + 基类构建模型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21033452/

相关文章:

javascript - 理解 JavaScript 原型(prototype)继承

domain-driven-design - 在领域驱动设计中实现限界上下文

c# - IQueryable 不包含 SingleOrDefault 的定义

c# - 在删除属性之前,必须删除或重新定义所有包含的外键 - EF Core

c++ - C++ 中的协变返回类型到底是什么?

iOS:如何使工厂方法与子类一起工作

domain-driven-design - 在事件溯源中如何处理一致性违规?

asp.net-mvc - 使用结合了状态模式的域模型

c# - savechanges 方法中的“System.Data.Entity.Infrastructure.DbUpdateException”

c# - dbcontext.Add 和 dbcontext.AddObject 之间有什么区别