Documente Academic
Documente Profesional
Documente Cultură
Linq es el nuevo ORM Object Relational Model que trata de saltar la brecha existente entre dos mundos otrora incompatibles: Los lenguajes de programacin y los lenguajes de acceso a bases de datos. Antes de Linq, los lenguajes de programacin no tenan nocin de los datos, y por ello nos encontrbamos escribiendo sentencias SQL en strings, sin oportunidad que los lenguajes nos pudieran ayudar con intellisense (que automticamente liste tablas, campos, etc.) y con revisin de sintaxis, etc.
CREANDO EL MODELO
Asumimos que tenemos ya un proyecto. En nuestro ejemplo, es un proyecto Web (conocidos en el mundo .NET como ASP.NET). En el Solution Explorer dar click derecho sobre el nombre del proyecto y pida la opcin Add New Item
Escoger el tem: LINQ to SQL Classes y en el nombre (Name) pngale un nombre descriptivo, como Northwind.dbml Presione en Add.
Aparece un mensaje indicando que insertar el archivo dentro de la carpeta App_Code. Conteste que S. Aparece el Object Relational Designer.
Note que a la derecha del Object Relational Designer hay una zona vertical para insertar mtodos. Si esta zona est escondida y desea verla, puede encenderla dando click derecho sobre el Object Relational Designer y pidiendo la opcin Show Methods Pane.
Ac puede crear entidades y relaciones manualmente, o los puede crear a partir de tablas de la base de datos, yendo al Server Explorer y arrastrando tablas al rea de diseo.
Note que el diseador trata de convertir los nombres de tabla en singular: Products se convierte en Product. Si desea cambiar el nombre, de doble click sobre el nombre. 3
Es posible tambin insertar objetos como Vistas. En este ejemplo, hemos adicionado la vista Invoices:
Si quiere agregar Procedimientos Almacenados (Stored Procedures), arrstrelo hacia el rea de mtodos. En este ejemplo, hemos arrastrado el procedimiento almacenado llamado Ten Most Expensive Products (Los 10 Productos Ms Caros) hacia el rea de mtodos.
Note que hay una relacin entre Product y Category. Automticamente en el DataContext genera una propiedad Category dentro del objeto Product, de forma que podemos accesar a Product.Category.CategoryName. Esta relacin puede ser modificada al seleccionarla. Note que la Cardenality es OneToMany (Uno a muchos).
Uno de los ajustes que podemos hacer es que slo carguen imgenes cuando son necesarios, para ahorrar ancho de banda. Por ejemplo, el campo Picture de la tabla Category:
Si quisiera usar Stored Procedures para insertar, modificar o eliminar, ac mostramos dnde deben especificarse:
Partial Class _Default Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Dim db As New NorthwindDataContext ' product tiene la interface iEnumerable, ' que permite DataBinding Dim product = From p In db.Products _ Select p gvNorthwind.DataSource = product gvNorthwind.DataBind() End Sub End Class
Lo corremos (posiblemente aparezca un mensaje pidiendo cambiar el Web Config para poder debug. Contestemos que s):
Modificamos nuestra expresin LINQ para que slo muestre los productos cuya categora tenga un nombre especfico. Note que este tipo de query implica un JOIN entre dos tablas.
Dim product = From p In db.Products _ Where p.Category.CategoryName = "Beverages" _ Select p
Una vez interrumpidos por el breakpoint, podemos ver los datos devueltos:
Supuestamente se tendra que ver tambin el Query, y aparece una lupa que hay que darle click. En el video, aparece la siguiente ventana:
10
Note que estamos haciendo un .Count de los Order_Details de p. Y luego, hacemos un aggregate usando p.Order_Details sumando la multiplicacin del precio unitario con la cantidad, para tener el valor total de un producto.
Nota: No s por qu el orden de las columnas sale de esa manera. Tal vez haya que especificar directamente en el GridView las columnas que queremos ver. Vamos a Paginar en el Server, pues hay muchos resultados. En el siguiente ejemplo vamos a la fila 50 y tomamos 10 registros.
gvNorthwind.DataSource = product.Skip(50).Take(10) gvNorthwind.DataBind()
Vamos a mover el cdigo de acceso hacia una rutina aparte, para que lo podamos llamar desde varias partes. Parametrizamos la fila inicial con un parmetro llamado startRow
11
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Dim row As Integer = Convert.ToInt32(Request.QueryString("startRow")) bindProducts(row) End Sub Protected Sub bindProducts(ByVal startRow As Integer) Dim db As New NorthwindDataContext ' product tiene la interface iEnumerable, ' que permite DataBinding Dim product = From p In db.Products _ Select p.ProductID, _ p.ProductName, _ numOrders = p.Order_Details.Count, _ revenue = Aggregate detail In p.Order_Details _ Into Sum(detail.UnitPrice * detail.Quantity) gvNorthwind.DataSource = product.Skip(startRow).Take(10) gvNorthwind.DataBind() End Sub
Parecera aca que primero se hace el Select trayendo todos los datos, y luego se pagina. Sin embargo, LINQ utiliza un modelo de Deferred Execution (Ejecucin Diferida), que le permite ser ms eficiente.
12
Modificaremos el Mapping File (para el Data Context) ligeramente para la siguiente demostracin:
Agregamos 3 botones al formulario ASP.NET. Les puse nombres bonitos como: btnLame, etc.
13
Queremos buscar productos caros que no se estn vendiendo bien (Productos Lame). El SQL para dicha operacin es el siguiente:
Select p.ProductID, p.ProductName, p.UnitPrice, p.ReorderLevel, sum(od.Quantity) as "UnitsSold" from Products as p, "Order Details" as od where od.ProductID = p.ProductID and p.UnitPrice > 15 group by p.ProductID, p.ProductName, p.UnitPrice, p.ReorderLevel having sum(od.Quantity) < 200 order by UnitSold
Al correrlo y oprimir el botn Lame tenemos los productos caros que no tienen mucho movimiento:
14
Vamos actualizar los ReorderLevel a cero, pues NO queremos pedir ms de estos productos.
Protected Sub btnLame_Click( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnLame.Click Dim db As New NorthwindDataContext Dim r = From p In db.Products _ Where p.UnitPrice > 15 And _ p.Order_Details.Sum(Function(x) x.Quantity) < 200 _ Select p For Each p As Product In r p.ReorderLevel = 0 Next db.SubmitChanges() gvNorthwind.DataSource = r gvNorthwind.DataBind() End Sub
Y vemos que todos los ReorderLevel estn en cero. Un punto interesante es que LINQ slo envi dos Update, pues el producto con ProductID=9 ya tena cero desde antes y no era necesario actualizarlo. Nota: Usa optimistic concurrency, por lo que el WHERE lleva todos los campos.
TRANSACCIONES
Antes de usar las transacciones, hay que agregar una referencia a System.Transactions. Botn derecho sobre el proyecto, y pedir la opcin de Property Pages. Colquese en References y oprima el botn Add Agregue System.Transactions
15
16
Ponemos un breakpoint en el Transactions.Transaction.Current.Rollback() y cuando lo corremos y se queda trabado en el breakpoint, si vamos al SQL, no podemos ver los registros, pues estn enllavados (locked). En la base de datos SQL podemos poner:
set transaction isolation level read uncommitted
que nos permite leer dirty records y al correr un query veremos que los registros parece que ya han sido borrados.
17
CREAR REGISTROS
Crearemos un nuevo Producto.
Protected Sub btnNewProduct_Click( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnNewProduct.Click Dim db As New NorthwindDataContext Dim p As Product = New Product p.UnitPrice = -4.3 db.Products.InsertOnSubmit(p) db.SubmitChanges()
End Sub
Esto nos da un error, pues el ProductName no puede ser nulo. Lo interesante es que el error es detectado en el lado del cliente, y no por el servidor.
18
19
La clase resultante le ponemos el calificativo partial, pues extender a una clase existente.
Imports Microsoft.VisualBasic Partial Public Class Order Private Sub OnFreightChanging(ByVal value As Decimal?) If value <= 0 Then Throw New ApplicationException("No free shipping") End If End Sub Private Sub OnValidate(ByVal action As System.Data.Linq.ChangeAction) If RequiredDate < OrderDate Then Throw New ApplicationException("Time travel not possible") End If End Sub End Class
Modificamos nuestro cdigo que agrega la orden, para que rompa las reglas del negocio: :
Dim o As New Order o.OrderDate = DateTime.Now o.RequiredDate = DateTime.Now.AddDays(-2) o.Freight = -4.3
20
Si corregimos el Shipping, nos da el error de la fecha: Note que el lugar en donde da error vara con respecto al anterior.
21
Y el resultado es ste:
22
Al correrlo, y oprimir Insert tenemos: (en la pgina resultante, vemos el nuevo registro al inicio: Scotts Apple Juice)
23
Actualizando un registro:
Protected Sub btnUpdate_Click( _ ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnUpdate.Click Dim db As New NorthwindDataContext Dim p1 = (From p In db.Products _ Where p.ProductName.StartsWith("Scott's") _ Select p).Single p1.UnitPrice = p1.UnitPrice * 1.5 db.SubmitChanges() showProducts() End Sub
24
25
26
LINQDATASOURCE
Esta vez tendremos el acceso de datos en una clase aparte. Cree un proyecto tipo ClassLibrary que le puse: clsLibData20080211. Con File New Project
No usaremos el archivo que nos da por default: Class1.vb (podramos incluso borrarlo). Mas bien, vamos a agregar una clase Linq to SQL Data Context. En el Solution Explorer, damos click derecho sobre el nombre del proyecto clsLibData20080211 y pedimos Add New Item
27
Desde el Server Explorer arrastr las tablas Orders, Order Details, Products, Categories, Customers. Note que puede acercarse y alejarse del diagrama de tablas utilizando Ctrl + Scroll Wheel del ratn. Damos un build al proyecto y nos olvidaremos por el momento de l.
28
PROYECTO WEB
Ahora crearemos otro proyecto, tipo ASP.NET (Web) para interfaz del usuario. Vamos a File Add New Web Site
Referenciamos el proyecto anterior clsLibData20080211, que es la que accesa los datos. En el Solution Explorer damos click derecho sobre nuestro proyecto Web:
En la pgina que nos da al inicio: Default.aspx agregamos un GridView. Yo lo nombr como gvProducts.
29
30
Damos click en la flechita en la parte superior derecha del GridView para tener el men contextual del GridView. Pedimos la opcin: Choose Data Source - <New Data Source>
Si no aparece el dcNorthwindDataContext, probablemente nos falte la referencia a la clase clsLibData20080211, o nos falta hacer un Build de nuestro proyecto Web.
31
Para nuestro ejemplo, tambin oprimimos el botn Advanced en donde le diremos que nuestro Data Source permitir actualizaciones a los datos:
32
Al regresar al Asistente, damos Finish y ya tenemos el Linq Data Source adicionado a la pgina Web:
En el men contextual del GridView podemos ahora habilitar paginacin, ordenamiento y hasta edicin. Tenemos las opciones de edicin debido a que en el LinqDataSource especificamos en Advanced que permitiera agregar, editar o eliminar.
Vamos a eliminar algunas de las columnas que se muestran al usuario. Para ello, debemos marcar la columna y en el men contextual del GridView (no de la Columna), pedimos Remove Column.
33
Para nuestro ejercicio, eliminamos las columnas UnitPrice y UnitsInStock. Le dimos un AutoFormat para que se vea mejor:
34
Sin embargo, vemos que SupplierID y CategoryID son Foreign Key que no son nada amigables para el usuario. Eliminaremos estas dos columnas, pero esta vez desde el cdigo fuente HTML de la pgina. Los marcamos y borramos:
35
<asp:TemplateField HeaderText="Category" SortExpression="Category.CategoryName"> <ItemTemplate> <%#Eval("Category.CategoryName")%> </ItemTemplate> </asp:TemplateField> <asp:TemplateField HeaderText="Supplier" SortExpression="Supplier.CompanyName"> <ItemTemplate> <%#Eval("Supplier.CompanyName")%> </ItemTemplate> </asp:TemplateField>
El resultado es que ahora vemos los nombres de la Categora (Beverages, Condiments, Produce) y del Proveedor (Exotic Liquids, New Orleans Cajun Delights, Grandma Kellys Homestead). Sin embargo, no podemos editarlos:
36
Ahora modificaremos los Template Columns para permitir la modificacin. Seleccionamos la columna que queremos modificar (comenzaremos con Category) NOTA: Creo que es indiferente qu columna marquemos, pues ms tarde nos pregunta por la columna. Damos botn derecho, Edit Template y escogemos la columna: Column[3] - Category
Nos interesa el EditItemTemplate, que muestra lo que ver el usuario cuando est editando. Arrastramos un DropDownList (un combo box). Yo le puse el nombre: ddlCategory.
Vamos a necesitar un nuevo DataSource para el Drop Down List. Pedimos el men contextual del Drop Down List, Choose Data Source. En la caja de dilogo, donde pide Select a Data Source: pedimos <New Data Source>.
37
Yo le puse de ID: ldsCategory. Pedimos el Context Object que reside en nuestro Class Library clsLibData20080211:
38
Escogemos la tabla Categories. Slo vamos a querer los campos: CategoryID y CategoryName:
Damos Finish y regresamos al Asistente de Choose a Data Source Escogemos cul campo se va a mostrar: CategoryName, y cul campo tiene los valores a devolver: CategoryID, por el Drop Down List ddlCategory:
39
Regresamos a la edicin del Template. Tenemos ahora que especificar los Data Bindings, de forma que lo mostrado en el Drop Down List ddlCategory sea aplicado a la tabla Products del GridView. En el men contextual de ddlCategory pedimos la opcin: Edit DataBindings.
Para salir del modo de edicin del Template, en el men contextual del Template hay una opcin End Template Editing
40
Hoy ya podemos cambiar la categora y el proveedor (supplier) escogiendo de drop down lists.
41
Usaremos el mismo LinqDataSource de Category ldsCategory que agregamos para el drop down list cuando estamos editando el grid. Sin embargo, el scope del data source es slo para el template. Lo sacaremos de all y lo pondremos fuera, donde puede ser usado por cualquier control del formulario. NOTA: Yo no estoy muy seguro que sea conveniente re-utilizar Data Sources. A m me pasaba con Windows applications que si dos controles usaban el mismo Data Source, ambos controles estaban conectados entre s. Tal vez en el Web este problema no se da.
Borramos el asp:LinqDataSource del Template, y lo colocamos en otra parte, casi al final del formulario:
42
Ahora regresamos a nuestr Drop Down List ddlCategoryFilter. En modo de diseo, pedimos su men contextual Choose a Data Source
Nota: Es posible que tenga que oprimir Refresh Schema para que aparezcan los campos para desplegar y para escoger el valor.
Para que inmediatamente al cambiar de categora nos muestre la pgina actualizada, pediremos: Enable AutoPostBack (Otra opcin sera poner un botn que lleve a cabo la accin). Tambin tenemos que indicarle al Grid que slo muestre los productos de una categora especfica.
43
Marcamos el Grid, vamos a su men contextual y pedimos: Configure Data Source Damos Next para pasar a la pantalla siguiente, donde nos muestra la tabla y los campos a mostrar. Oprimimos el botn Where
Queremos que la columa CategoryID sea igual (==) al Control ddlCategoryFilter. No se le olvide oprimir en Add para adicionar la expresin.
44
45
Abrimos el area de diseo de dcCustOrderList.dbml. Note que a la derecha hay un rea para mtodos. Si no se muestra, puede encenderla con botn derecho sobre el area de diseo, Show Methods Pane. Arrastraremos tambin la tabla Products para usarlo en el ejercicio.
46
Vamos a trabajar con el Stored Procedures llamado CustOrderHist que tiene el siguiente cdigo:
ALTER PROCEDURE CustOrderHist @CustomerID nchar(5) AS SELECT ProductName, Total=SUM(Quantity) FROM Products P, [Order Details] OD, Orders O, Customers C WHERE C.CustomerID = @CustomerID AND C.CustomerID = O.CustomerID AND O.OrderID = OD.OrderID AND OD.ProductID = P.ProductID GROUP BY ProductName
47
Del Server Explorer arrastramos el Stored Procedure CustOrderHist hacia el Methods Pane:
Agregamos una pgina Web DefaultCustOrderList.aspx y colocamos un GridView que llamamos gvCustOrderList.
48
Lo creamos y luego lo arrastramos al area de diseo de dcCustOrderList.dbml. Agregamos un label a la pgina Web. Yo le puse de ID: lblRowCount
49
50