sql-server - truncar - no se puede quitar la restricción consulte los errores anteriores
Uso de clave externa(FK) como discriminador para la tabla por jerarquía(TPH) (3)
[P:] ¿Es posible usar un FK como discriminador en EF y qué soluciones tienen las personas?
El escenario
Objetos EF
public class List
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ListItem> Items { get; set; }
}
public abstract class ListItem
{
public int Id { get; set; }
public List List { get; set; }
public string Text { get; set; }
}
Base de datos
La base de datos existente que no es utilizada exclusivamente por EF (es decir, no se puede cambiar) tiene los siguientes campos:
List
Id int not null (identity)
Name varchar
ListItem
Id int not null (identity)
ListId int not null (FK to List.Id)
Text varchar
El resultado deseado
Quiero que el ID de la lista sea el discriminador para el artículo de lista. es decir, para cada entrada en la lista se implementa una clase separada descendiente de ListItem .
Por ejemplo para la lista [Id: 1]
public class PersonListItem : ListItem
{
public int PersonId { get; set; }
public Person Person { get; set; }
}
public class ListItemConfiguration : EntityTypeConfiguration<ListItem>
{
Map<PersonListItem>(m => m.Requires("ListId").HasValue(1));
}
En el escenario anterior, guardar los cambios resulta en una excepción de SQL porque EF intenta insertar en List_Id al crear una nueva instancia de ListItem . List_Id no es un campo, y no puedo asignar ListId como una propiedad en ListItem, ya que no podría usarse como discriminador.
Mi solución hasta ahora ...
Estas preguntas y respuestas explican por qué tomaron la decisión de no permitir a FK como discriminador.
Mi solución hasta ahora es agregar otro campo a la base de datos para usar como un discriminador que luego se establece en el mismo valor que ListId usando un disparador de inserción (para manejar inserciones que no son EF) y luego agregar la propiedad de navegación ListId a la entidad ListItem .
¿Alguien tiene alguna sugerencia / alternativa?
Asumiendo el código primero EF 4, existe una forma algo (muy?) Artificial, que requiere el uso de vistas para crear una columna discriminadora virtual que sea igual a ListId.
Debe ser capaz de agregar una vista a la base de datos y "en lugar de" activadores a esa vista para simular una nueva columna. En todo esto, debe abordar algunos problemas que EF tiene con este enfoque.
CREATE view [dbo].[vListItem] as
select Id, ListId,[Text]
,cast(ListId as varchar(10)) as Discriminator
from xListItem;
GO
Note: need the cast to varchar <= int causes errors on EF (MaxLength facet)
Un ejemplo de disparador para insertar sería:
CREATE TRIGGER trg_vListItem_Insert
ON [vListItem]
INSTEAD OF INSERT
AS
Begin
-- set nocount on
Insert into xListItem (ListId,[Text])
Select i.ListId, i.[Text]
from Inserted i
-- Need this so Ef knows a row has been inserted
select Id from xListItem where @@ROWCOUNT > 0 and Id = scope_identity()
End
Note: Delete and Update would be similar.
Entonces podrás tener en tu modelo:
public class vListItem
{
[Key]
public int Id { get; set; }
public string Text { get; set; }
[ForeignKey("ListId")]
public xList aList { get; set; }
public int ListId { get; set; }
}
public class vPerson : vListItem
{
}
public class vThing : vListItem
{
}
En tu DbContext puedes tener:
public DbSet<vListItem> vitems { get; set; }
public DbSet<vPerson> vpersons { get; set; }
public DbSet<vThing> vthings { get; set; }
Y, OnModelCreating define:
modelBuilder.Entity<vListItem>()
.Map<vPerson>(m => m.Requires("Discriminator").HasValue("1"))
.Map<vThing>(m => m.Requires("Discriminator").HasValue("2"))
;
Eso es. Algunas pruebas:
[TestMethod]
public void TestMethodPersonInsert()
{
Entities db = new Entities();
xList xl = db.lists.SingleOrDefault(x => x.Id == 1);
vPerson vp = new vPerson();
vp.Text = "p4";
vp.aList = xl;
db.vpersons.Add(vp);
db.SaveChanges();
}
[TestMethod]
public void TestMethodThingInsert()
{
Entities db = new Entities();
xList xl = db.lists.SingleOrDefault(x => x.Id == 2);
vThing vt = new vThing();
vt.Text = "t0";
vt.aList = xl;
db.vthings.Add(vt);
db.SaveChanges();
}
Más fácilmente, la solución radica en la introducción de una enumeración que se utiliza como discriminador para representar el tipo. La publicación que mencionó describe claramente que no se puede usar un FK, por lo que debe refactorizar eso a algo que se pueda aplicar. Requiere algo de mantenimiento, pero como también debe conocer los tipos que se derivan de su lista de temas, no veo que esto sea un problema importante. Otra opción que tendrías sería alejarte de TPH y usar TablePerType. De esa manera, en realidad no tiene que introducir una enumeración, pero incurrirá en una tabla para cada elemento de lista derivado.
Yo usaría múltiples mapeos. Pero, el núcleo del problema es no tener este tipo de relación en absoluto y revisar su diseño.