jueves, 4 de julio de 2013

[Entity Framework][Code First] Asociación uno a muchos (2/3)

 

Introducción


En esta segunda parte del artículo se ejecutaran varios Test que permitirán analizaran las consultas que Entity Framework generara contra la base de datos.

La primer parte del artículo define las entidades que aquí se validaran con los test.

[Entity Framework][Code First] Asociación uno a mucho (1/3)

 

Test – Crear Producto con categorías y proveedores existentes


Crearemos de forma individual la entidad de categorías y proveedores para luego asociarlas al producto.

[TestMethod]
public void GetSingle_WithCategoryAndSupplier_Product()
{
    ProductRepository repoProduct = new ProductRepository();
    CategoryRepository repoCategory = new CategoryRepository();
    SupplierRepository repoSupplier = new SupplierRepository();

    //se crea la categoria
    Category categoryNew = new Category()
    {
        CategoryName = "category1",
        Description = "desc category 1"
    };
    repoCategory.Create(categoryNew);

    //se crea un proveedor
    Supplier supplierNew = new Supplier()
    {
        CompanyName = "Company 1",
    };
    repoSupplier.Create(supplierNew);

    //se crea el producto relacionado con la categoria
    Product prod = new Product()
    {
        ProductName = "prod 1",
        UnitPrice = 10,
        Discontinued = false,
        CategoryID = categoryNew.CategoryID,
        SupplierID = supplierNew.SupplierID
    };
    repoProduct.Create(prod);

    //se recupea el producto con la categoria y proveedor asociado
    var productSelected = repoProduct.Single(x => x.ProductID == prod.ProductID,
                                            new List<Expression<Func<Product, object>>>() { x => x.Category, x=>x.Supplier });

    Assert.IsNotNull(productSelected.Category);
    Assert.AreEqual(productSelected.Category.CategoryID, categoryNew.CategoryID);
    
    Assert.IsNotNull(productSelected.Supplier);
    Assert.AreEqual(productSelected.Supplier.SupplierID, supplierNew.SupplierID);
}

 

Se instancias los repositorios para cada una de las entidades intervinientes y se crean las entidades de forma individual.

Por medio de la propiedad que representan la Foreing Key es que logramos la asociación entre las entidades. Cada operación genera en la db las siguientes operaciones de insert

image_thumb[31]

Al recuperar el producto y sus relaciones se debe especificar cuales se quiere traer en al definición del parámetro del include, esto genera la consulta:

image_thumb[21]

en una sola operación se recupera el producto, la categoría y el proveedor optimizando el acceso a la base de datos

 

Test – Crear Producto con categorías y proveedores NO existentes


Si se necesita en un único paso crear una entidad y sus relaciones se puede definir como el siguiente test

[TestMethod]
public void Create_WithCategoryAndSupplier_Product()
{
    ProductRepository repoProduct = new ProductRepository();

    //se crea el producto relacionado con la categoria
    Product productNew = new Product()
    {
        ProductName = "prod 1",
        UnitPrice = 10,
        Discontinued = false,
        Category = new Category()
                    {
                        CategoryName = "category1",
                        Description = "desc category 1"
                    },
        Supplier = new Supplier()
                    {
                        CompanyName = "Company 1",
                    }
    };
    repoProduct.Create(productNew);

    //se recupea el producto con la categoria y proveedor asociado
    var productSelected = repoProduct.Single(x => x.ProductID == productNew.ProductID,
                                            new List<Expression<Func<Product, object>>>() { x => x.Category, x => x.Supplier });

    Assert.IsNotNull(productSelected.Category);
    Assert.AreEqual(productSelected.Category.CategoryID, productNew.CategoryID);

    Assert.IsNotNull(productSelected.Supplier);
    Assert.AreEqual(productSelected.Supplier.SupplierID, productNew.SupplierID);
}

solo se utiliza un único repositorio creando las entidades asociadas directamente en la misma acción, al ejecutar en la db se registras las operaciones de insert por cada entidad

image_thumb[32]

a diferencia del test anterior aquí las operaciones se realizan una a continuación de otra, luego de la creación de las entidades estas tendrán asignado los Id que genera la db para todas las entidades, tanto la principal como para las relacionadas

el select que recupera el producto no sufre cambios comparado con el test anterior

image_thumb[33]

 

Test – Crear Categoría con Productos existentes


Crearemos el productos de forma independiente para después relacionar con la categoría.

