17

次のサンプル データベース スキーマ (SQL Server) の Entity Framework Code-First マッピングを作成する際に問題が発生しています。

複合外部キーを持つデータベース スキーマ

すべてのテーブルには、TenantIdすべての (複合) プライマリ キーと外部キー (マルチテナンシー) の一部である が含まれています。

ACompanyは aCustomerまたは a のいずれかであり、 SupplierTable-Per-Type (TPT) 継承マッピングを介してこれをモデル化しようとしています。

public abstract class Company
{
    public int TenantId { get; set; }
    public int CompanyId { get; set; }

    public int AddressId { get; set; }
    public Address Address { get; set; }
}

public class Customer : Company
{
    public string CustomerName { get; set; }

    public int SalesPersonId { get; set; }
    public Person SalesPerson { get; set; }
}

public class Supplier : Company
{
    public string SupplierName { get; set; }
}

Fluent API を使用したマッピング:

modelBuilder.Entity<Company>()
    .HasKey(c => new { c.TenantId, c.CompanyId });

modelBuilder.Entity<Customer>()
    .ToTable("Customers");

modelBuilder.Entity<Supplier>()
    .ToTable("Suppliers");

ベース テーブルCompaniesには と 1 対多の関係がありAddress(顧客かサプライヤかに関係なく、すべての会社に住所があります)、この関連付けのマッピングを作成できます。

 modelBuilder.Entity<Company>()
     .HasRequired(c => c.Address)
     .WithMany()
     .HasForeignKey(c => new { c.TenantId, c.AddressId });

外部キーは、主キーの一部であるTenantIdと別の列である で構成されますAddressId。これは機能します。

データベース スキーマでわかるように、データベースの観点からすると、Customerとの関係Personは基本的に と の 1 対多の関係と同じですCompanyAddress外部キーはTenantId(主キーの一部) と列で構成されます。 SalesPersonId. (顧客だけが営業担当者ではなく営業担当者を持っているSupplierため、関係は今回は基本クラスではなく派生クラスにあります。)

前と同じ方法で、Fluent API とのこの関係のマッピングを作成しようとします。

modelBuilder.Entity<Customer>()
    .HasRequired(c => c.SalesPerson)
    .WithMany()
    .HasForeignKey(c => new { c.TenantId, c.SalesPersonId });

しかし、EF がモデルをコンパイルしようとすると、次のエラーInvalidOperationExceptionがスローされます。

外部キー コンポーネント 'TenantId' は、タイプ 'Customer' で宣言されたプロパティではありません。モデルから明示的に除外されていないこと、および有効なプリミティブ プロパティであることを確認してください。

どうやら、基本クラスのプロパティと派生クラスの別のプロパティから外部キーを作成することはできません (ただし、データベース スキーマでは、外部キーは派生型の table の両方Customerの列で構成されます)。

おそらく動作させるために2つの変更を試みました:

  • Customerとの間の外部キーの関連付けをPerson独立した関連付けに変更しました。つまり、プロパティSalesPersonIdを削除してから、マッピングを試みました。

    modelBuilder.Entity<Customer>()
        .HasRequired(c => c.SalesPerson)
        .WithMany()
        .Map(m => m.MapKey("TenantId", "SalesPersonId"));
    

    それは役に立ちません(私は本当に望んでいませんでした)そして例外は次のとおりです:

    指定されたスキーマは無効です。... タイプ内の各プロパティ名は一意である必要があります。プロパティ名 'TenantId' は既に定義されています。

  • TPT から TPH へのマッピングが変更されました。つまり、2 つのToTable呼び出しが削除されました。しかし、それは同じ例外をスローします。

2 つの回避策があります。

  • クラスに aSalesPersonTenantIdを導入します。Customer

    public class Customer : Company
    {
        public string CustomerName { get; set; }
    
        public int SalesPersonTenantId { get; set; }
        public int SalesPersonId { get; set; }
        public Person SalesPerson { get; set; }
    }
    

    およびマッピング:

    modelBuilder.Entity<Customer>()
        .HasRequired(c => c.SalesPerson)
        .WithMany()
        .HasForeignKey(c => new { c.SalesPersonTenantId, c.SalesPersonId });
    

    これをテストしましたが、動作します。SalesPersonTenantIdただし、に加えて、Customersテーブルに新しい列がありますTenantId。ビジネスの観点から、両方の列が常に同じ値である必要があるため、この列は冗長です。

  • Company継承マッピングを放棄し、 と の間、Customerと の間Companyに1 対 1 のマッピングを作成しますSupplierCompany次に、抽象型ではなく具象型になる必要があり、 に 2 つのナビゲーション プロパティがありCompanyます。しかし、このモデルは、企業が顧客またはサプライヤーのいずれかであり、同時に両方になることはできないことを正しく表現していません。私はそれをテストしませんでしたが、うまくいくと信じています。

