LINQ to SQL y una Aplicacion en capas….

Vayamos directo al grano, vamos a tomar a AdventureWorks como ejemplo.

Veamos a Linq to SQL:

En la forma simple de usarlo agregamos un diagrama Linq to SQL Class, a nuestra aplicación:

 http://galeon.com/solocodigo/images/blog/2008/03Mar/22_Linq_Sql.jpg

Creamos una consulta simple y básica:

   1: static void Main(string[] args)
   2:     {
   3:  
   4:       dcAdventureWorksDataContext dbAW = 
   5:         new dcAdventureWorksDataContext();
   6:  
   7:       colGen.IEnumerable<Product> enumProd = 
   8:               from p in dbAW.Products
   9:               where p.Name.StartsWith("W")
  10:               select p;
  11:  
  12:       foreach (Product prod in enumProd.ToList<Product>())
  13:       {
  14:         Console.WriteLine("Prod: {0} -> {1}", 
  15:                   prod.Name, prod.ProductNumber);
  16:       }
  17:       Console.ReadLine();
  18:     }

Este query muestra todos la lista de productos cuyo nombre empiece con W.

Veamos la magia de Linq to SQL:

  • Para usar el diagrama que hemos agregado debemos usar una instancia del mismo.
  • Hay muchos ejemplos donde sólo se usa “var” para declarar a la variable que contendrá los resultados, y esto podría ser un útil para construir listas con nuevas columnas, por ejemplo columnas agregadas, claro eso en el select. Pero cuando yo agrego un diagrama LinqtoSQL, este también crea una clase por cada entidad en el diagrama:
   1: [Table(Name="Production.Product")]
   2: public partial class Product : INotifyPropertyChanging, INotifyPropertyChanged
   3: {
  • Es por eso que cuando tu pones “p.”, te salen todas las columnas disponibles de la tabla mapeada, y no es que este consultado a la base de datos para saber que columnas tiene, si por ejemplo se agrega una columna a la tabla en la base de datos, tenemos que volver a mapear, o agregar la propiedad respectiva a la clase existente. Y es así que puede usar un IEnumerable<Product>, para almacenar los resultados, recuerden que la clase Product, yo no la he creado, a sido generada en la generación del diagrama.
  • Miren la condición que estamos agregando, creo que la principal ventaja, es que tenemos todo el soporte de .NET, para las condiciones y operaciones que necesitemos realizar en la consulta.
  • Como pueden ve,r fácilmente puede convertir IEnumerable<> a un List<>.
  • Pero que pasa en SQL?, miremos a SQL Profiler:
   1: exec sp_executesql N'SELECT [t0].[ProductID], [t0].[Name], [t0].[ProductNumber], 
   2:    [t0].[MakeFlag], [t0].[FinishedGoodsFlag], [t0].[Color], [t0].[SafetyStockLevel], 
   3:    [t0].[ReorderPoint], [t0].[StandardCost], [t0].[ListPrice], [t0].[Size], 
   4:    [t0].[SizeUnitMeasureCode], [t0].[WeightUnitMeasureCode], [t0].[Weight], 
   5:    [t0].[DaysToManufacture], [t0].[ProductLine], [t0].[Class], [t0].[Style], 
   6:    [t0].[ProductSubcategoryID], [t0].[ProductModelID], [t0].[SellStartDate], 
   7:    [t0].[SellEndDate], [t0].[DiscontinuedDate], [t0].[rowguid], [t0].[ModifiedDate]
   8: FROM [Production].[Product] AS [t0]
   9: WHERE [t0].[Name] LIKE @p0',N'@p0 nvarchar(2)',@p0=N'W%'
  • Primero que notamos?, que es un query parametrizado, es decir que si el usuario quiere hacer SQL Injection, sería algo como esto:
   1: exec sp_executesql N'SELECT [t0].[ProductID], [t0].[Name], [t0].[ProductNumber], 
   2:    [t0].[MakeFlag], .....
   3: FROM [Production].[Product] AS [t0]
   4: WHERE [t0].[Name] LIKE @p0',N'@p0 nvarchar(10)',@p0=N'W'' OR 1=1%'

Ya hablamos de lo bonito, pero….

  • Si tenemos una aplicación en capas, este modelo no nos ayudaría mucho. Ya que tenemos una capa especial para consulta de acceso a datos.
  • Y si quiero usar Linq to SQL, en capas?… a ver veamos

Ahora hagamos un custom Linq to SQL, para usarlo en una aplicación en Capas:

Digamos que yo cuento con mis entidades:

   1: public class miProducto
   2:  {
   3:  
   4:    private Int32 productIDField;
   5:    private String nameField;
   6:    private String productNumberField;
   7:    private Decimal listPriceField;
   8:  
   9:    public Int32 ProductID
  10:    {
  11:      get { return productIDField; }
  12:      set { productIDField = value; }
  13:    }    
  14:    public String Name
  15:    {
  16:      get { return nameField; }
  17:      set { nameField = value; }
  18:    }    
  19:    public String ProductNumber
  20:    {
  21:      get { return productNumberField; }
  22:      set { productNumberField = value; }
  23:    }    
  24:    public Decimal ListPrice
  25:    {
  26:      get { return listPriceField; }
  27:      set { listPriceField = value; }
  28:    }
  29:  }

A nuestra entidad tenemos que agregarle los siguientes atributos:

   1: [Table(Name = "Production.Product")]
   2:   public class miProducto
   3:   {
   4:  
   5:     private Int32 productIDField;
   6:     private String nameField;
   7:     private String productNumberField;
   8:     private Decimal listPriceField;
   9:  
  10:     [Column]
  11:     public Int32 ProductID
  12:     {
  13:       get { return productIDField; }
  14:       set { productIDField = value; }
  15:     }
  16:     [Column]
  17:     public String Name
  18:     {
  19:       get { return nameField; }
  20:       set { nameField = value; }
  21:     }
  22:     [Column]
  23:     public String ProductNumber
  24:     {
  25:       get { return productNumberField; }
  26:       set { productNumberField = value; }
  27:     }
  28:     [Column]
  29:     public Decimal ListPrice
  30:     {
  31:       get { return listPriceField; }
  32:       set { listPriceField = value; }
  33:     }
  34:   }

Con estos atributos estoy habilitando para que mi clase pueda ser usada con Linq to SQL, notar que en las propiedades de los atributos se puede determinar con que tabla voy a mapear, y podría determinar con que columna de la tabla voy a mapear una determinanda propiedad.

En la capa de acceso, mi clase sería así:

   1: public class ProductRepository
   2: {
   3:  
   4:   private DataContext dbAW;
   5:   public ProductRepository(String connectionString)
   6:   {
   7:     dbAW = new DataContext(connectionString);
   8:   }
   9:  
  10:   public IEnumerable<miProducto> getManyByNameStart(String start)
  11:   {
  12:     IEnumerable<miProducto> enumProd;   
  13:     
  14:       enumProd = from prod in dbAW.GetTable<miProducto>()
  15:                  where prod.Name.StartsWith(start)
  16:                  select prod;
  17:  
  18:       return enumProd;
  19:   }
  20: }

Notar que el connectionString lo estamos pasando de la capa que la llame, y es aquí donde iría los queries, Linq to SQL.

Y en la capa de negocios sería así:

   1: public class BLProduct
   2: {
   3:  
   4:   private ProductRepository repProduct;
   5:  
   6:   public BLProduct(String connectionString)
   7:   {
   8:     repProduct = new ProductRepository(connectionString);
   9:   }
  10:  
  11:   public List<miProducto> getManyByNameStart(String start)
  12:   {
  13:     return repProduct.getManyByNameStart(start).ToList<miProducto>();
  14:   }    
  15: }

Sólo un maquillado para la llamada de la capa de presentación.

Y por último el FrontEnd, lo podría usar así:

   1: static void Main(string[] args)
   2:  {
   3:  
   4:    String cnAW = 
   5:      ConfigurationManager.ConnectionStrings["dbAW"].ConnectionString;
   6:    BLProduct blProd = new BLProduct(cnAW);
   7:  
   8:    List<miProducto> lstProd = blProd.getManyByNameStart("L");
   9:    foreach (miProducto prod in lstProd)
  10:    {
  11:      Console.WriteLine("Prod: {0} -> {1}", 
  12:                prod.Name, prod.ProductNumber);
  13:    }
  14:  
  15:    Console.ReadLine();
  16:  }

Algunas conclusiones finales:

  • Esta es una de las formas como podría encajar Linq to SQL, en un modelo en capas. Si de todas maneras estas armando tus queries en la capa de acceso, pues Linq to SQL te puede caer a pelo de gato :D.
  • Ahora, si tienes un modelo donde sólo usas StoreProcedures, Linq to SQL, no va encajar bien en tu escenario.
  • Y noten que el dilema no sería Linq to SQL (en la capa de acceso a datos) vs. StoreProcedures, la verdadera discusión sería “Queries en ADO.NET o usar Store Procedures”…. por cierto aquí se armo una buena discusión: ventajas de usar Procedimientos Almacendos?.

Descargar ejemplo: Linq to SQL en una Aplicacion en Capas.

Nos vemos, y ya viene el post sobre Linq to Objects :D…

Saludos,

18 comentarios en “LINQ to SQL y una Aplicacion en capas….”

  1. Hola Sergio!
    Muy buena entrada…sin duda LINQ To SQL es una buena opción para aplicaciones en n-capas sencillas, pero ¿Qué pasa cundo empezamos a pensar en arquitecturas más complicadas? Por ejemplo, que tengamos n-capas y estas estén distribuidas. Para mi esta es una gran duda con respecto a LINQ To SQL y que no tengo con ADO.NET EF…he leído en varios blogs sobre el tema, pero no queda claro cuál sería la forma adecuada de implementar este tipo de arquitecturas con LINQ To SQL en cuanto a las dudas que hay en torno a si el objeto DataContext es serializable o no.

    Un saludo

    JC’s

  2. Interesante, pero…¿tendríamos que remapear todas las propiedades de nuestra clase de BL a nuestra clase de LINQ a mano? Porque la consulta que haces es a nivel de clase, pero qué pasa cuándo sea a nivel de objecto. i.e: Dame todos los productos con que empiecen con w y esten asociados a este.

    POr último…porque no hacerlo con parciales y ya está? qué ganas con tu aproximación?

    Gracias, pero es un tema que me interesa mucho

  3. Hola Juan Carlos!

    Ojo que no estoy promoviendo Linq to SQL en la capa de acceso a datos, lo quería recalcar es que si tienen estructura en capas sencilla y estas haciendo tus queries en ADO.NET, y no usando Store Procedures, te puede servir mucho.

    Saludos,

  4. Toneti, no pretendo que todos los escenarios se aplique este modelo que he presentado, es más, yo para el acceso a datos uso el paquete de acceso a datos del Web Service Software Factory, y bueno no voy a remapear todas mis clases.

    Lo qué quería mostrar en mi post:
    1. Linq to SQL, de manera simple no se puede aplicar en una aplicación en capas, y digamos que no es útl.
    2. Si vas hacer una aplicación capas, y vas armar tus queries en ADO.NET, no vas a usar Store Procedures, pues un custom Linq to SQL te puede servir.
    3. Ahora en mi aproximación, es una manera de hacer Linq to SQL en una Aplicación en Capas, como bien mencionas hay otras aproximaciones que se puede hacer, la idea no es cambien sus modelos a esta estructura de capas simple, si no que aprovechen el custom Linq to SQL, en sus aplicaciones con capas, claro siempre y cuando tengan pensado hacer los queries en ADO.NET, por que si no tampovo es aplicable.

    Gracias por los comentarios, y a esperar que trae la versión final de SQL to Entities…

    Saludos,

  5. Una pregunta, ¿ es impresión mia o estás pasando la cadena de conexion por casi todas las capas?

    No me parece la mejor forma, espero que sea para este ejemplo 🙂

    Por otro lado el problema que veo al usar LINQ en capas es mantener con “vida” el DataContext y no tener que crearlo cada vez que necesitas hacer una busqueda por el nombre.

    Amén de otras dudas como las que plantea JC

  6. Hola Neko!

    En las pruebas que hice cada vez que creas un nuevo DataContext, reutiliza la misma conexion que ya ha sido creado por el anterior, por ejemplo so encierras todo el código del ejemplo dentro del mail, y lo llamas cinco mil veces, todas usarán el mismo pool de conexiones (exec sp_WHO en SQL, para chekar esto).

    Artículos relacionados: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2087068&SiteID=1, y http://www.blogcoward.com/archive/2008/02/07/398.aspx.

    Recalco, no estoy propionendo este modelo para el desarrollo de aplicaciones, sólo quería plantear una aproximación, si alguién esta pensando en implementar Linq to SQL, en un modelo en capas. Y que gracias a los comentarios de todos, se puede llegar a un buen modelo :), y nuevamenta para todos los que vayan hacer sus queries en ADO.NET, y no en en la base de datos con SPs…

    Saludos,

  7. Charlie, la condición que puse era sólo como ejemplo para mostrar la traducción del código .NET a SQL, pero como dices para consultas dinámicas la librería Dinamic LINQ cae a pelo, gracias por la acotación.

    Saludos,

  8. Yo todavia lo q no entiendo bien es como poder usar las entidades como DTO, ya q si deseas “compartir” todas las entidades por los proyectos UI-BL-DB se debe hacer referencia a la capa DB desde UI…no siendo del todo correcto.
    Sabeis si EF permite generar el mapeo conceptual en otro proyecto independiente?

    Saludos y muchas gracias!

  9. Hola Sergio:

    Maestro, segun el codigo quie veo que genera linq, la verdad me parece que por ejm. en escenarios web la performance es baja, cosa que es un crimen en este tipo de escenarios.

    Linq se ve muy bonito y manejable, pero a mi arecer aun tiene que pasar por su etapa de maduracion.

    Una aplicacion profesional quiere performance y linq por lo visto aun no lo brinda, es algo asi como los Datasets e inclumo mas lento que ello (me refiero a performance), que opinas al respecto?

  10. La aplicacion msdn video es una buena fuente para investigar ahi lo implmentan con wcf ahora les doy una aproximacion con respcto a la pregunta como poder usar las entidades como DTO…
    En msdn video el datacontext no esta creado en la capa de datos si no mas bien como una capa de entidades de negocio.
    saludos,

  11. en tu capa de acceso cuando estableciste este metodo, yo sugiero que se podria trabajar con static al metodo, incluso a la variable dbAW, asi evitamos el problema comentado de las conexiones…
    public ProductRepository(String connectionString)

    6: {

    7: dbAW = new DataContext(connectionString);

    8: }

    Otro comentario que me parecio interesante es el hecho de tener en cuenta que linq aun no esta del todo listo(maduro) como para ser aplicado en grande, tampoco se puede decir que es de lo peor. En modelo de capas podria armarse una muy buena discusion sobre la participacion de linq.

    Gracias por el aporte Sergio que me ayudo a ver un poco mas claro el funcionamiento de esto en visual ya que no soy de desarrollar con tecnologia de microsoft y no me gustaba ver en mis trabajos de la u q empiecen a salir nuevos archivos generados por el ide. Ya vi como puedo ordenarme.

  12. John, hay algunos artículos que hablan sobre la performance, pero depende también de la base de datos tu aplicación.

    El objetivo del artículo era mostrar como se puede personalizar algunas cosas y de ahí hablar de Linq to Objects, pero no para recomendar su uso.

    Por otro lado, para proyectos no he usado Linq To Sql, si no Linq to Entities o también llamado ADO.NET EF.

    Saludos,

  13. A ver, que soy un poco nuevo en esto. Creo que todo puede ser mas senillo ¿no? Pongamos que tengo 3 capas (3 namespaces principales) AccesoDatos, LogicaNegocio y Presentacion.
    Mi objetivo es independizar el código entre las capas, de manera que pueda cambiar la tecnología de acceso a datos totalmente sin necesidad de tocar el código de la capa de Presentación ni un ápice ¿no?
    Pues pongamos que para enlazar (DataBind) cualquier control de la capa de Presentación a los datos quiero SIEMPRE usar DataTables. Los DataTables se rellenarían simplemente haciendo:

    dt = AccesoDatos.GetElementos()
    control.DataSource = dt

    siendo GetElementos() la función dentro de la capa de AccesoDatos que me recupera los elementos de la base de datos…

    Entonces… ¿Qué más da si lo hago con ADO.NET puro y duro, o con LINQ to SQL puro y duro, o con LINQ to DataSet, o con las virguerías que querais? Será lo que a mi me guste más ¿no? Mi único objetivo en la capa de acceso a datos es que la función me devuelva un DataTable, que es lo que necesito yo en el resto de mis capas (y quien dice un datatable dice un xmlNodeList).

    Es que no entiendo por qué hay que complicarse tanto la habichuela…

    Gracias, estoy empezando y estos temas me interesan…

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *