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:

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,