miércoles, 28 de agosto de 2013

[Entity Framework][Code First] Herencia - Tabla por tipo - Table per Type (TPT)

 

Introducción


Analizaremos un modelo de herencia el cual permite definir una tabla para cada tipo concreto. En este caso la relación entre las tablas define el tipo de instancia de la entidad.

El modelo utilizado en este artículo es idéntico al anterior:

[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)

 

Definición del modelo


Modelaremos la entidad empleado y sus derivados que representan al personal externo y el contratado por la compañía.

Partiremos de un modelo de clases como el siguiente:

image

y obtendremos un modelo de tablas

image

en donde cada la clase base estará en una tabla mientras que los derivados, con sus atributos particulares, en otras distintas.

En este caso la relación entre las tablas define el tipo, a diferencia del modelo TPH (Tabla por jerarquía) en donde un campo era quien lo indicaba.

 

Definición del Mapping


Se va a necesitar definir en que tabla se persiste cada tipo especifico, es por ello que se requiere una clase de mapping para cada derivado que herede de la base.

 

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
        this.Configuration.LazyLoadingEnabled = false;
        this.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new EmployeeMap());

        modelBuilder.Configurations.Add(new EmployeeInternalMap());
        modelBuilder.Configurations.Add(new EmployeeExternalMap());

        base.OnModelCreating(modelBuilder);
    }

}

public class EmployeeMap : EntityTypeConfiguration<Employee>
{
    public EmployeeMap()
    {
        HasKey(x => x.EmployeeID);
        Property(x => x.LastName).HasMaxLength(20).IsRequired();
        Property(x => x.FirstName).HasMaxLength(10).IsRequired();

        Property(x => x.Address).HasMaxLength(60);
        Property(x => x.City).HasMaxLength(15);
        Property(x => x.Region).HasMaxLength(15);
        Property(x => x.PostalCode).HasMaxLength(10);
        Property(x => x.Country).HasMaxLength(15);

    }
}

public class EmployeeInternalMap : EntityTypeConfiguration<EmployeeInternal>
{
    public EmployeeInternalMap()
    {
        ToTable("EmployeeInternal");

      
    }
}

public class EmployeeExternalMap : EntityTypeConfiguration<EmployeeExternal>
{
    public EmployeeExternalMap()
    {
        ToTable("EmployeeExternal");

        Property(x => x.ConsultantName).IsRequired()
                                       .HasColumnType("varchar")
                                       .HasMaxLength(100);
    }
}

El principal detalle esta en la definición de los nombre de la tablas

image

 

Definición Repository


Este no sufrió cambios con respecto al articulo anterior donde tratamos TPH.

Nota: se agrego un método adicional a la interfaz del repositorio el cual implementa una query linq que utiliza el “is”, pero no varia la forma de definir esta funcionalidad con respecto al articulo anterior. Se analizara este punto más adelante cuando tratemos el Test que utiliza esta implementacion.

 

Test – Inicializar datos


La inicialización de datos es idéntica al artículo anterior donde explique TPH.

Pero el resultado de los “insert” que define EF son bastante diferentes, ya que por cada entidad creada requiere realiza dos operaciones, uno en la tabla base y otro en cada especialización, esto se aprecia claramente en el trace tomado desde el sql profiler.

image

 

Test – Obtener todos los empleados


Recuperar todos los empleados requiere implementar la unión de dos tablas, la de empleados internos y externos,  al ejecutar el test se puede observar claramente si analizamos el select definido por EF

image

La unión de las dos tablas que representan las entidades hijas se unen mediante un INNER JOIN con la tabla padre de empleados

Aquí nuevamente aparecen los “0X0X” y “0X1X” los cuales son usados por EF para determinar que tipo de instancia debe crear

 

Test – Recuperar todos los empleados de tipo interno, usando un repositorio especifico


Utilizamos el repositorio definido para un tipo concreto, en este caso se recuperan los empleados internos a la empresa

SNAGHTML31ecfb93

Esta acción unirá mediante un INNER JOIN la tabla que representa estos empleados con la tabla base, del cruce se obtendrán solo los registros de un único tipo.

 

image

 

Test – Recuperar todos los empleados del tipo interno, usando funcionalidad del repositorio genérico


Utilizar el método OfType<> definido en un método en al repositorio general

SNAGHTML31f473ce

dará el mismo resultado que utilizar el repositorio especifico del test anterior

image

 

Test – Obtener todos los empleados Externos


Se obtienes todos los empleados de contratación externa.

SNAGHTML32077f62

El join se realiza utilizando la tabla que representa los empleados externos a la compañía.

image

 

