Uso de Entity Framework 4.0 y el control ObjectDataSource, parte 1: Introducción
Por Tom Dykstra
Esta serie de tutoriales se basa en la aplicación web Contoso University creada por la Introducción a Entity Framework 4.0 serie de tutoriales. Si no completó los tutoriales anteriores, como punto de partida de este tutorial, puede descargar la aplicación que habría creado. También puede descargar la aplicación creada por la serie de tutoriales completa.
En la aplicación web de ejemplo Contoso University se muestra cómo crear aplicaciones ASP.NET Web Forms con Entity Framework 4.0 y Visual Studio 2010. La aplicación de ejemplo es un sitio web para una universidad ficticia de Contoso. Incluye funciones como la admisión de estudiantes, la creación de cursos y asignaciones de instructores.
En el tutorial se muestran ejemplos en C#. El ejemplo descargable contiene código en C# y Visual Basic.
Database First
Hay tres maneras de trabajar con datos en Entity Framework: Database First, Model First, y Code First. Este tutorial es para Database First. Para obtener información sobre las diferencias entre estos flujos de trabajo e instrucciones sobre cómo elegir la mejor para su escenario, vea Flujos de trabajo de desarrollo de Entity Framework.
Web Forms
Al igual que la serie Introducción, esta serie de tutoriales usa el modelo de formularios ASP.NET Web Forms y supone que sabe cómo trabajar con ASP.NET Web Forms en Visual Studio. Si no lo hace, vea Introducción a ASP.NET 4.5 Web Forms. Si prefiere trabajar con el marco de ASP.NET MVC, vea Introducción a Entity Framework mediante ASP.NET MVC.
Versiones de software
Se muestra en el tutorial También funciona con Windows 7 Windows 8 Visual Studio 2010 Visual Studio 2010 Express para Web. El tutorial no se ha probado con versiones posteriores de Visual Studio. Hay muchas diferencias en las selecciones de menú, los cuadros de diálogo y las plantillas. .NET 4 .NET 4.5 es compatible con versiones anteriores con .NET 4, pero el tutorial no se ha probado con .NET 4.5. Entity Framework 4 El tutorial no se ha probado con versiones posteriores de Entity Framework. A partir de Entity Framework 5, EF usa de forma predeterminada el DbContext API
que se introdujo con EF 4.1. El control EntityDataSource se diseñó para usar laObjectContext
API. Para obtener información sobre cómo usar el control EntityDataSource con laDbContext
API, vea esta entrada de blog.Preguntas
Si tiene preguntas que no están directamente relacionadas con el tutorial, puede publicarlas en el Foro de ASP.NET Entity Framework, el Foro de Entity Framework y LINQ to Entities, o StackOverflow.com.
El EntityDataSource
control le permite crear una aplicación muy rápidamente, pero normalmente requiere que mantenga una cantidad significativa de lógica de negocios y lógica de acceso a datos en las páginas de .aspx. Si espera que la aplicación crezca en complejidad y requiera mantenimiento continuo, puede invertir más tiempo de desarrollo por adelantado para crear una estructura de aplicaciones de n niveles o superpuestas que sea más fácil de mantener. Para implementar esta arquitectura, se separa la capa de presentación de la capa de lógica de negocios (BLL) y la capa de acceso a datos (DAL). Una manera de implementar esta estructura es usar el ObjectDataSource
control en lugar del EntityDataSource
control. Cuando se usa el ObjectDataSource
control, se implementa su propio código de acceso a datos y, a continuación, se invoca en .aspx páginas mediante un control que tiene muchas de las mismas características que otros controles de origen de datos. Esto le permite combinar las ventajas de un enfoque de n niveles con las ventajas de usar un control de Formularios Web Forms para el acceso a datos.
El ObjectDataSource
control también proporciona más flexibilidad de otras maneras. Dado que escribe su propio código de acceso a datos, es más fácil hacer más que leer, insertar, actualizar o eliminar un tipo de entidad específico, que son las tareas que el EntityDataSource
control está diseñado para realizar. Por ejemplo, puede realizar el registro cada vez que se actualiza una entidad, archivar los datos cada vez que se elimina una entidad o comprobar y actualizar automáticamente los datos relacionados según sea necesario al insertar una fila con un valor de clave externa.
Clases de lógica de negocios y repositorio
Un ObjectDataSource
control funciona invocando una clase que se crea. La clase incluye métodos que recuperan y actualizan datos, y se proporcionan los nombres de esos métodos al control ObjectDataSource
en el marcado. Durante la representación o el procesamiento de postback, el ObjectDataSource
llama a los métodos especificados.
Además de las operaciones CRUD básicas, es posible que la clase que cree para usarla con el control ObjectDataSource
necesite ejecutar lógica de negocios cuando el ObjectDataSource
lee o actualiza los datos. Por ejemplo, al actualizar un departamento, es posible que tenga que validar que ningún otro departamento tenga el mismo administrador porque una persona no puede ser administrador de más de un departamento.
En algunas ObjectDataSource
documentación, como la información general de la clase ObjectDataSource , el control llama a una clase denominada objeto de negocio que incluye lógica de negocios y lógica de acceso a datos. En este tutorial creará clases independientes para la lógica de negocios y para la lógica de acceso a datos. La clase que encapsula la lógica de acceso a datos se denomina repositorio. La clase lógica de negocios incluye métodos de lógica de negocios y métodos de acceso a datos, pero los métodos de acceso a datos llaman al repositorio para realizar tareas de acceso a datos.
También creará una capa de abstracción entre BLL y DAL que facilita las pruebas unitarias automatizadas de BLL. Esta capa de abstracción se implementa mediante la creación de una interfaz y el uso de la interfaz al crear instancias del repositorio en la clase business-logic. Esto permite proporcionar a la clase lógica de negocios una referencia a cualquier objeto que implemente la interfaz del repositorio. Para una operación normal, se proporciona un objeto de repositorio que funciona con Entity Framework. Para las pruebas, se proporciona un objeto de repositorio que funciona con los datos almacenados de una manera que se puede manipular fácilmente, como las variables de clase definidas como colecciones.
En la ilustración siguiente se muestra la diferencia entre una clase lógica de negocios que incluye lógica de acceso a datos sin un repositorio y otra que usa un repositorio.
Comenzará creando páginas web en las que el control ObjectDataSource
se enlaza directamente a un repositorio porque solo realiza tareas básicas de acceso a datos. En el siguiente tutorial, creará una clase lógica de negocios con lógica de validación y enlazará el control ObjectDataSource
a esa clase en lugar de a la clase de repositorio. También creará pruebas unitarias para la lógica de validación. En el tercer tutorial de esta serie, agregará funcionalidad de ordenación y filtrado a la aplicación.
Las páginas que cree en este tutorial funcionan con el Departments
conjunto de entidades del modelo de datos que creó en la Serie de tutoriales introducción.
Actualización de la base de datos y el modelo de datos
Para comenzar este tutorial, realizará dos cambios en la base de datos, que requieren los cambios correspondientes en el modelo de datos que creó en los tutoriales Introducción a Entity Framework y Web Forms. En uno de esos tutoriales, ha realizado cambios en el diseñador manualmente para sincronizar el modelo de datos con la base de datos después de un cambio en la base de datos. En este tutorial, usará la herramienta Actualizar modelo desde base de datos del diseñador para actualizar el modelo de datos automáticamente.
Agregar una relación a la base de datos
En Visual Studio, abra la aplicación web Contoso University que creó en la serie de tutoriales Introducción a Entity Framework y Web Forms y, a continuación, abra el SchoolDiagram
diagrama de base de datos.
Si observa la Department
tabla en el diagrama de base de datos, verá que tiene una Administrator
columna. Esta columna es una clave externa para la tabla Person
, pero no se define ninguna relación de clave externa en la base de datos. Debe crear la relación y actualizar el modelo de datos para que Entity Framework pueda controlar automáticamente esta relación.
En el diagrama de base de datos, haga clic con el botón derecho en la tabla Department
y seleccione Relaciones.
En el cuadro Relaciones de clave externa, haga clic en Agregar y, a continuación, haga clic en los puntos suspensivos de Tablas y especificaciones de columnas.
En el cuadro de diálogo Tablas y columnas, establezca la tabla y el campo de clave principal en Person
y PersonID
, y establezca la tabla y el campo de clave externa en Department
y Administrator
. (Al hacerlo, el nombre de la relación cambiará de FK_Department_Department
a FK_Department_Person
.)
Haga clic en Aceptar en el cuadro Tablas y columnas, haga clic en Cerrar en el cuadro Relaciones de clave externa y guarde los cambios. Si se le pregunta si desea guardar las tablas de Person
y Department
, haga clic en Sí.
Nota:
Si ha eliminado Person
filas que corresponden a datos que ya están en la columna Administrator
, no podrá guardar este cambio. En ese caso, use el editor de tablas en Explorador de servidores para asegurarse de que el valorAdministrator
en cada fila Department
contiene el identificador de un registro que realmente existe en la tabla Person
.
Después de guardar el cambio, no podrá eliminar una fila de la tabla Person
si esa persona es administrador de departamento. En una aplicación de producción, proporcionaría un mensaje de error específico cuando una restricción de base de datos impide una eliminación o especificaría una eliminación en cascada. Para obtener un ejemplo de cómo especificar una eliminación en cascada, vea Entity Framework y ASP.NET – Introducción a la parte 2.
Agregar una vista a la base de datos
En la nueva página de Departments.aspx que va a crear, quiere proporcionar una lista desplegable de instructores, con nombres en formato "last, first" para que los usuarios puedan seleccionar administradores de departamento. Para que sea más fácil hacerlo, creará una vista en la base de datos. La vista constará solo de los datos necesarios en la lista desplegable: el nombre completo (con el formato correcto) y la clave de registro.
En Explorador de servidores, expanda School.mdf, haga clic con el botón derecho en la carpeta Vistas y seleccione Agregar nueva vista.
Haga clic en Cerrar cuando aparezca el cuadro de diálogo Agregar tabla y pegue la siguiente instrucción SQL en el panel SQL:
SELECT LastName + ',' + FirstName AS FullName, PersonID
FROM dbo.Person
WHERE (HireDate IS NOT NULL)
Guarde la vista como vInstructorName
.
Actualización del modelo de datos
En la carpeta DAL, abra el archivo SchoolModel.edmx , haga clic con el botón derecho en la superficie de diseño y seleccione Actualizar modelo en base de datos .
En el cuadro de diálogo Elegir los objetos de base de datos, seleccione la pestaña Agregar y seleccione la vista que acaba de crear.
Haga clic en Finalizar
En el diseñador, verá que la herramienta creó una entidad vInstructorName
y una nueva asociación entre las entidades Department
y Person
.
Nota:
En las ventanas Salida y Lista de errores, es posible que vea un mensaje de advertencia que le informa de que la herramienta creó automáticamente una clave principal para la nueva vInstructorName
vista. Este es el comportamiento esperado.
Al hacer referencia a la nueva entidad de vInstructorName
en el código, no desea usar la convención de base de datos de prefijo de una "v" en minúsculas. Por lo tanto, cambiará el nombre de la entidad y del conjunto de entidades en el modelo.
Abra el Explorador de modelos. Verá vInstructorName
que aparece como un tipo de entidad y una vista.
En SchoolModel (no SchoolModel.Store), haga clic con el botón derecho en vInstructorName y seleccione Propiedades. En la ventana Propiedades, cambie la propiedad Name a "InstructorName" y cambie la propiedad Entity Set Name a "InstructorNames".
Guarde y cierre el modelo de datos y vuelva a generar el proyecto.
Usar una clase de repositorio y un control ObjectDataSource
Cree un nuevo archivo de clase en la carpeta DAL, asígnelo el nombre SchoolRepository.cs, y reemplace el código existente por el código siguiente:
using System;
using System.Collections.Generic;
using System.Linq;
using ContosoUniversity.DAL;
namespace ContosoUniversity.DAL
{
public class SchoolRepository : IDisposable
{
private SchoolEntities context = new SchoolEntities();
public IEnumerable<Department> GetDepartments()
{
return context.Departments.Include("Person").ToList();
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
context.Dispose();
}
}
this.disposedValue = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Este código proporciona un único método GetDepartments
que devuelve todas las entidades del conjunto de entidades Departments
. Dado que sabe que va a acceder a la propiedad de navegación Person
para cada fila devuelta, especifique la carga diligente de esa propiedad mediante el método Include
. La clase también implementa la interfaz IDisposable
para asegurarse de que la conexión de base de datos se libera cuando se elimina el objeto.
Nota:
Una práctica habitual es crear una clase de repositorio para cada tipo de entidad. En este tutorial, se usa una clase de repositorio para varios tipos de entidad. Para obtener más información sobre el patrón de repositorio, consulte las entradas del blog del equipo de Entity Framework y blog de Julie Lerman.
El GetDepartments
método devuelve un IEnumerable
objeto en lugar de un IQueryable
objeto para asegurarse de que la colección devuelta se puede usar incluso después de eliminar el propio objeto del repositorio. Un IQueryable
objeto puede provocar el acceso a la base de datos cada vez que se tiene acceso a ella, pero el objeto de repositorio podría eliminarse en el momento en que un control de entrada de datos intenta representar los datos. Podría devolver otro tipo de colección, como un objeto IList
en lugar de un objeto IEnumerable
. Sin embargo, devolver un objeto IEnumerable
garantiza que puede realizar tareas típicas de procesamiento de listas de solo lectura, como bucles foreach
y consultas LINQ, pero no puede agregar o quitar elementos de la colección, lo que podría implicar que dichos cambios se conservarían en la base de datos.
Cree una página de Departments.aspx que use la página maestraSite.Master y agregue el marcado siguiente en el Content
control denominado Content2
:
<h2>Departments</h2>
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.Department"
SelectMethod="GetDepartments" >
</asp:ObjectDataSource>
<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="DepartmentsObjectDataSource" >
<Columns>
<asp:CommandField ShowEditButton="True" ShowDeleteButton="True"
ItemStyle-VerticalAlign="Top">
</asp:CommandField>
<asp:DynamicField DataField="Name" HeaderText="Name" SortExpression="Name" ItemStyle-VerticalAlign="Top" />
<asp:DynamicField DataField="Budget" HeaderText="Budget" SortExpression="Budget" ItemStyle-VerticalAlign="Top" />
<asp:DynamicField DataField="StartDate" HeaderText="Start Date" ItemStyle-VerticalAlign="Top" />
<asp:TemplateField HeaderText="Administrator" SortExpression="Person.LastName" ItemStyle-VerticalAlign="Top" >
<ItemTemplate>
<asp:Label ID="AdministratorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
<asp:Label ID="AdministratorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Este marcado crea un control ObjectDataSource
que usa la clase de repositorio que acaba de crear y un control GridView
para mostrar los datos. El GridView
control especifica comandos Editar y Eliminar, pero aún no ha agregado código para admitirlos.
Varias columnas usan controles DynamicField
para que pueda aprovechar las ventajas del formato automático de datos y la funcionalidad de validación. Para que funcionen, tendrá que llamar al método EnableDynamicData
en el controlador de eventos Page_Init
. (DynamicControl
los controles no se usan en el campo Administrator
porque no funcionan con las propiedades de navegación.)
Los atributos Vertical-Align="Top"
serán importantes más adelante cuando agregue una columna que tenga un control GridView
anidado a la cuadrícula.
Abra el archivo Departments.aspx.cs y agregue la siguiente instrucción using
:
using ContosoUniversity.DAL;
A continuación, agregue el siguiente controlador para el evento Init
de la página:
protected void Page_Init(object sender, EventArgs e)
{
DepartmentsGridView.EnableDynamicData(typeof(Department));
}
En la carpeta DAL, cree un nuevo archivo de clase denominado Department.cs y reemplace el código existente por el código siguiente:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.DAL
{
[MetadataType(typeof(DepartmentMetaData))]
public partial class Department
{
}
public class DepartmentMetaData
{
[DataType(DataType.Currency)]
[Range(0, 1000000, ErrorMessage = "Budget must be less than $1,000,000.00")]
public Decimal Budget { get; set; }
[DisplayFormat(DataFormatString="{0:d}",ApplyFormatInEditMode=true)]
public DateTime StartDate { get; set; }
}
}
Este código agrega metadatos al modelo de datos. Especifica que la propiedad Budget
de la entidad Department
representa realmente la moneda, aunque su tipo de datos es Decimal
, y especifica que el valor debe estar comprendido entre 0 y 1.000.000.00. USD. También especifica que la propiedad StartDate
debe tener el formato de fecha en el formato mm/dd/aaaa.
Ejecute la página Departments.aspx.
Tenga en cuenta que, aunque no especificó una cadena de formato en el marcado de página de Departments.aspx para las columnas Presupuesto o Fecha de inicio, los DynamicField
controles han aplicado el formato de moneda y fecha predeterminados mediante los metadatos proporcionados en el archivo Department.cs.
Agregar funcionalidad de inserción y eliminación
Abra SchoolRepository.cs, agregue el código siguiente para crear un método Insert
y un método Delete
. El código también incluye un método denominado GenerateDepartmentID
que calcula el siguiente valor de clave de registro disponible para que lo use el método Insert
. Esto es necesario porque la base de datos no está configurada para calcular esto automáticamente para la tabla Department
.
public void InsertDepartment(Department department)
{
try
{
department.DepartmentID = GenerateDepartmentID();
context.Departments.AddObject(department);
context.SaveChanges();
}
catch (Exception ex)
{
//Include catch blocks for specific exceptions first,
//and handle or log the error as appropriate in each.
//Include a generic catch block like this one last.
throw ex;
}
}
public void DeleteDepartment(Department department)
{
try
{
context.Departments.Attach(department);
context.Departments.DeleteObject(department);
context.SaveChanges();
}
catch (Exception ex)
{
//Include catch blocks for specific exceptions first,
//and handle or log the error as appropriate in each.
//Include a generic catch block like this one last.
throw ex;
}
}
private Int32 GenerateDepartmentID()
{
Int32 maxDepartmentID = 0;
var department = (from d in GetDepartments()
orderby d.DepartmentID descending
select d).FirstOrDefault();
if (department != null)
{
maxDepartmentID = department.DepartmentID + 1;
}
return maxDepartmentID;
}
Método Attach
El método DeleteDepartment
llama al método Attach
para volver a establecer el vínculo que se mantiene en el administrador de estado de objetos del contexto de objeto entre la entidad en memoria y la fila de la base de datos que representa. Esto debe ocurrir antes de que el método llame al método SaveChanges
.
El término contexto de objeto hace referencia a la clase Entity Framework que deriva de la clase ObjectContext
que se usa para tener acceso a los conjuntos de entidades y entidades. En el código de este proyecto, la clase se denomina SchoolEntities
, y una instancia de él siempre se denomina context
. El administrador de estado de objetos del contexto de objeto es una clase que deriva de la clase ObjectStateManager
. El contacto del objeto usa el administrador de estado de objeto para almacenar objetos de entidad y realizar un seguimiento de si cada uno está sincronizado con su fila o filas de tabla correspondientes en la base de datos.
Al leer una entidad, el contexto del objeto lo almacena en el administrador de estado de objetos y realiza un seguimiento de si esa representación del objeto está sincronizada con la base de datos. Por ejemplo, si cambia un valor de propiedad, se establece una marca para indicar que la propiedad que ha cambiado ya no está sincronizada con la base de datos. A continuación, cuando se llama al método SaveChanges
, el contexto del objeto sabe qué hacer en la base de datos porque el administrador de estado de objetos sabe exactamente lo que es diferente entre el estado actual de la entidad y el estado de la base de datos.
Sin embargo, este proceso normalmente no funciona en una aplicación web, ya que la instancia de contexto de objeto que lee una entidad, junto con todo lo que hay en su administrador de estado de objetos, se elimina después de representar una página. La instancia de contexto de objeto que debe aplicar cambios es una nueva que se crea una instancia para el procesamiento de postback. En el caso del DeleteDepartment
método, el ObjectDataSource
control vuelve a crear la versión original de la entidad a partir de valores en estado de vista, pero esta entidad creada de nuevo Department
no existe en el administrador de estado de objetos. Si llamó al DeleteObject
método en esta entidad de nueva creación, se producirá un error en la llamada porque el contexto del objeto no sabe si la entidad está sincronizada con la base de datos. Sin embargo, al llamar al método Attach
se vuelve a establecer el mismo seguimiento entre la entidad que se ha vuelto a crear y los valores de la base de datos que se realizó originalmente cuando la entidad se leyó en una instancia anterior del contexto del objeto.
Hay ocasiones en las que no desea que el contexto del objeto realice un seguimiento de las entidades en el administrador de estado de objetos y puede establecer marcas para evitar que lo haga. Algunos ejemplos de esto se muestran en tutoriales posteriores de esta serie.
El método SaveChanges
Esta clase de repositorio simple ilustra los principios básicos de cómo realizar operaciones CRUD. En este ejemplo, se llama al método SaveChanges
inmediatamente después de cada actualización. En una aplicación de producción, es posible que desee llamar al método SaveChanges
desde un método independiente para proporcionarle más control sobre cuándo se actualiza la base de datos. (Al final del siguiente tutorial encontrará un vínculo a una notas del producto que describe la unidad de patrón de trabajo que es un enfoque para coordinar las actualizaciones relacionadas). Observe también que en el ejemplo, el método DeleteDepartment
no incluye código para controlar conflictos de simultaneidad; código para ello que se agregará en un tutorial posterior de esta serie.
Recuperar nombres de instructor para seleccionar al insertar
Los usuarios deben poder seleccionar un administrador de una lista de instructores en una lista desplegable al crear nuevos departamentos. Por lo tanto, agregue el código siguiente a SchoolRepository.cs para crear un método para recuperar la lista de instructores mediante la vista que creó anteriormente:
public IEnumerable<InstructorName> GetInstructorNames()
{
return context.InstructorNames.OrderBy("it.FullName").ToList();
}
Crear una página para insertar departamentos
Cree una página de DepartmentsAdd.aspx que use la página de Site.Master y agregue el marcado siguiente en el Content
control denominado Content2
:
<h2>Departments</h2>
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository" DataObjectTypeName="ContosoUniversity.DAL.Department"
InsertMethod="InsertDepartment" >
</asp:ObjectDataSource>
<asp:DetailsView ID="DepartmentsDetailsView" runat="server"
DataSourceID="DepartmentsObjectDataSource" AutoGenerateRows="False"
DefaultMode="Insert" OnItemInserting="DepartmentsDetailsView_ItemInserting">
<Fields>
<asp:DynamicField DataField="Name" HeaderText="Name" />
<asp:DynamicField DataField="Budget" HeaderText="Budget" />
<asp:DynamicField DataField="StartDate" HeaderText="Start Date" />
<asp:TemplateField HeaderText="Administrator">
<InsertItemTemplate>
<asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
SelectMethod="GetInstructorNames" >
</asp:ObjectDataSource>
<asp:DropDownList ID="InstructorsDropDownList" runat="server"
DataSourceID="InstructorsObjectDataSource"
DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init">
</asp:DropDownList>
</InsertItemTemplate>
</asp:TemplateField>
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
<asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server"
ShowSummary="true" DisplayMode="BulletList" />
Este marcado crea dos ObjectDataSource
controles, uno para insertar nuevas Department
entidades y otro para recuperar nombres de instructor para el DropDownList
control que se usa para seleccionar administradores de departamento. El marcado crea un DetailsView
control para escribir nuevos departamentos y especifica un controlador para el evento del ItemInserting
control para que pueda establecer el Administrator
valor de clave externa. Al final, se trata de un control ValidationSummary
para mostrar mensajes de error.
Abra DepartmentsAdd.aspx.cs y agregue la siguiente instrucción using
:
using ContosoUniversity.DAL;
Agregue la siguiente variable de clase y métodos:
private DropDownList administratorsDropDownList;
protected void Page_Init(object sender, EventArgs e)
{
DepartmentsDetailsView.EnableDynamicData(typeof(Department));
}
protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
administratorsDropDownList = sender as DropDownList;
}
protected void DepartmentsDetailsView_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
e.Values["Administrator"] = administratorsDropDownList.SelectedValue;
}
El método Page_Init
habilita la funcionalidad Datos dinámicos. El controlador del DropDownList
evento del Init
control guarda una referencia al control y el controlador del DetailsView
evento del Inserting
control usa esa referencia para obtener el PersonID
valor del instructor seleccionado y actualizar la Administrator
propiedad de clave externa de la Department
entidad.
Ejecute la página, agregue información para un nuevo departamento y haga clic en el vínculo Insertar.
Escriba los valores de otro nuevo departamento. Escriba un número mayor que 1.000.000.00 en el campo Presupuesto y la pestaña en el campo siguiente. Aparece un asterisco en el campo y, si mantiene el puntero del mouse sobre él, puede ver el mensaje de error que escribió en los metadatos de ese campo.
Haga clic en Insertar, y verá el mensaje de error que muestra el control ValidationSummary
en la parte inferior de la página.
A continuación, cierre el explorador y abra la página Departments.aspx. Agregue la funcionalidad de eliminación a la página de Departments.aspx agregando un atributo DeleteMethod
al control ObjectDataSource
y un atributo DataKeyNames
al control GridView
. Ahora, las etiquetas de apertura de estos controles se asemejarán al ejemplo siguiente:
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.Department"
SelectMethod="GetDepartments"
DeleteMethod="DeleteDepartment" >
<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID" >
Ejecute la página.
Elimine el departamento que agregó al ejecutar la página de DepartmentsAdd.aspx.
Adición de la funcionalidad de actualización
Abra SchoolRepository.cs y agregue el siguiente método Update
:
public void UpdateDepartment(Department department, Department origDepartment)
{
try
{
context.Departments.Attach(origDepartment);
context.ApplyCurrentValues("Departments", department);
context.SaveChanges();
}
catch (Exception ex)
{
//Include catch blocks for specific exceptions first,
//and handle or log the error as appropriate in each.
//Include a generic catch block like this one last.
throw ex;
}
}
Al hacer clic en Actualizar en la página Departments.aspx, el ObjectDataSource
control crea dos Department
entidades para pasar al UpdateDepartment
método. Uno contiene los valores originales que se han almacenado en estado de vista y el otro contiene los nuevos valores especificados en el control GridView
. El código del método UpdateDepartment
pasa la entidad Department
que tiene los valores originales al método Attach
para establecer el seguimiento entre la entidad y lo que hay en la base de datos. A continuación, el código pasa la entidad Department
que tiene los nuevos valores al método ApplyCurrentValues
. El contexto del objeto compara los valores antiguos y nuevos. Si un nuevo valor es diferente de un valor anterior, el contexto del objeto cambia el valor de propiedad. A continuación, el método SaveChanges
actualiza solo las columnas modificadas de la base de datos. (Sin embargo, si la función de actualización de esta entidad se asignaba a un procedimiento almacenado, toda la fila se actualizaría independientemente de qué columnas se cambiaran.)
Abra el archivo Departments.aspx y agregue los atributos siguientes al control DepartmentsObjectDataSource
:
UpdateMethod="UpdateDepartment"
ConflictDetection="CompareAllValues"
Esto hace que los valores antiguos se almacenen en estado de vista para que se puedan comparar con los nuevos valores del métodoUpdate
.OldValuesParameterFormatString="orig{0}"
Esto informa al control de que el nombre del parámetro de valores originales esorigDepartment
.
El marcado de la etiqueta de apertura del control ObjectDataSource
ahora es similar al ejemplo siguiente:
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.DAL.SchoolRepository"
DataObjectTypeName="ContosoUniversity.DAL.Department"
SelectMethod="GetDepartments" DeleteMethod="DeleteDepartment"
UpdateMethod="UpdateDepartment"
ConflictDetection="CompareAllValues"
OldValuesParameterFormatString="orig{0}" >
Agregue un atributo OnRowUpdating="DepartmentsGridView_RowUpdating"
al control GridView
. Lo usará para establecer el valor de la propiedad Administrator
en función de la fila que el usuario selecciona en una lista desplegable. La etiqueta de apertura GridView
ahora es similar al ejemplo siguiente:
<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID"
OnRowUpdating="DepartmentsGridView_RowUpdating">
Agregue un control EditItemTemplate
para la columna Administrator
al control GridView
, inmediatamente después del control ItemTemplate
para esa columna:
<EditItemTemplate>
<asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
SelectMethod="GetInstructorNames" TypeName="ContosoUniversity.DAL.SchoolRepository">
</asp:ObjectDataSource>
<asp:DropDownList ID="InstructorsDropDownList" runat="server" DataSourceID="InstructorsObjectDataSource"
SelectedValue='<%# Eval("Administrator") %>'
DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init" >
</asp:DropDownList>
</EditItemTemplate>
Este control EditItemTemplate
es similar al control InsertItemTemplate
de la página de DepartmentsAdd.aspx. La diferencia es que el valor inicial del control se establece mediante el SelectedValue
atributo.
Antes del control GridView
, agregue un control ValidationSummary
como hizo en la página DepartmentsAdd.aspx.
<asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server"
ShowSummary="true" DisplayMode="BulletList" />
Abra Departments.aspx.cs e inmediatamente después de la declaración de clase parcial, agregue el código siguiente para crear un campo privado para hacer referencia al control DropDownList
:
private DropDownList administratorsDropDownList;
A continuación, agregue controladores para el evento DropDownList
del control Init
y el evento GridView
del control RowUpdating
:
protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
administratorsDropDownList = sender as DropDownList;
}
protected void DepartmentsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
e.NewValues["Administrator"] = administratorsDropDownList.SelectedValue;
}
El controlador del evento Init
guarda una referencia al control DropDownList
en el campo de clase. El controlador del evento RowUpdating
usa la referencia para obtener el valor especificado por el usuario y aplicarlo a la propiedad Administrator
de la entidad Department
.
Use la página DepartmentsAdd.aspx para agregar un nuevo departamento, ejecute la página de Departments.aspx y haga clic en Editar de la fila que agregó.
Nota:
No podrá editar las filas que no ha agregado (es decir, que ya estaban en la base de datos), debido a datos no válidos en la base de datos; los administradores de las filas que se crearon con la base de datos son estudiantes. Si intenta editar uno de ellos, obtendrá una página de error que notifica un error como 'InstructorsDropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.
Si escribe un presupuesto no válido y, a continuación, hace clic en Actualizar, verá el mismo asterisco y mensaje de error que vio en la página de Departments.aspx.
Cambie un valor de campo o seleccione otro administrador y haga clic en Actualizar. Se muestra el cambio.
Esto completa la introducción al uso del control ObjectDataSource
para las operaciones CRUD básicas (crear, leer, actualizar, eliminar) con Entity Framework. Ha creado una aplicación sencilla de n niveles, pero la capa de lógica de negocios todavía está estrechamente acoplada a la capa de acceso a datos, lo que complica las pruebas unitarias automatizadas. En el siguiente tutorial, verá cómo implementar el patrón de repositorio para facilitar las pruebas unitarias.