テストした完全な例 (コンソール アプリケーション、NuGet 経由でダウンロードした EF 4.3.1 アセンブリへの参照) をここに貼り付けます。

using System;
using System.Data.Entity;

namespace EFTPTCompositeKeys
{
    public abstract class Company
    {
        public int TenantId { get; set; }
        public int CompanyId { get; set; }

        public int AddressId { get; set; }
        public Address Address { get; set; }
    }

    public class Customer : Company
    {
        public string CustomerName { get; set; }

        public int SalesPersonId { get; set; }
        public Person SalesPerson { get; set; }
    }

    public class Supplier : Company
    {
        public string SupplierName { get; set; }
    }

    public class Address
    {
        public int TenantId { get; set; }
        public int AddressId { get; set; }

        public string City { get; set; }
    }

    public class Person
    {
        public int TenantId { get; set; }
        public int PersonId { get; set; }

        public string Name { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Company> Companies { get; set; }
        public DbSet<Address> Addresses { get; set; }
        public DbSet<Person> Persons { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Company>()
                .HasKey(c => new { c.TenantId, c.CompanyId });

            modelBuilder.Entity<Company>()
                .HasRequired(c => c.Address)
                .WithMany()
                .HasForeignKey(c => new { c.TenantId, c.AddressId });

            modelBuilder.Entity<Customer>()
                .ToTable("Customers");

            // the following mapping doesn't work and causes an exception
            modelBuilder.Entity<Customer>()
                .HasRequired(c => c.SalesPerson)
                .WithMany()
                .HasForeignKey(c => new { c.TenantId, c.SalesPersonId });

            modelBuilder.Entity<Supplier>()
                .ToTable("Suppliers");

            modelBuilder.Entity<Address>()
                .HasKey(a => new { a.TenantId, a.AddressId });

            modelBuilder.Entity<Person>()
                .HasKey(p => new { p.TenantId, p.PersonId });
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
            using (var ctx = new MyContext())
            {
                try
                {
                    ctx.Database.Initialize(true);
                }
                catch (Exception e)
                {
                    throw;
                }
            }
        }
    }
}

質問: 上記のデータベース スキーマを Entity Framework を使用してクラス モデルにマップする方法はありますか?

4

3 に答える 3

7

まあ、私は何もコメントできないようですので、これを答えとして追加します。

この問題について CodePlex で問題を作成したので、すぐに調査してくれることを願っています。乞うご期待!

http://entityframework.codeplex.com/workitem/865


CodePlex でのイシュー (当面はクローズされています) の結果、問題のシナリオはサポートされておらず、現在、近い将来にサポートする予定はありません。

CodePlex の Entity Framework チームからの引用:

これは、基本型でプロパティを定義し、それを派生型の外部キーとして使用することを EF がサポートしていない、より基本的な制限の一部です。残念ながら、これはコード ベースから取り除くのが非常に難しい制限です。多くのリクエストが寄せられていないことを考えると、この段階で対処する予定はないため、この問題をクローズします。

于 2013-02-14T16:59:36.977 に答える
0

解決策ではありませんが、回避策(*):通常は自動インクリメントされる単一のID列(as)を使用し、外部キーや一意のインデックスなどを使用してデータベースの整合性を提供することをお勧めします。トリガーになるので、その方向に向かっている可能性がありますが、アプリケーションが実際にデータに焦点を合わせている場合を除いて、アプリケーションのビジネスロジックレベルに任せることができます。しかし、Entity Frameworkを使用しているので、これがあなたのケースではないと考えるのはおそらく安全です...?

(*)ivowibloによって提案されたとおり

于 2012-06-18T14:34:13.870 に答える
0

Table --> TableId (PK) --> FK を含む他の列を持つ方が簡単で、複雑さが軽減されると思います。

したがって、あなたの例では、 CustomerId 列を Customers テーブルに追加すると、問題が解決します。

于 2012-09-16T15:27:12.043 に答える