Test – Obtener todo los empleados externos, usando el “is”


Aplicaremos una variación en la técnica utilizada al definir el tipo que queremos filtrar, para ello nos ayudaremos con el “is” en el where de la query linq.

SNAGHTML321b1bf0

como veremos EF detecta el “is” como la acción de recuperar un tipo especifico, por eso el sql que define nuevamente hace uso del JOIN entre las tablas

image

 

Documentación de referencia


Inheritance with EF Code First: Part 2 – Table per Type (TPT)

 

Código


Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF

[C#]
 

29 comentarios:

  1. Hola muy buenas :

    Tengo una pequeña duda sobre una base de datos que estoy realizando.
    Tengo varias tablas una de ellas se llama Modelo_zapatos, en esta tabla a través de un formulario damos de alta todos los modelos de los zapatos, el trabajo que lleva y el precio del trabajo.
    A la hora de facturar los trabajos tengo que seleccionar,Notas, el modelo del zapato y quiero que solo me aparezca el trabajo que lleva solo ese modelo y cuando yo seleccione el trabajo en la casilla precio aparezca automaticamente el precio del trabajo que se tiene que multiplicar por el total de pares para que me aparezca el total de la nota.
    Me puede echar una mano ?
    Un saludo gracis

    ResponderEliminar
  2. hola

    respondi en la pregunta del foro

    http://social.msdn.microsoft.com/Forums/es-ES/b69cc83f-da99-4314-aa44-4f832e2dc88f/ayuda-sobre-access

    saludos

    ResponderEliminar
  3. Hola Leandro, una vez más tengo una consulta, esta no tiene nada que ver con la entrada, pero no encontré alguna relacionada con la consulta que tengo. Quisiera saber si existe alguna herramienta o alguna forma de hacer una barra buscadora dentro de un proyecto web desarrollado con C# en Visual Studio 2010 o 2012, el problema es que necesito que esta búsqueda no solo se realice sobre la pagina web, también tengo archivos guardados en una base de datos sql server en un campo de tipo varbinary, ¿se puede buscar dentro de estos archivos coincidencias en cuanto a palabras? Tengo entendido que el buscador que proporciona SharePoint realiza este tipo de busquedas

    ResponderEliminar
  4. hola david

    hasta donde se control para implementar una busqueda no existe, podrias simplemente definirlo con un textbox y un boton

    se que existen servicios que proporciona el iis para implementar busquedas

    Search Engine Optimization Toolkit

    para buscar sobre la db deberias ver el servicio de

    Full-Text Search (SQL Server)

    saludos

    ResponderEliminar
  5. Hola leandro, muchas gracias por las sugerencias, las probare y te comento como me va, adicionalmente quisiera preguntarte si conoces una forma de guardar en una base de datos el contenido de documentos como word o pdf, pero solo el contenido eliminando el formato, algo parecido a un parser, pero implementarlo desde C# para poder tener una tabla en sql con el contenido de los archivos.
    Saludos

    ResponderEliminar
  6. hola david

    imagino apuntas a persistir los documentos usando EF

    si es asi podrias definir un campo del tipo bute[] y este mapear contra el campo de la tabla, de esta forma si usas el

    File.ReadAllByte()

    podrias asignar el contenido del archivo en el campos

    saludos

    ResponderEliminar
  7. Hola leandro, no estoy utilizando EF, estoy desarrollando una aplicación web con visual studio 2010 y C#, utilizo una base de datos sql server 2008 en donde guardo archivos que los usuarios suben a la aplicación, estos archivos los guardo en la BD en un campo de tipo varbinary(max), pero ahora existe la necesidad de generar un buscador para encontrar archivos que contengan cierta información y esta búsqueda tengo que realizarla en el contenido de los archivos ya que no basta con buscar en nombres del archivo o propiedades, pero no se de que forma se pueda implementar este buscador

    ResponderEliminar
  8. hola david

    si solo guardas el binario del archivo esta claro que no se podra aplicar busquedas

    quizas debas analizar el uso de

    Full-Text Search (SQL Server)

    saludos

    ResponderEliminar
  9. Hola leandro muchas gracias por la ayuda, full text search funciona perfecto

    ResponderEliminar
  10. hola leandro! tengo una duda, en este tipo de herencia, si deseo hacer referencia a un empleado, por ejemplo en una liquidacion, la misma va dirigida a la clase padre o a uno de sus hijos (heredadas)? tambien supongo que un empleado deberia ser de un tipo u otro, no ambos...esto lo manejas con un atributo discriminador, o un disparador? saludos!!

    ResponderEliminar
  11. hola matias

    la referencia que realices va a depender del negocio, si este indica que esa liquidacion se realiza a un determinado tipo de empleado vincularias contra la clase hija
    si la operacion permite que sea una entidad empleado sin discriminar que tipo lo harias contra la entidad padre

    para que sea de un tipo u otro sin que se puedan crear instancias de la entidad padre lo defines mediante la declaracion como "abstract" de la entidad padre, en el ejemplo justamente lo realizo de esta forma

    saludos

    ResponderEliminar
  12. Hola de nuevo, es clarp lo que decis, pero mi pregunta iba referida a como evitar que se carguen las dos hijas, en los casos que el negocio no lo permita, como este ejemplo, donde una persona no podria ser los dos tipos de empleado al mismo tiempo.
    Me gustaria saber sobre todo a nivel tabla como lo haria, por eso preguntaba si se ponía algun atributo discriminador, saludos!!

    ResponderEliminar
  13. hola matias

    si entendi bien, lo que planteas no se puede dar nunca, porque solo puedes crear un unico tipo de instancia

    si defines:

    EmployeeInternal emp1 = new EmployeeInternal();

    solo sera un emplado de contratacion interna, no puede ser tambien un empleado externo porque lo define la instancia, la simple declaracion ya lo esta limitando

    saludos

    ResponderEliminar
  14. Hola Leandro me puedes hacer el favor y me ayudas.es que tengo 2 tablas y necesito restar de una de ellas x cantidad o sea de la tabla producto.existencias restarle a la tabla venta.cantidad estoy trabajando en Access y vb.net y están en 3 formularios diferentes.muchas gracias

    ResponderEliminar
  15. hola Aldo

    pero lo estas implementando con entity framework ? o usas ado.net simple

    si solo es una query del UPDATE sabes como ejecutarla, digo utilizas los objeto OleDbConnection, OleDbCommand, para poder ejecutar la query de update

    ADO.NET – Parte 4 – Actualización Información Ms Access

    saludos

    ResponderEliminar
  16. Leandro te queria preguntar si tienes algun ejemplo con Silverlight 4 en asp

    ResponderEliminar
  17. hola Noe

    me temo que no tengo, la verdad no he trabajado con silverlight

    si conozco algo del tema porque trabaje con xaml en wpf, pero fue todo desktop

    saludos

    ResponderEliminar
  18. ok de todos modos gracias por tu ayuda y responder nuestras dudas eres un excelente programador

    ResponderEliminar
  19. Buenas tarde Leandro!!

    Oye estoy desarrollando en MVC3 con C#, javascript y razor.

    Pero intento hacer algo que no me funciona. Ojala puedas apoyarme..

    Tengo una función de agregar lineas. Para esto agrego código html con razor y javascript ya que es algo dinamico.

    El conflicto que tengo es agregarle una variable javascript a la lista,
    trato de concatenarla pero no se como concatenar en una linea razor con javascript.


    '@Html.TextBoxFor(model => model.RMRP.lstForm[a].TAG, new { id = "TAG" , maxlength = "7" })'

    Ojala puedas ayudarme...

    ResponderEliminar
  20. hola mike`s

    pero porque no lo concatenas en el model, o quizas en el action

    o sea podrias hacer

    public class ModelXX{

    public string TagText{
    get{
    return string.Join(",", this.lstForm);
    }
    }

    }

    entonces usarias

    @Html.TextBoxFor(model => model.RMRP.TagText, new { id = "TAG" , maxlength = "7" })'

    imagine que lstForm es un array de estring no? sino se puede adaptar pero creo que la idea se entiende

    saludos

    ResponderEliminar
  21. Hola Leandro. Tengo una duda. He leido tu post y me fue de mucha utilidad pero me he topado con un problema.

    Cree una entidad base que se llama "Persona" de esta le heredan "EmpleadoInterno" y "EmpleadoExterno" muy parecido a tu codigo.

    Cuando agrego Empleado Internos o Externos no tengo problema. Pero cuando agrego una persona que despues quiero convertir a Empleado Interno o Externo me da una error de llave primaria.

    Me podrias explicar como corregir este error

    ResponderEliminar
  22. hola William

    pero si crear una entidad como Persona esta claro que no podras pasarla a empleado ya que la herencia no lo permite

    podrias asignar instancia de empleado interno o externo a Persona pero no al contrario, es mas no tendria mucho sentido porque habria propiedades que no se pueden definir cuando vas a una instancia de entidad mas compleja

    es mas me entra la duda como lograste hacer que la aplicaicon compile, porque yo hay esto

    public class Persona
    {
    }

    public class Empleado : Persona
    {
    }

    y luego defino

    Persona p1 = new Persona();
    Empleado e1 = p1;

    y no me compila, justamente porque no se puede realizar esa asignacion, ahora si uso

    Empleado e1 = new Empleado();
    Persona p1 = e1;

    sa asignacion si me deja
    saludos

    ResponderEliminar
  23. Claro que entiendo lo que comentas. Por lo que entiendo en este modelo si creas una persona como entidad base nunca va a poder ser empleado interno o externo. Si quisiera un empleado interno o externo tendría que crear otra entidad que tenga la misma información que la entidad base mas los campos que la diferencian de la entidad base. Y estriamos hablando de otra persona.

    En la vida real tu puedes registrar todas las personas que quieras. Y estas personas después pueden pasar a formar parte de una empresa como empleados internos o externos. Muchas veces los empleados externos se convierten a empleados internos. Pero en todos los casos siempre es la misma persona.

    Eso es lo que estoy tratando de implementar en el modelo. Que una persona registrada en la entidad persona sea la base para crear un empleado interno o externo. Es decir el mismo Id

    Mi pregunta es si se soporta esto en el entity framework.

    Muchas gracias de antemano.

    ResponderEliminar
  24. hola William

    eso puedes hacerlo, para persistir un empleado base solo no definas como abstract la clase base, asi podras crear instancias

    con esto podrias crear entidades de la clase base y persistirlas

    ahora de ahi a que despues pase a ser interno o externo seguramente requiere de una transformacion, la verdad no he probado convertir de un tipo a otro

    saludos

    ResponderEliminar
  25. Hola Leandro.

    Como lo comentas resolví este problema haciendo una transformación. Agregue un método en la clase DbContext que reciba una entidad base como entrada e inserte con un stored procedure en la tabla de la entidad heredada y al final regresa la instancia de la entidad heredada.

    Este es el código.

    public EmpleadoInterno AddEmpleadoInterno(Persona Persona)
    {

    SqlConnection sqlConn = new SqlConnection(Empleados.Properties.Settings.Default.EmpleadosContext);
    SqlCommand cmd = new SqlCommand();

    cmd.CommandText = "spEmpleadoInterno_Add";
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add(new SqlParameter("@PersonaId",Persona.PersonaId));
    cmd.Connection = sqlConn;
    sqlConn.Open();
    cmd.ExecuteNonQuery();
    sqlConn.Close();

    using (var db = new EmpleadosContext())
    {
    return db.EmpleadosInternos.Single(x => x.PersonaId == Persona.PersonaId);
    }

    }

    Y así lo mando llamar.

    EmpleadoInterno new_ei = db.AddEmpleadoInterno(p, db);
    new_ei.Nomina = "1010";

    db.SaveChanges();

    Te parece bien la solución o que le mejorarías tu.

    Gracias por tu apoyo.

    ResponderEliminar
  26. hola William

    la verdad no veo correcta la solucion, no es una buena idea tener que recurrir a un procedure cuando deberias lograrlo usando solo entity framework

    si quieres convertir una entidad cambiandola, podrias eliminar la original y crear una nueva, quizas para este caso puntual el id no lo hagas identity sino que tu lo defines asi puedes reusarlo cuando creas la nueva entidad de persona a empleado interno

    ademas el procedure ni siquiera lo invocas por medio de EF, sino que usas ado.net, eso es aun peor

    saludos

    ResponderEliminar
  27. Pues si puedo hacer lo que comentas de eliminar la instancia base y crear una instancia heredada con el mismo Id. Lo único que me da temor es afectar relaciones de llaves foráneas que pueda tener en algunos otros puntos de la base de datos. Pero enteramente si funciona.

    Con respecto al stored procedure, lo hice de esa forma solo para ejemplificar que si funciona solo complementar la base de datos para instanciar la entidad heredada y también no me he adentrado en customizar el EF con stored procedures.

    Muchas gracias por tu dirección y pronta respuesta.

    ResponderEliminar
  28. hola William

    si es cierto las relaciones son un tema, por eso comente que quizas para este caso en concreto no usar identity para la key, asi podrias reutilizar para la nueva entidad el id que ya tenias

    Si bien es una nueva entidad cuando lo attaches al contexto de EF lo deberias marcar para actualizacion

    Es mas ahora que lo pienso, si armas una nueva entidad "empleado interno" y le completas las propiedades copiando los datos de la entidad persona, y ademas asignas el id que ya tenias, pero al attachar al contexto lo pones para modificar quizas funcione

    saludos

    ResponderEliminar
  29. Ok. Voy a intentar esta parte de agregar la entidad al dbcontext y marcarla como update no como new.

    Gracias

    ResponderEliminar