Наследование и составные внешние ключи — одна часть ключа в базовом классе, другая часть в производном классе

У меня возникли проблемы с созданием сопоставления Code-First в Entity Framework для следующего образца схемы базы данных (в SQL Server):

Database schema with composite foreign keys

Every таблица содержит TenantId, который является частью всех (составных) первичных и внешних ключей (Multi-Tenancy).

Компанияявляется либо Клиентом, либо Поставщиком, и я пытаюсь смоделировать это с помощью сопоставления наследования 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");

Базовая таблица Компанииимеет отношение «один ко многим» к Адресу(у каждой компании есть адрес, независимо от того, клиент это или поставщик) и я могу создать сопоставление для этой ассоциации:

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

Внешний ключ состоит из одной части первичного ключа — TenantId— и отдельного столбца — AddressId. Это работает.

Как видно из схемы базы данных, с точки зрения базы данных отношения между Customerи Personв основном такие же отношения один ко многим, как между Companyи Address— внешний ключ снова состоит из TenantId(часть первичного ключа) и столбца SalesPersonId.(Только у клиента есть продавец, а не Поставщик, поэтому на этот раз отношение находится в производном классе, а не в базовом классе.)

Я пытаюсь создать отображение для этого отношения с помощью Fluent API такой же, как и раньше:

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

Но когда EF пытается скомпилировать модель, выдается InvalidOperationException:

Компонент внешнего ключа «TenantId» не является объявленным свойством на введите «Клиент». Убедитесь, что он не был явно исключен из модели и что это допустимое примитивное свойство.

По-видимому, я не могу составить внешний ключ из свойства в базовом классе и из другого свойства в производном классе (хотя в схеме базы данных внешний ключ состоит из столбцов и в таблице производного типа Заказчик).

Я попробовал две модификации, чтобы заставить его работать, возможно:

  • Изменил связь внешнего ключа между Customerи Personна независимую ассоциацию, т.е. удалил свойство SalesPersonId , а затем попробовал сопоставление:

    modelBuilder.Entity()
     .HasRequired(c => c.SalesPerson)
     .Со многими()
     .Map(m => m.MapKey("TenantId", "SalesPersonId"));
    

    Это не помогает (я на самом деле не надеялся, что поможет), и исключение:

    Указанная схема недействительна. ... Каждое имя свойства в типе должно быть уникальный. Имя свойства TenantId уже определено.

  • Изменено сопоставление TPT с TPH, т. е. удалены два вызова ToTable. Но выдает такое же исключение.

Я вижу два обходных пути:

  • Ввести SalesPersonTenantIdв класс Customer:

    открытый класс Customer : Company
    {
    публичная строка CustomerName { получить; задавать; }
    
    публичный интервал SalesPersonTenantId { получить; задавать; }
    общественный интервал SalesPersonId { получить; задавать; }
    общественное лицо SalesPerson { получить; задавать; }
    }
    

    и сопоставление:

    modelBuilder.Entity()
     .HasRequired(c => c.SalesPerson)
     .Со многими()
     .HasForeignKey(c => new { c.SalesPersonTenantId, c.SalesPersonId});
    

    Я проверил это, и это работает. Но у меня будет новый столбец SalesPersonTenantIdв таблице Customersв дополнение к TenantId. Этот столбец является избыточным, поскольку оба столбца всегда должны иметь одно и то же значение с точки зрения бизнеса.

  • Отменить сопоставление наследования и создать взаимно однозначное сопоставление между Компаниейи Заказчикоми между Компаниейи Поставщиком. Companyтогда должна стать конкретным типом, а не абстрактным, и у меня будет два навигационных свойства в Company. Но эта модель не будет правильно выражать, что компания является либо покупателем , либо поставщиком и не может быть и тем, и другим одновременно. Я не проверял это, но я верю, что это сработает.

Я вставляю полный пример, который тестировал (консольное приложение, ссылка на EF 4.3.1, скачанная через NuGet) здесь, если кому-то нравится с ней экспериментировать:

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?

17
задан Slauma 9 June 2012 в 14:17
поделиться