una qué protegidos programacion privados misma los hijo hijas herencias herencia hereda funcionan derivada definidos como clases clase caracteristicas atributos .net entity-framework inheritance ef-code-first table-per-type

.net - qué - los atributos de una clase base definidos como protegidos funcionan como privados en la misma clase



Herencia y claves externas compuestas: una parte de la clave en la clase base, la otra parte en la clase derivada (3)

Tengo problemas para crear una asignación de Entity Framework Code-First para el siguiente esquema de base de datos de ejemplo (en SQL Server):

Cada tabla contiene un TenantId que forma parte de todas las claves primarias y externas (compuestas) (Multi-Tenancy).

Una Company es un Customer o un Supplier y trato de modelar esto a través del mapeo de herencia de tabla por tipo (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; } }

Mapeo con Fluent API:

modelBuilder.Entity<Company>() .HasKey(c => new { c.TenantId, c.CompanyId }); modelBuilder.Entity<Customer>() .ToTable("Customers"); modelBuilder.Entity<Supplier>() .ToTable("Suppliers");

Las Companies tabla base tienen una relación de uno a varios con una Address (cada compañía tiene una dirección, no importa si es cliente o proveedor) y puedo crear una asignación para esta asociación:

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

La clave externa se compone de una parte de la clave principal, el TenantId , y una columna separada, el AddressId . Esto funciona.

Como puede ver en el esquema de la base de datos, desde la perspectiva de la base de datos, la relación entre el Customer y la Person es básicamente el mismo tipo de relación de uno a muchos que existe entre la Company y la Address : la clave externa está compuesta nuevamente por el TenantId (parte de la clave principal). clave) y la columna SalesPersonId . (Solo un cliente tiene una persona de ventas, no un Supplier , por lo tanto, la relación está en la clase derivada esta vez, no en la clase base).

Intento crear una asignación para esta relación con Fluent API de la misma manera que antes:

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

Pero cuando EF intenta compilar el modelo, se lanza una InvalidOperationException :

El componente de clave foránea ''TenantId'' no es una propiedad declarada en el tipo ''Cliente''. Verifique que no se haya excluido explícitamente del modelo y que sea una propiedad primitiva válida.

Aparentemente, no puedo componer una clave foránea a partir de una propiedad en la clase base y de otra propiedad en la clase derivada (aunque en el esquema de la base de datos la clave foránea está compuesta de columnas en la tabla del tipo derivado Customer ).

Intenté dos modificaciones para que funcione quizás:

  • Se cambió la asociación de clave externa entre el Customer y la Person a una asociación independiente, es decir, se eliminó la propiedad SalesPersonId y luego se intentó la asignación:

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

    No ayuda (realmente no lo esperaba, lo haría) y la excepción es:

    El esquema especificado no es válido. ... Cada nombre de propiedad en un tipo debe ser único. El nombre de propiedad ''TenantId'' ya estaba definido.

  • Se cambió la asignación de TPT a TPH, es decir, se eliminaron las dos llamadas de ToTable . Pero lanza la misma excepción.

Veo dos soluciones:

  • Introduzca un SalesPersonTenantId en la clase 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; } }

    y el mapeo:

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

    He probado esto y funciona. Pero tendré una nueva columna SalesPersonTenantId en la tabla Customers además de TenantId . Esta columna es redundante porque ambas columnas siempre deben tener el mismo valor desde la perspectiva del negocio.

  • Abandonar la asignación de herencia y crear asignaciones uno a uno entre la Company y el Customer y entre la Company y el Supplier . Company debe convertirse en un tipo concreto, no abstracto y tendría dos propiedades de navegación en la Company . Pero este modelo no expresaría correctamente que una empresa es un cliente o un proveedor y no puede ser ambos al mismo tiempo. No lo probé pero creo que funcionaría.

Pegue el ejemplo completo que probé con (aplicación de consola, referencia al ensamblaje de EF 4.3.1, descargado a través de NuGet) aquí si a alguien le gusta experimentar con él:

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

Pregunta: ¿Hay alguna forma de asignar el esquema de base de datos anterior a un modelo de clase con Entity Framework?


Bueno, parece que no puedo comentar nada, así que estoy agregando esto como respuesta.

Creé un problema en CodePlex para este problema, así que espero que lo revisen pronto. ¡Manténganse al tanto!

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

El resultado del problema en CodePlex (que se ha cerrado mientras tanto) es que el escenario de la pregunta no es compatible y actualmente no hay planes para soportarlo en un futuro próximo.

Cita del equipo de Entity Framework en CodePlex:

Esto es parte de una limitación más fundamental cuando EF no admite tener una propiedad definida en un tipo base y luego usarla como una clave externa en un tipo derivado. Desafortunadamente, esta es una limitación que sería muy difícil de eliminar de nuestro código base. Dado que no hemos visto muchas solicitudes para esto, no es algo que planeamos abordar en esta etapa, por lo que estamos cerrando este problema.


Creo que es más simple y reduce la complejidad de tener Table -> TableId (PK) -> otras columnas incluyendo FKs.

En su ejemplo, agregar una columna CustomerId a la tabla Clientes resolvería su problema.


No es una solución, sino una solución alternativa (*): una buena opción es usar columnas de Id. Simples (como), generalmente auto-incrementadas, y proporcionar integridad de base de datos usando claves externas, índices únicos, etc. Se podría lograr una integridad de datos más compleja con desencadenadores, por lo que tal vez podría dirigirse de esa manera, pero podría dejarlo en el nivel de lógica de negocios de la aplicación, a menos que la aplicación esté realmente centrada en los datos. ¿Pero ya que está utilizando Entity Framework, es probable que suponga que este no es su caso ...?

(*) según lo sugerido por ivowiblo