次のサンプル データベース スキーマ (SQL Server) の Entity Framework Code-First マッピングを作成する際に問題が発生しています。
すべてのテーブルには、TenantId
すべての (複合) プライマリ キーと外部キー (マルチテナンシー) の一部である が含まれています。
ACompany
は aCustomer
または a のいずれかであり、 Supplier
Table-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 対多の関係と同じですCompany
。Address
外部キーは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 つの回避策があります。
クラスに a
SalesPersonTenantId
を導入します。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 のマッピングを作成しますSupplier
。Company
次に、抽象型ではなく具象型になる必要があり、 に 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 を使用してクラス モデルにマップする方法はありますか?