4

問題

前の質問のようにテーブル分割を使用して、約 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

私はそれらをabstractと宣言できると思ったので、少なくともプログラマーは正しいメンバーを実装することを余儀なくされます(派生クラスごとに再宣言するのはまだ面倒です):

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 が、PropertyInfo.ReflectedType に関係なく、PropertyInfos または何かにインデックスを付けているかのようです。

改善の試み #3

インターフェイスを使用してパターンを強制することもできますが、かなり奇妙に見え始めている各クラスでインターフェイスを宣言する必要があるため、これはあまり好ましくありません。

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

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

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

は?

これは E/F のバグですか? 基本クラスのプロパティを派生クラスのプロパティと同じように扱うのに苦労していますか?

説明が長くなってしまい申し訳ありません、今朝の全調査のまとめです。

4

1 に答える 1

5

結局、改善の試みがうまくいかなかったため、手動の解決策を採用することにしました。

コードとモデルはエレガントではありませんが、最終的には問題なく動作します。このパターンを 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);
    }
}
于 2014-01-15T23:32:26.603 に答える