[TestMethod]
public void GetSingle_AllProducts_Category()
{
    ProductRepository repoProduct = new ProductRepository();
    CategoryRepository repoCategory = new CategoryRepository();

    //se crea la categoria
    Category categoryNew = new Category()
    {
        CategoryName = "category1",
        Description = "desc category 1"
    };
    repoCategory.Create(categoryNew);

    //se crea el producto relacionado con la categoria
    Product productNew1 = new Product()
    {
        ProductName = "prod 1",
        UnitPrice = 10,
        Discontinued = false,
        CategoryID = categoryNew.CategoryID
    };
    repoProduct.Create(productNew1);

    Product productNew2 = new Product()
    {
        ProductName = "prod 2",
        UnitPrice = 12,
        Discontinued = false,
        CategoryID = categoryNew.CategoryID
    };
    repoProduct.Create(productNew2);


    var categorySelected = repoCategory.Single(x => x.CategoryID == categoryNew.CategoryID, 
                                                    new List<Expression<Func<Category,object>>>() { x=>x.Products });

    Assert.IsNotNull(categorySelected);
    Assert.IsNotNull(categorySelected.Products);
    Assert.AreEqual(categorySelected.Products.Count, 2);

}

Sino se define el include al recuperar la categoría la propiedad “Products” devolverá null

image

pero si se define el “include” se podrán obtener los productos relacionados a la entidad

image

se ejecuta un único select para recuperar la categoría y sus productos

image

el “include” optimiza la consulta generada por EF para recuperar las entidades

 

Test – Crear Categoría con Productos NO existentes


Se puede crear las entidades y asociarlas en una misma acción, pero debe recordarse que se crearan registros nuevos en la tabla para la entidad principal como para las relaciones

[TestMethod]
public void Create_WithProducts_Category()
{
    CategoryRepository repoCategory = new CategoryRepository();

    //se crea la categoria
    Category categoryNew = new Category()
    {
        CategoryName = "category1",
        Description = "desc category 1",
        Products = new List<Product>(){
            new Product()
                {
                    ProductName = "prod 1",
                    UnitPrice = 10,
                    Discontinued = false
                },
            new Product()
                {
                    ProductName = "prod 2",
                    UnitPrice = 12,
                    Discontinued = false
                }
        }
    };
    repoCategory.Create(categoryNew);

    var categorySelected = repoCategory.Single(x => x.CategoryID == categoryNew.CategoryID,
                                                    new List<Expression<Func<Category, object>>>() { x => x.Products });

    Assert.IsNotNull(categorySelected);
    Assert.IsNotNull(categorySelected.Products);
    Assert.AreEqual(categorySelected.Products.Count, 2);

}

La creación de los registros ejecutara varias instrucciones insert, luego de la creación del registro devolverá los Id generados en cada instrucción

image

al recuperar al entidad principal y su relación con la colección optimizara el select para realizarlo en una única operación

image

 

Código


El código se encuentra en la primer parte del artículo.

