¡Esta es una revisión vieja del documento!
O Linq to SQL.
El modelo de base de datos se muestra de forma visual en Visual Studio 2008 a partir de un archivo .dbml, puede ser editado mediante la Toolbox o arrastrando los elementos desde el administrador de servidores.
Al modelar la DB en el .dbml y guardar se crea una clase DataContext y clases relacionadas a las entidades y relaciones que hemos indicado. La clase DataContext es que representará la DB con propiedades correspondientes a las tablas y métodos a los procedimientos almacenados.
Por ejemplo, imaginemos que mapeamos una DB con productos y categorías en el siguiente DataContext:
NorthwindDataContext db = new NorthwindDataContext();
Podríamos hacer la siguiente consulta:
var products = from p in db.Products where p.Category.CategoryName == "Beverage" select p;
O la siguiente actualización:
NorthwindDataContext db = new NorthwindDataContext (); Product product = db.Products.Single (p => p.ProductName == "Toy 1"); product.UnitPrice = 99; product.UnitsInStock = 5; db.submitChanges();
O agregar algún producto:
Product prod = new Product (); prod.ProductName = "Toy"; db.Products.InsertOnSubmit (prod); db.SubmitChanges()
← Esto se podía hacer en anteriores versiones.
db.Products.Add (prod);
O borrarlo:
var toyProducts = from p in db.Products where p.ProductName.Contains("Toy") select p; db.Products.RemoveAll (toyProducts); db.submitChanges();
var products = db.GetProductsByCategory (5);
Coger productos de forma paginada:
var products = (from p in db.Products where p.Category.CategoryName.StartsWith("C") select p).Skip(200).Take(10);
Las “Clases Entidad” (Entity Classes) mapean tablas y sus instancias son denominadas “entidades”, las filas de la tabla. Las propiedades serían las columnas de dicha tabla. Podremos cambiar los nombres de dichas clases o de sus propiedades desde el mismo editor.
Las propiedades de las clases entidad tienen una propiedad denominada “Delay Loaded” cuando está a true hace que no se utilice esta propiedad hasta que no vaya a usarse (por ejemplo para que no nos vengan imágenes grandes cada vez que hacermos una consulta).
Si en el diseñador arrastramos procedimientos almacenados estos pasarán a ser métodos del DataContext. Si lo arrastramos sobre la entidad Product, esta devolverá un IEnumerable<Product>.
Si utilizasemos procedimientos almacenados para hacer Delete/Insert/Update sobre alguna tabla podemos indicarlo en las propiedades Delete, Insert o Update de una clase entidad.
La paginación la hacemos utilizando el Skip y el Take, además, como la consulta no se realiza hasta que no se la llama podríamos hacer algo así:
void BindProducts (int startRow) { NorthwindDataContext db = new NorthwindDatacontext(); var products = from p in db.Products where p.OrderDetails.Count > 2 select new { Id = p.ProductID, Name = p.ProductName, NumOrders = p.OrderDetails.Count, Revenue = p.OrderDetails.Sum (o => o.UnitPrice * o.Quantity) }; GridView1.DataSource = products.Skip(startRow).Take(10); gridView1.DataBind(); }
Para realizar validaciones sólo tendremos que crear una clase parcial y agregar un método parcial denominado On<campo>Changing:
public partial class Customer { partial void OnPhoneChanging(string value) { Regex phoneNumber = new Regex (@"^[2-9]\d{2}"); if (phoneNumber.IsMatch (value) == false) { throw new Exception("Not a valid phone number"); } } }
Ahora no se podrá hacer algo así:
Customer myCustomer = northwind.Customers.Single(c => c.CompanyName == "Beverages"); myCustomer.Phone = "asfkjasklfjaslkf"; northwind.SubmitChanges();
Podemos agregar reglas de validación a nivel de entidad, es decir, no sólo para las propiedades sino para la tabla en sí, y poder validar así datos que estén relacionados.
public partial class Order { public partial void OnValidate () { if (this.dataOrder > this.dataDelivery) throw ("La fecha de pedido es mayor que la de entrega!"); } }
Al hacer una llamada al método SubmitChanges se llamarán a otros métodos según la acción realizada y donde podemos lanzar nuestras excepciones para que dicha acción no llegue a realizarse. Por ejemplo, para una clase Order estos serían de la siguiente forma:
public partial class NorthwindDataContext { partial void InsertOrder (Order instance) { this.ExecuteDynamicInsert(instance); } partial void UpdateOrder (Order instance) { this.ExecuteDynamicUpdate(instance); } partial void DeleteOrder (Order instance) { this.ExecuteDynamicDelete(instance); } }
Pero podemos ir aún más lejos y hacer nuestras validaciones\comprobaciones en el método SubmitChanges de una clase propia que creemos:
public class MyNorthwindDataContext : NorthwindDataContext { public override void SubmitChanges (System.Data.Linq.ConfictMode failureMode) { ChangeSet changes = this.GetChangeSet(); changes.AddedEntities ... base.SubmitChanges (failureMode); } }
Podemos crear funciones personalizadas en la clase DataContext de la aplicación:
public partial class NorthwindDataContext { public IEnumerable<Product> GetProductsByCategory (int categoryID) { return from p in this.Products where p.CategoryID = categoryID select p; } }
Mediante el método ExecuteQuery de la clase DataContext:
public partial class NorthwindDataContext { public IEnumerable<Product> GetProductsByCategory (int categoryID) { return ExecuteQuery<Product>(@"select * from products where CategoryID = {0}", categoryID); } }
Cuando tenemos datos personalizados, es decir una clase como la siguiente:
public class ProductSummary { public int ProductID { get; set; } public string ProductName { get; set; } public Decimal? UnitPrice { get; set; } }
El retorno debería ser algo así:
public IEnumerable<ProductSummary> GetProductSummariesByCategory (int id) { return ExecuteQuery<ProductSummary>(@"select ProductID, ProductName, UnitPrice from products where CategoryID = {0}", categoryID); }
Es el método que tendremos que utilizar para realizar acciones de borrado, inserción o actualización.
Por ejemplo, para sobreescribir el borrado de la clase Product haríamos:
public partial class NOrthwindDatacontext { partial void DeleteProduct (Product instance) { ExecuteCommand ("delete from Products where ProductId = {0}", instance.ProductID); } }
Esto se utilizaría de la siguiente forma:
NorthwindDataContext db = new NorthwindDataContext (); Product product = db.Products.Single(p=>p.ProductName == "My product"); db.Products.Remove (product); db.SubmitChanges ();
Podemos mapear los procedimientos almacenados como métodos en la clase DataContexts arrastrandolos al diseñador. Si el método devolviese una secuencia de un elemento de una tabla, por ejemplo registros de Product, lo que haríamos sería arrastrarlo sobre la tabla.
Ahora podemos asignar el resultado de un procedimiento al DataSource de una tabla:
GridView.DataSource = Northwind.CustOrderHist ("Alfred"); GridView.DataBind();
Si el procedimiento pudiese devolver tanto campos de una tabla como de otra distinta tendremos que configurar el método para que devuelva una instancia de IMultipleResult.
Si quisieramos utilizar un procedimiento almacenado para hacer un Insert, Delete o Update de una tabla sólo tenemos que ir, en el diseñador, a dicha tabla y sobre el método deseado (en las propiedades) indicar que se hará de forma personalizada indicando entonces el procedimiento almacenado deseado.
El control <asp:LinqDataSource> enlaza modelos de datos LINQ. Utilizandolo no tenemos que definir métodos de consulta, inserción, actualización o borrado, sino que añadiendolo y definiendo las entidades con las que queremos que trabaje podremos enlazar cualquier control ASP.NET para que trabaje con él. Para ello, una vez tenemos el modelo y el entorno diseñados sólo tenemos que indicar en la propiedad DataSource del control que deseemos enlazar el modelo que queramos utilizar.
Esto generará un código con las columnas que se incluyen en el grid como <asp:BoundField>, y mediante sus propiedades podremos acceder al texto de cabecera y demás… Si alguna no quisieramos mostrarla la borraríamos desde el código o desde el mismo editor.
Podemos cambiar el campo de una tabla, por ejemplo uno que tenga identificadores por el nombre del registro correspondiente, cambiando el <asp:BoundField> por un <asp:TemplateField>. Tipo lo siguiente:
<!-- Este lo cambiamos --> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <!-- Por este --> <asp:TemplateField HeaderText="Supplier" SortExpression="Suppliers.CompanyName"> <ItemTemplate><%#Eval("Suppliers.CompanyName") %></ItemTemplate> </asp:TemplateField>
Podemos también colocar un combobox en un campo del grid, para ello añadiremos otro LinqDataSource a la página, esta vez de la tabla a la que represente. Por ejemplo:
<asp:LinqDataSource ID="SuppliersDataSource" runat="server" ContextTypeName="WebApplication1.NorthwindDataContext" TableName="Suppliers">
La columna en la que queramos agregar el combo ha de ser una TemplateField. En ella agregamos una EditItemTemplate enlazada al campo deseado:
<asp:TemplateField HeaderText="Supplier" SortExpression="Suppliers.CompanyName"> <ItemTemplate><%#Eval("Suppliers.CompanyName") %></ItemTemplate> <EditItemTemplate> <asp:DropDownList ID="DropDownList1" DataSourceID="SuppliersDataSource" DataValueField="SupplierID" DataTextField="CompanyName" SelectedValue='<%#Bind("SupplierID") %>' runat="server" /> </EditItemTemplate> </asp:TemplateField>
Si quisieramos filtrar los datos que aparecen el grid por un campo, podemos hacerlo a partir de un combobox enlazado a un LinqDataSource. Para ello colocamos dicho control en la página y únicamente tenemos que definir el DataSource al que está enlazado, el campo que se mostrará (DataTextField) y cual es el que contendrá el valor (DataValueField).
<asp:DropDownList ID="DropDownList2" runat="server" DataSourceID="SuppliersDataSource" DataTextField="CompanyName" DataValueField="SupplierID"> </asp:DropDownList>
Desde el editor podemos reconfigurar el DataSource del GridView y elegir el filtro (que se haga por Control (el nuestro, un combo), cookie, session, querystring…) tras apretar al botón where.
Para validar los datos introducidos lo haremos como ya lo hemos visto en el apartado de validación. Por ejemplo, en el caso de tener un grid que muestre productos lo que haremos será crear una clase parcial Product y escribir el método OnValidate:
partial public class Product { private void OnValidate () { if ((this.Discontinued) && (this.UnitsOnOrder > 0)) throw new Exception ("No puede ser pedido ese producto porque ha sido descontinuado"); } }
Esto se utilizará cada vez que se haga uso del modelo de datos (con una actualización, inserción o borrado).
Para realizar el control de errores tal vez el mejor sistio sea en el evento rowUpdated del GridView, ya que este recibe por parámetro los eventos ocurridos en la capa de presentación y se ejecutará cada vez que se actualice el DataSource.
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e) { if (e.Exception != null) { ErrorMessage.Text = e.Exception.Message; e.ExceptionHandled = true; e.KeepInEditMode = true; } }
Para que el código anterior funcione deberíamos de tener un texto donde con id ErrorMessage que será donde se muestre el mensaje de error:
<span><asp:Literal ID="Literal1" runat="server"></asp:Literal></span>
Podemos implementar el evento “Selecting” del objeto LinqDataSource esto nos servirá para poder escribir el código que queramos y obtener los datos; todo lo que tenemos que hacer es asignar la propiedad Result al objeto LinqDataSourceSelectEventArgs.
protected void ProductDataSource_Selecting (object sender, LinqDataSourceSelectEventArgs e) { NorthwindDataContext northwind = new NorthwindDataContext (); string[] countries = new string[] {"UK", "France", "Germany"}; var products = from p in northwind.Products where countries.Contains(p.Supplier.Country) select p; e.Result = products; }
Por ejemplo, podemos modificar el evento Selecting para montar un GridView que muestre un subconjunto de información de Product.
protected void ProductDataSource_Selecting (object sender, LinqDataSoruceSelectEventArgs e) { NorthwindDataContext northwind = new NorthwindDataContext (); var products = from p in northwind.Products where p.Category.CategoryName.StartsWith("C") select new { p.ProductID, p.ProductName, p.UnitPrice, NumOrders = p.OrderDetails.Count, Revenue = p.OrderDetails.Sum (o=>o.Quantity * o.UnitPrice) }; e.Result = products; }
Lo malo es que no funcionará la edición. Esto es debido a que estamos haciendo una montaje personalizado en el método Selecting, y el LINQDataSource no tiene forma de saber cómo actualizar la entidad. Si queremos añadir soporte para la edición podríamos crear un control ObjectDataSource (al que le agregaríamos un método Update personalizado para contorlarlos), o hacer que el usuario navegue a una nueva página para hacer la actualización - y mostrar un DetailsView o FormView enlazado a la entidad Producto para la edición (y no intentar hacerlo en el grid).