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

时间:2014-01-09 23:35:21

标签: 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...

但是在第一次请求时,这个FAILS“Sequence包含多个匹配元素”:

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

但是当同样的错误时,这会失败。咦?类定义是IDENTICAL作为工作副本,除了它们被声明为“覆盖”而不是“虚拟”。这就好像E / F在PropertyInfos上建立索引或者不考虑PropertyInfo.ReflectedType

尝试改进#3

我可以使用接口强制执行该模式,但这不太可取,因为必须在每个类上声明接口,这看起来很奇怪:

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

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

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

咦吗

这是E / F中的一个错误,它是否难以处理基类上与派生类相同的属性?

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

1 个答案:

答案 0 :(得分:4)

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

代码&amp;模特不优雅,但在一天结束时表现不错。我已经在3个区域实现了模式,并且它在域和SQL层中按要求执行。

为了减轻痛苦并为开发人员提供使用此模式的一致方法,我创建了此接口以强制执行所有关系:

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);
    }
}