9 comentarios:

  1. Hola Leo!!

    Soy yo otra vez, no me denuncies por acoso ;) , los articulos son muy buenos y me sirvieron un monton y entendi todo hasta ahora ya que lo explicas de una manera muy buena, pero... lo que no entiendo bien es la parte del repositoriobase, si entiendo como funciona el patron no entiendo lo de las listas de expresiones y delegados del todo bien, por ejemplo

    public T Single(Expression> predicate, List>> includes)
    {
    List includelist = new List();

    foreach (var item in includes)
    {
    MemberExpression body = item.Body as MemberExpression;
    if (body == null)
    throw new ArgumentException("The body must be a member expression");

    includelist.Add(body.Member.Name);
    }

    using (LearningContext context = new LearningContext())
    {
    DbQuery query = context.Set();

    includelist.ForEach(x => query = query.Include(x));

    return query.FirstOrDefault(predicate);
    }
    }
    no lo puedo terminr de entender, podris hacer cuando puedas un articulo sobre esto?

    Gracias!!!!

    ResponderEliminar
  2. hola Patos

    se que no es un tema simple de entender, tampoco me enfoque en estos puntos porque la idea era tratar EF

    para comprender esa parte primero debes conocer lo que es un Expression Tree

    Expression Trees (C# and Visual Basic)

    esta aporta dinamismo al codigo, ya que permite analizar la expression definida en el lambda y poder usarla para armar queries dinamica

    en este caso lo utilizo para poder hacer tipado la definicion del include y luego dentro del metodo parsear la expresion y obteniendo en este caso las propiedades que luego utilizare para definir el include de de EF

    es igualmente valido si quieres hacer que el include sea una lista de string, funciona perfectamente, solo que no haces que el repositorio sea tipado

    saludos

    ResponderEliminar
  3. Hola Leo Te hago una consulta,
    basada en el modelo de estos articulos de EF que publiscaste.

    Yo tengo dos entidaades con relaciones de muchos a muchos que son proveedores y marcas,

    suponiendo que el objeto marca ya existe, yo quiero hacer proveedor.marcas.add(objeto_marca);


    repositorioProveedores.update(proveedor);

    pero no me toma el cambio, como hago para poder asociar estas dos entidades ?

    Desde ya muchas gracias!

    ResponderEliminar
  4. hola Patos

    sabes que ese es todo un tema, justamente estoy armando un articulo para las relaciones mucho a mucho donde planteo esto que comentas, porque es verdad no aplica exactamente como lo quieres realizar

    si me esperas al fin de semana lo publico

    saludos

    ResponderEliminar
  5. Hola leandro, disculpa que te moleste otra vez. Estoy utilizando tu repositorio genérico y a medida que voy haciendo uso de los métodos, tengo algunas dificultades: En este caso tengo una consulta:
    Tengo 3 entidades: User, Role y UserRole(que nace de una relación N:N). Cuando hago uso del método List con includes, obtengo el resultado sin problemas, pero como valor de User y Role (propiedades virtuales de la entidad UserRole) me devuelve lo siguiente:
    User: Entities.User
    Role: Entities.Role

    No se si estoy haciendo mal los includes. Como podría hacer para que las propiedades tengan el valor ejemplo:
    User: Carlos Perez
    Role: Contador

    A continuación le muestro el código:

    public static List ListUserRole()
    {
    return useRolRepository.List(new List>>() {x => x.Role, x => x.User});
    }


    Saludos y Gracias...

    ResponderEliminar
  6. hola cquispe

    pero esa entidad UserRole no define la relacion muchos a muchos, porque si es asi como entidad no debes definirlo

    las relaciones entre User y Rol es directa por medio de propiedades del tipo ICollection<>

    porque no defines una relacion mucho a muchos? y asi evitas la entidad UserRole

    saludos

    ResponderEliminar
  7. Hola Leandro, el detalle es que las entidades no lo definí manualmente, generé con EF Power Tools. A continuación te muestro las 3 entidades:

    public partial class User
    {
    public User()
    {
    this.UserRoles = new List();
    }

    public int UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Dni { get; set; }
    public string Address { get; set; }
    public string Email { get; set; }
    public System.DateTime BirthDate { get; set; }
    public string Gender { get; set; }
    public int SystemAccess { get; set; }
    public string Login { get; set; }
    public string Password { get; set; }
    public int State { get; set; }
    public int CreateUser { get; set; }
    public Nullable UpdateUser { get; set; }
    public System.DateTime CreateDate { get; set; }
    public Nullable UpdateDate { get; set; }
    public virtual ICollection UserRoles { get; set; }
    }


    public partial class Role
    {
    public Role()
    {
    this.UserRoles = new List();
    }

    public int RoleId { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public int State { get; set; }
    public int CreateUser { get; set; }
    public Nullable UpdateUser { get; set; }
    public System.DateTime CreateDate { get; set; }
    public Nullable UpdateDate { get; set; }
    public virtual ICollection UserRoles { get; set; }
    }

    public partial class UserRole
    {
    public int UserRoleId { get; set; }
    public int UserId { get; set; }
    public int RoleId { get; set; }
    public int State { get; set; }
    public int CreateUser { get; set; }
    public Nullable UpdateUser { get; set; }
    public System.DateTime CreateDate { get; set; }
    public Nullable UpdateDate { get; set; }
    public virtual Role Role { get; set; }
    public virtual User User { get; set; }
    }

    Saludos...

    ResponderEliminar
  8. hola cquispe

    y te quedas con lo que el power tools te haya generado

    las herramientas pueden aproximar la definicion peor podrias aplciar cambios sobre esta
    esta claro que esto que planteas es una relacion mucho a muchos en donde la tabla UserRole no deberia mapearse como entidad

    de porque esta tabla tiene un UseRoleId la verdad no lo veo correcto y tampoco deberia tener auditoria, ni un campos de estado, las relaciones entre entidades deben ser simples
    no habias evaluado definir una relacion muchos a muchos entre estas entidades ?

    sino esta claro que vas a tener que programarte toda la relacion ya que serian dos relaciones uno a muchos
    la propiedad no solo tiene un user y un rol sino que tiene estado y demas campos que la definen por eso es que no puedes asignar algo simple a esa entidad porque la relacion es compleja

    saludos

    ResponderEliminar