Introducción
Cuando se definen entidades estas pueden contener atributos que no siempre se quiere recuperar, ya que de hacerlo podría afectar la performance. Un ejemplo se observa en aquellos atributos donde se persisten datos de un volumen importante, como ser campos del tipo image, varbinary o ntext, los cuales por lo general no son necesarios recuperar cuando se carga un listado de la entidad en un grid, pero si son necesarios cuando se edita una entidad individual.
Por supuesto no solo esta limitado a este uso, se podría dividir simplemente porque un conjunto de propiedades representan la información clave de la entidad la cual es frecuentemente utilizada, separándola de aquellos atributos donde, salvo en algún uso puntual, no siempre se requieren.
Esta división permite mapear una tabla simple en múltiples entidades.
Complex Type Vs Table Splitting
Seguramente se estén preguntando, porque simplemente no se usa un Tipo Complejo? definiendo en este las propiedades que se consideran especiales.
El tema pasa porque los Tipo Complejos no permiten la carga retardada (lazy load), mientras que la división de la tabla en varias entidades si lo permite.
Al recuperar una entidad los tipo complejo serán siempre devueltos, no hay control sobre esta entidad, mientras que una tabla dividida en entidades se le puede indicar cuando se necesita recuperar este bloque de datos.
Definición del modelo
El objetivo es permitir separar un conjunto de propiedades en una entidad distinta
a pesar de esta dividido en varias entidades todas son mapeadas a la misma tabla, por lo que resulta
public class Employee { public Employee() { this.Localization = new Localization(); } public int EmployeeID { get; set; } public string LastName { get; set; } public string FirstName { get; set; } public virtual Localization Localization { get; set; } public virtual EmployeeExtended EmployeeExt { get; set; } } public class EmployeeExtended { public int EmployeeID { get; set; } public byte[] Photo { get; set; } public string PhotoPath { get; set; } public string Notes { get; set; } }
La entidad principal define una propiedad que referencia a la entidad extendida, a la vez que ambas entidades definen la misma propiedad como key, ya que por medio de esta se relacionan.
Definición del Mapping
Al definir el mapping ambas entidades persisten en la misma tabla, por eso el uso del ToTable(). También hacen uso de la misma key.
El HasRequired() permite indicar cual será la propiedad que representaran los datos extendidos en la entidad principal.
public class EmployeeMap : EntityTypeConfiguration<Employee> { public EmployeeMap() { ToTable("Employees"); HasKey(x => x.EmployeeID); Property(x => x.LastName) .HasMaxLength(20) .IsRequired(); Property(x => x.FirstName) .HasMaxLength(10) .IsRequired(); HasRequired(x => x.EmployeeExt) .WithRequiredPrincipal(); } } public class EmployeeExtendedMap : EntityTypeConfiguration<EmployeeExtended> { public EmployeeExtendedMap() { ToTable("Employees"); HasKey(x => x.EmployeeID); Property(x => x.Notes) .HasColumnType("ntext") .IsOptional(); Property(x => x.Photo) .HasColumnType("image") .IsOptional(); Property(x => x.PhotoPath) .HasColumnType("nvarchar") .HasMaxLength(255) .IsOptional(); } }
Definición del Repositorio
Hay cierta funcionalidad que requiere un tratamiento especial para estos casos es que se crea funcionalidad en el repositorio de le entidad empleado.
Se define esta en la interfaz, para luego implementarla en la clase concreta.
public interface IEmployeeRepository : IRepository<Employee> { EmployeeExtended GetExtendedById(int id); void DeleteIncludeExtended(Employee entity); }
public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository { /// <summary> /// Recupera solo la entidad Extendida /// </summary> /// <param name="id"></param> /// <returns></returns> public EmployeeExtended GetExtendedById(int id) { using (NorthWindContext context = new NorthWindContext()) { return context.EmployeeExtendeds.FirstOrDefault(x => x.EmployeeID == id); } } /// <summary> /// Elimina la entidad incluyendo la informacion extendida /// en caso de tenerla /// </summary> /// <param name="entity"></param> public void DeleteIncludeExtended(Employee entity) { using (NorthWindContext context = new NorthWindContext()) { if (entity.EmployeeExt == null) entity.EmployeeExt = new EmployeeExtended() { EmployeeID = entity.EmployeeID }; context.Employees.Attach(entity); context.Employees.Remove(entity); context.SaveChanges(); } } }
Estos método serán utilizados en los Test, donde se probara su funcionalidad
Configuración del proyecto de Test
Para probar la funcionalidad se requiere que un archivo que representa la foto que será asignada a la propiedad de la entidad. Este archivo forma parte del proyecto de Test, pero debe ser copiado a la carpeta de output.
Sera necesario definir un archivo de TestSetting el cual no ayudara a configurar el entorno de prueba, para ello agregamos este archivo usando la opción
En este configuramos las opciones de deploy para que copie el archivo de la imagen a la carpeta que defina el test cuando ejecute
Asignamos cual será el archivo usado en la ejecución de los test
Test – Eliminar empleado con y sin información extendida
Si eliminamos una entidad haciendo uso del método Delete() del repositorio base obtendremos un error
Se produce porque se requiere no solo marcar para eliminar la entidad principal sino también la extendida, por esta razón en el repositorio de la entidad empleado se creo un método especifico para este caso.
El método DeleteIncludeExtended() elimina la entidad y también define la entidad extendida a pesar que no se envíe como dato en la entidad que se asigna como parámetro.
[TestMethod] public void Delete_Employee() { EmployeeRepository repoEmployee = new EmployeeRepository(); // //se crea un empleado con info extendida // CreateEmployeeWithPhoto(); //elimina el empleado y su informacion extendida repoEmployee.DeleteIncludeExtended(new Employee() { EmployeeID = employeeNew.EmployeeID } ); //se recupera el empleado que fue eliminado en el paso anterior Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID); Assert.IsNull(employeeSel); // //se crean el empleados sin informacion extendida // Employee employee = new Employee() { FirstName = "name 1", LastName = "lastname 1", EmployeeExt = new EmployeeExtended() }; repoEmployee.Create(employee); repoEmployee.DeleteIncludeExtended(employee); //se recupera el empleado que fue eliminado en el paso anterior employeeSel = repoEmployee.Single(x => x.EmployeeID == employee.EmployeeID); Assert.IsNull(employeeSel); }
El test tiene dos partes, la primera crea un empleado con funcionalidad extendida y lo elimina usando el método que hemos creado en el repositorio para este propósito, la segunda parte crea una entidad sin información extendida y también elimina esta entidad, demostrando que sin importar la información con la cual se cree la entidad puede ser eliminada.
Como se vera para ambos casos es necesario definir el id de la entidad extendida para que sea marcada tanto la entidad principal como la extendida para ser eliminadas.
Test – Obtener todos los empleados
Se crean dos empleado, uno con información extendida y el otro sin ella.
En el test recupera la lista de empleados, pudiéndose analizarse como la información extendida no se obtiene, mientas que la propiedad que representa el complex type si es devuelta al recuperar la entidad.
Este caso es el típico que uno usaría para cargar un listado o grid con la información base de la entidad.
[TestMethod] public void GetAll_Employee() { string foto = Path.Combine(this.TestContext.DeploymentDirectory, "foto.jpg"); EmployeeRepository repoEmployee = new EmployeeRepository(); //se eliminan las entidades que pudieran quedar de la ejecucion de test anteriores repoEmployee.GetAll().ForEach(x => repoEmployee.DeleteIncludeExtended(x)); //se crean dos empleados CreateEmployeeWithPhoto(); Employee employee2 = new Employee() { FirstName = "name 2", LastName = "lastname 2", EmployeeExt = new EmployeeExtended() }; repoEmployee.Create(employee2); List<Employee> list = repoEmployee.GetAll(); Assert.AreEqual(list.Count, 2); Assert.IsNull(list[0].EmployeeExt); Assert.IsNotNull(list[0].Localization); Assert.IsTrue(list[0].Localization.HasValue); Assert.IsNull(list[1].EmployeeExt); Assert.IsNotNull(list[1].Localization); }
Test – Obtener entidad SIN información extendida
Aunque se haya creado un empleado con información adicional al recuperar la entidad esta no forma parte de los datos.
[TestMethod] public void GetById_Employee() { //se crea el empleado con informacion extendida CreateEmployeeWithPhoto(); //Se recupera la entidad Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID); Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID); Assert.IsTrue(employeeSel.Localization.HasValue); Assert.AreEqual(employeeSel.Localization.Address, "Address 1"); Assert.AreEqual(employeeSel.Localization.City, "City 1"); Assert.AreEqual(employeeSel.Localization.Country, "Country 1"); Assert.IsNull(employeeSel.EmployeeExt); }
La consulta recupera el empleado y deja evidencia de la inclusión de los campos que define el complex type pero no se incluyen los de la entidad extendida.
Test – Obtener entidad CON información extendida
En este caso el test define un “include” de la propiedad extendida, permitiendo recuperar la foto asignada al empleado
[TestMethod] public void GetById_EmployeeExt() { //se crea el empleado con informacion extendida CreateEmployeeWithPhoto(); //se recupera la entidad y sus propiedad extendida Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID, new List<Expression<Func<Employee, object>>>() { x=>x.EmployeeExt }); Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID); Assert.IsTrue(employeeSel.Localization.HasValue); Assert.AreEqual(employeeSel.Localization.Address, "Address 1"); Assert.AreEqual(employeeSel.Localization.City, "City 1"); Assert.AreEqual(employeeSel.Localization.Country, "Country 1"); Assert.IsNotNull(employeeSel.EmployeeExt); Assert.IsNotNull(employeeSel.EmployeeExt.Photo); Assert.AreEqual(employeeSel.EmployeeExt.PhotoPath, employeeNew.EmployeeExt.PhotoPath); }
la consulta resultante es bastante mas compleja ya que debe unir los campos de la entidad extendida
Lo importante a destacar es que uno controla cuando es necesario recuperar los datos extendido para así mejorar la performance de la aplicación.
Test – Recuperar SOLO la información extendida
Si ya se dispone de la entidad con los datos básicos y se requiere solo los datos extendidos se podría recuperar únicamente estos.
[TestMethod] public void GetById_Employee_RecoverOnlyExtended() { CreateEmployeeWithPhoto(); EmployeeExtended employeeSel = repoEmployee.GetExtendedById(employeeNew.EmployeeID); Assert.IsNotNull(employeeSel); Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID); Assert.IsNotNull(employeeSel.Photo); Assert.AreEqual(employeeSel.PhotoPath, employeeNew.EmployeeExt.PhotoPath); }
El método GetExtendedById() hace uso de la propiedad del contexto que accede a esta entidad extendida de forma directa.
Es la misma consulta que recupera al empleado pero solo define los campos de la entidad extendida.
Documentación de referencia
Associations in EF Code First: Part 4 – Table Splitting
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#]
|
Hola Leandro, son muy interesantes tus articulos sobre CF. Me intriga bastante el tema, estoy investigando y haciendo mis primeras pruebas, y estuve viendo que tambien se puede utilizar con BD existentes:
ResponderEliminarhttp://weblogs.asp.net/scottgu/archive/2010/08/03/using-ef-code-first-with-an-existing-database.aspx
http://weblogs.asp.net/jgalloway/archive/2011/02/24/generating-ef-code-first-model-classes-from-an-existing-database.aspx
Me gustaria saber que opinas al respecto, estoy muy acostumbrado a trabajar primero en la BD, y luego codificar la app; mi duda es sobre todo respecto a como quedaria el mapeo si parto de una BD existente y con los tipos de herencia que se generaria a partir de la estructura de las tablas de la BD. Como me recomendarias encarar este tipo de proyecto? saludos!!
hola matias
ResponderEliminarpuedes mapear perfectamente una db existente con clases de EF
quizas habria que evaluar que nivel de herencia se podria aplicar, si es que se requiere, sino de ultima podrias usar la entidad simple
la idea es no forzar un diseño que termine complicandolo todo, algunas entidades seguramente seran mas simple de llevar a un modelo de objetos, otras quizas no, y deben usarse EF simplemente para facilitar el acceso a datos y nada mas
Code First to an Existing Database
EntityFramework Reverse POCO Generator
la ides es pensar la persistencia desde EF y que esta genere la db, pero sino se puede aplciar esta tecnica se trata de aproxima sin forzar
saludos
Hola leandro, mira estoy realizando un proyecto tipo academico en MVC4 en visual studio 2012, y no me deja conectar la base de datos del membership "defaultconConnection" por medio del asp.net , te agradeceria si me respondes
ResponderEliminarGracias
hola Julian
ResponderEliminarpero el error de conexion cual seria ?
validaste que el connection string este bien definido, o si desde donde te ejecutas el sitio se puede alcanzar la db a la cual te quiere conectar
saludos
hola,
ResponderEliminarmira, ejecuto el proyecto sin modificarle nada y no deja ingresar al membership para administrar los roles,( en mvc 4 )
observación : se puede ingresar a la base de datos defaultconnection por el explorador de servidores y la aplicación me deja listar los usuarios registrados en el membership, pero al momento de añadir el rol dice q presenta problemas de conexión con sql (error 26 )
saludos
hola Julian
ResponderEliminarpero la base de membership la tienes como un archivo .mdf en el App_Data o esta dentro del servicio de sql server
desde donde es que agregas el rol? desde la aplicacion WAT
saludos
hola, la BD del membership esta configurada como .mdf
ResponderEliminary se guarda en app_data
y ando agregando los roles desde el explorador de servidores
hola Julian
ResponderEliminarpero puedes acceder a la pagina de WAT para crear usuario y roles desde esta ?
porque a la db que esta en App_Data no debesrias crearle registros directamente, sino por medio de la pagina que proporciona WAT
saludos
Hola, no se puede acceder al WAT para ingresar nuevos roles
ResponderEliminarhola Julian
ResponderEliminarentonces crea tus propias paginas haciendo uso de las clases de asp.net Memebership para crear los registros
Membership Class
veras en los metodo de la clase membership que puedes crear,eliminar, etc usuario de esas tablas
Roles Class
tambien esta la clase para los roles, con estas podrias crear tus propias paginas de administracion de seguridad,sin hacer uso de WAT
ojo si usas mvc 4 quizas este configurando
SimpleMembershipProvider Class
esa es la que reemplaza a la de Membership
saludos
Hola Leandro.
ResponderEliminarVeo interesante el sistema de Table Splitting y dividir una tabla en 2 entidades, en una podríamos poner el Id y la Descripción y en otra entidad poner el resto de propiedades. Esto viene bien a la hora de cargar un combobox, que lo cargaríamos con la entidad base.
En los ejemplos veo cómo cargar la información extendida en una entidad base, pero mi pregunta es cómo podría hacerse para cargar todas las entidades base con su información extendida para pasarlo todo a un datagrid y poder ver toda la información.
Gracias y felicitaciones por el blog
hola pepito
ResponderEliminarno se si entendi del todo el planteo, pero podrias ver de usar el Include() en la query para recuperar la info extendida
Loading Related Entities
saludos
Mi pregunta era la siguiente, supongamos que tengo una entidad de tipo Ciudad dividida en 2 entidades:
ResponderEliminarpublic class Ciudad {
public int Id { get; set; }
public string Nombre { get; set; }
public virtual CiudadExt CiudadExt { get; set; }
}
public class CiudadExt {
public int Id { get; set; }
public int PaisId { get; set; }
public int IdiomaId { get; set; }
public int IntervaloId { get; set; }
public bool Capital { get; set; }
}
La idea que tengo en mente es usar la clase Ciudad para cargar comboboxes, pero a la hora de gestionar las altas, modificaciones y bajas de las ciudades tener toda la información completa en un grid.
He consultado la información que me aconsejaste y ¡¡¡ bingo !!!, ahí está la solución ¡¡¡ el include !!!
Muchas gracias por la ayuda.
Una cosa más, hace unos años seguí tu ejemplo de aplicación N-Tier, ¿no tendrías un ejemplo de aplicación N-Tier con Entity Framework y WPF MVVM?
Me gustaría saber cómo quedaría el "esqueleto" de la aplicación, me refiero a cuantos proyectos habría y qué iría en cada uno de ellos, y también cómo se implementaría la navegación de Pages o UserControls usando MVVM. Se que hay frameworks como Caliburn Micro, pero me gusta aprender y saber lo que estoy haciendo.
Si no vas a publicar ningún artículo sobre MVVM, si no es mucha molestia, podrías enviarme un ejemplo de aplicación con esas tecnologías.
Muchas gracias y disculpa las molestias.
Saludos.
hola Alex
ResponderEliminarhace tiempo tengo pensado publicar temas de mvvm pero no me hago el tiempo para poder armar algo que publicar
los ejemplos que tengo son de la consultora donde trabajo no podrias publicarlos o compartirlo ya que tendria problemas con los clientes
saludos
Hola Leandro, estoy migrando un sistema en vb 2003 - al vb 2010 y tengo un problema a la hora de levantar el sistema lo cual me sale este mensaje: "se ha producido un error al crear un controlador de seccion de configuracion para uipconfiguration: el comodin '##any' permite el elemento 'navigationGraph' y hace que el modelo de contenido sea ambiguo. un modelo de contenido debe estar constituido de forma que durante la validacion de una secuencia de elementos de informacion de elemento, la particula contenida directa, indirecta o implicitamente en el mismo y con la que se debe intentar validar cada elemento de secuencia se pueda determinar de forma exclusiva sin examinar el contenido o los atributos de dicho elemento, y sin ninguna informacion acerca de los elementos del resto de la secuencia"
ResponderEliminarsi serias amable de poder ayudarme. gracias
hola Jesus
ResponderEliminarentiendo por el error que este se genera por culpa del archivo de configuracion del proyecto
creas de casualidad alguna seccion custom para leer la configuracion
usas algun componente de terceros referenciado en proyecto?
saludos
el sistema me lo encontre con un dll
ResponderEliminarllamado: "Microsoft.ApplicationBlocks.UIProcess" ese es el que hace el llamado a todos los menús. Necesito de tu ayuda leandro ya que soy practicante en una empresa. gracias
ya que ha la hora entrar al login me levanta perfectamente, y a la hora de ingresar al menu principal que lo hace con esta linea de codigo: "UIPManager.StartTask("Logon")", ya que esa linea viene hacer un metodo del dll que te lo mencione en el anterior comentario.
ResponderEliminarhola leandro, muchisimas gracias por tu blog esta muy bueno y tambien los comentarios que realizas en el foro de microsoft, tengo una duda con vba, estoy trabajando en excel, necesito crear un arreglo de textbox, e investigado mucho y no e encontrado nada de utilidad, espero me puedas ayudar saludos y gracias
ResponderEliminarhola azael
ResponderEliminarno intentaste grabar una macro y despues ver el codigo que genera? eso a veces ayuda para ver que codigo vba define el excel y despues tomar la tecnica y replicarla
saludos
Este comentario ha sido eliminado por el autor.
ResponderEliminarHola leandro. En primer lugar disculpa si estoy usando mal tu blog y mi pregunta no procede aqui. En cualquier caso esta seria:
ResponderEliminarEn una arquitectura distribuida n-tiers hecha en .net, seria posible un esquema como el siguiente:
1º En el ordenador cliente un proyecto vb.net con "winforms"
2º En un servidor en Valencia la capa de nogocio.
3º En un servidor en Madrid la capa modelo.
Mi pregunta, además sería, como se comunica un winform con la capa de negocio? Podria ser con algun servicio de WCF.
Gracias anticipadas.
Un saludo
Saludos Leandro, aunque mi consulta no tiene referencia con este tema, resulta que necesito cargar una imagen que se encuentra guardada en un archivo .JPG. en un webform.
ResponderEliminarMi código es el siguiente:
Dim Raster As Bitmap
Raster = New Bitmap(c:\origen\foto.jpg)
El problema es que el archivo "foto.jpg" tiene un tamaño de 262 Mb y muestra el error "Desbordamiento".
Cuando lo hago con imágenes mas pequeñas lo hace bien.
Sabes cómo puedo hacer para que el programa maneje mayor cantidad de memoria para cargar imágenes grandes ??
Gracias.
Buenos dias Leandro Tuttini mucho gusto, no se si podrias ayudarme con la creación de una impresora virtual utilizando vb 2008 o vb 2012
ResponderEliminarLeandro,
ResponderEliminarIntentó con Code First que la plataforma me cree una tabla para la siguiente clase (simplificada)
Class Forma
public int FormaId ...
public string Nombre ...
public Rectangle Area ...
Me crea la tabla pero sin el campo Area. ¿Hay alguna manera de mapear los tipos Struc como es el caso de Rectangle?
Saludos y felicitaciones por el Blog.
hola
EliminarLa verdad es que no he usado struct para mapear campos en uan tabla, ademas este puede contener varios valores, como deberia representarlo en la tabla? lo veo dificil
Si necesitas quita el struct o intenta mapear este a una tabla o sea que cree la tabla de nombre "Rectangle" y genere una asociacion entre la clase que estas creando y la de Rectangle
La tabla Rectangle contendra los campos que el struct defina. Igual lo veo dificil porque se supone que al struct o class debas definirle una propiedad que actue como key
En resumen no uses ese struct, podrias crear una propio como ser
public class Rectangle{
public int RectangleId {get;set;}
public int X {get;set;}
public int Y {get;set;}
}
la idea es que se genere una relacion entre las tablas
saludos
Leandro gracias por tu respuesta. En realidad la idea no es crear una tabla para rectángulos, puesto que la tabla es Formas (la cuál abrevié) y Rectangle es un campo (tal vez complejo) de dicha tabla. Imagino que voy a tener que separar en cuatro campos RectX, RectY, RectW y RectH. Detrás de esto lo que hay es la información de un diseño de emisión de informes liviano que antes guardaba en XML. Abrazo!
Eliminarhola
EliminarEl tema es que no existe de campos complejo en las base de datos, si el tipo de dato tiene varias propiedades entonces seria otra tabla que debes relacionar.
Exacto vas a tener que llevar los 4 campos a una tabla y mapear a una entidad. Sino podrias hacer que la propiedad xml y guardarla como string, o quizas como json si es que usas una de las ultimas versiones de sql server
saludos