Introdução com o Entity Framework 4.0 Database First e ASP.NET 4 Web Forms – Parte 4

por Tom Dykstra

O aplicativo Web de exemplo da Contoso University demonstra como criar aplicativos ASP.NET Web Forms usando o Entity Framework 4.0 e o Visual Studio 2010. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial da série

No tutorial anterior, você usou o EntityDataSource controle para filtrar, classificar e agrupar dados. Neste tutorial, você exibirá e atualizará os dados relacionados.

Você criará a página Instrutores que mostra uma lista de instrutores. Ao selecionar um instrutor, você verá uma lista de cursos ministrados por esse instrutor. Ao selecionar um curso, você verá detalhes do curso e uma lista de alunos inscritos no curso. Você pode editar o nome do instrutor, a data de contratação e a atribuição do escritório. A atribuição do office é um conjunto de entidades separado que você acessa por meio de uma propriedade de navegação.

Você pode vincular master dados para detalhar dados na marcação ou no código. Nesta parte do tutorial, você usará os dois métodos.

Imagem01

Crie uma nova página da Web chamada Instructors.aspx que usa a página master Site.Master e adicione a seguinte marcação ao Content controle chamado Content2:

<h2>Instructors</h2>
    <div>
        <asp:EntityDataSource ID="InstructorsEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False" 
            EntitySetName="People"
            Where="it.HireDate is not null" Include="OfficeAssignment" EnableUpdate="True">
        </asp:EntityDataSource>
    </div>

Essa marcação cria um EntityDataSource controle que seleciona instrutores e habilita atualizações. O div elemento configura a marcação a ser renderizada à esquerda para que você possa adicionar uma coluna à direita mais tarde.

Entre a EntityDataSource marcação e a marca de fechamento </div> , adicione a seguinte marcação que cria um GridView controle e um Label controle que você usará para mensagens de erro:

<asp:GridView ID="InstructorsGridView" runat="server" AllowPaging="True" AllowSorting="True"
            AutoGenerateColumns="False" DataKeyNames="PersonID" DataSourceID="InstructorsEntityDataSource"
            OnSelectedIndexChanged="InstructorsGridView_SelectedIndexChanged" 
            SelectedRowStyle-BackColor="LightGray" 
            onrowupdating="InstructorsGridView_RowUpdating">
            <Columns>
                <asp:CommandField ShowSelectButton="True" ShowEditButton="True" />
                <asp:TemplateField HeaderText="Name" SortExpression="LastName">
                    <ItemTemplate>
                        <asp:Label ID="InstructorLastNameLabel" runat="server" Text='<%# Eval("LastName") %>'></asp:Label>,
                        <asp:Label ID="InstructorFirstNameLabel" runat="server" Text='<%# Eval("FirstMidName") %>'></asp:Label>
                    </ItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="InstructorLastNameTextBox" runat="server" Text='<%# Bind("FirstMidName") %>' Width="7em"></asp:TextBox>
                        <asp:TextBox ID="InstructorFirstNameTextBox" runat="server" Text='<%# Bind("LastName") %>' Width="7em"></asp:TextBox>
                    </EditItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Hire Date" SortExpression="HireDate">
                    <ItemTemplate>
                        <asp:Label ID="InstructorHireDateLabel" runat="server" Text='<%# Eval("HireDate", "{0:d}") %>'></asp:Label>
                    </ItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="InstructorHireDateTextBox" runat="server" Text='<%# Bind("HireDate", "{0:d}") %>' Width="7em"></asp:TextBox>
                    </EditItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Office Assignment" SortExpression="OfficeAssignment.Location">
                    <ItemTemplate>
                        <asp:Label ID="InstructorOfficeLabel" runat="server" Text='<%# Eval("OfficeAssignment.Location") %>'></asp:Label>
                    </ItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="InstructorOfficeTextBox" runat="server" 
                        Text='<%# Eval("OfficeAssignment.Location") %>' Width="7em"
                        oninit="InstructorOfficeTextBox_Init"></asp:TextBox>
                    </EditItemTemplate>
                </asp:TemplateField>
            </Columns>
            <SelectedRowStyle BackColor="LightGray"></SelectedRowStyle>
        </asp:GridView>
        <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false" ViewStateMode="Disabled"></asp:Label>

Esse GridView controle habilita a seleção de linhas, realça a linha selecionada com uma cor de tela de fundo cinza claro e especifica manipuladores (que você criará posteriormente) para os SelectedIndexChanged eventos e Updating . Ele também especifica PersonID para a DataKeyNames propriedade , para que o valor da chave da linha selecionada possa ser passado para outro controle que você adicionará posteriormente.

A última coluna contém a atribuição de escritório do instrutor, que é armazenada em uma propriedade de navegação da Person entidade porque ela vem de uma entidade associada. Observe que o EditItemTemplate elemento especifica Eval em vez de Bind, porque o GridView controle não pode associar diretamente às propriedades de navegação para atualizá-las. Você atualizará a atribuição do escritório em código. Para fazer isso, você precisará de uma referência ao TextBox controle e obterá e salvará isso no TextBox evento do Init controle.

Seguindo o GridView controle está um Label controle usado para mensagens de erro. A propriedade do controle é falsee o estado de Visible exibição está desativado, para que o rótulo apareça somente quando o código o tornar visível em resposta a um erro.

Abra o arquivo Instructors.aspx.cs e adicione a seguinte using instrução:

using ContosoUniversity.DAL;

Adicione um campo de classe privada imediatamente após a declaração de nome de classe parcial para manter uma referência à caixa de texto de atribuição de escritório.

private TextBox instructorOfficeTextBox;

Adicione um stub para o manipulador de eventos para o SelectedIndexChanged qual você adicionará código posteriormente. Adicione também um manipulador para o evento do controle de atribuição TextBox do Init office para que você possa armazenar uma referência ao TextBox controle. Você usará essa referência para obter o valor inserido pelo usuário para atualizar a entidade associada à propriedade de navegação.

protected void InstructorsGridView_SelectedIndexChanged(object sender, EventArgs e)
{
}

protected void InstructorOfficeTextBox_Init(object sender, EventArgs e)
{
    instructorOfficeTextBox = sender as TextBox;
}

Você usará o GridView evento do Updating controle para atualizar a Location propriedade da entidade associada OfficeAssignment . Adicione o seguinte manipulador para o Updating evento:

protected void InstructorsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    using (var context = new SchoolEntities())
    {
        var instructorBeingUpdated = Convert.ToInt32(e.Keys[0]);
        var officeAssignment = (from o in context.OfficeAssignments
                                where o.InstructorID == instructorBeingUpdated
                                select o).FirstOrDefault();

        try
        {
            if (String.IsNullOrWhiteSpace(instructorOfficeTextBox.Text) == false)
            {
                if (officeAssignment == null)
                {
                    context.OfficeAssignments.AddObject(OfficeAssignment.CreateOfficeAssignment(instructorBeingUpdated, instructorOfficeTextBox.Text, null));
                }
                else
                {
                    officeAssignment.Location = instructorOfficeTextBox.Text;
                }
            }
            else
            {
                if (officeAssignment != null)
                {
                    context.DeleteObject(officeAssignment);
                }
            }
            context.SaveChanges();
        }
        catch (Exception)
        {
            e.Cancel = true;
            ErrorMessageLabel.Visible = true;
            ErrorMessageLabel.Text = "Update failed.";
            //Add code to log the error.
        }
    }
}

Esse código é executado quando o usuário clica em Atualizar em uma GridView linha. O código usa LINQ to Entities para recuperar a OfficeAssignment entidade associada à entidade atualPerson, usando a PersonID da linha selecionada do argumento de evento.

Em seguida, o código executa uma das seguintes ações dependendo do valor no InstructorOfficeTextBox controle:

  • Se a caixa de texto tiver um valor e não OfficeAssignment houver nenhuma entidade a ser atualizada, ela criará uma.
  • Se a caixa de texto tiver um valor e houver uma OfficeAssignment entidade, ela atualizará o valor da Location propriedade.
  • Se a caixa de texto estiver vazia e uma OfficeAssignment entidade existir, ela excluirá a entidade.

Depois disso, ele salva as alterações no banco de dados. Se ocorrer uma exceção, ela exibirá uma mensagem de erro.

Execute a página.

Imagem02

Clique em Editar e todos os campos são alterados para caixas de texto.

Imagem03

Altere qualquer um desses valores, incluindo Atribuição do Office. Clique em Atualizar e você verá as alterações refletidas na lista.

Cada instrutor pode ministrar um ou mais cursos, portanto, você adicionará um EntityDataSource controle e um GridView controle para listar os cursos associados a qualquer instrutor selecionado no controle de instrutores GridView . Para criar um título e o EntityDataSource controle para entidades de cursos, adicione a seguinte marcação entre o controle de mensagem Label de erro e a marca de fechamento </div> :

<h3>Courses Taught</h3>
        <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False" 
            EntitySetName="Courses" 
            Where="@PersonID IN (SELECT VALUE instructor.PersonID FROM it.People AS instructor)">
            <WhereParameters>
                <asp:ControlParameter ControlID="InstructorsGridView" Type="Int32" Name="PersonID" PropertyName="SelectedValue" />
            </WhereParameters>
        </asp:EntityDataSource>

O Where parâmetro contém o valor do PersonID do instrutor cuja linha está selecionada no InstructorsGridView controle . A Where propriedade contém um comando de subseleção que obtém todas as entidades associadas Person da propriedade de navegação de People uma Course entidade e seleciona a Course entidade somente se uma das entidades associadas Person contiver o valor selecionadoPersonID.

Para criar o GridView controle., adicione a seguinte marcação imediatamente após o CoursesEntityDataSource controle (antes da marca de fechamento </div> ):

<asp:GridView ID="CoursesGridView" runat="server" 
            DataSourceID="CoursesEntityDataSource"
            AllowSorting="True" AutoGenerateColumns="False"
            SelectedRowStyle-BackColor="LightGray" 
            DataKeyNames="CourseID">
            <EmptyDataTemplate>
                <p>No courses found.</p>
            </EmptyDataTemplate>
            <Columns>
                <asp:CommandField ShowSelectButton="True" />
                <asp:BoundField DataField="CourseID" HeaderText="ID" ReadOnly="True" SortExpression="CourseID" />
                <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
                <asp:TemplateField HeaderText="Department" SortExpression="DepartmentID">
                    <ItemTemplate>
                        <asp:Label ID="GridViewDepartmentLabel" runat="server" Text='<%# Eval("Department.Name") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

Como nenhum curso será exibido se nenhum instrutor for selecionado, um EmptyDataTemplate elemento será incluído.

Execute a página.

Imagem04

Selecione um instrutor que tenha um ou mais cursos atribuídos e o curso ou cursos aparecem na lista. (Observação: embora o esquema de banco de dados permita vários cursos, nos dados de teste fornecidos com o banco de dados nenhum instrutor realmente tem mais de um curso. Você pode adicionar cursos ao banco de dados por conta própria usando a janela Servidor Explorer ou a página CoursesAdd.aspx, que você adicionará em um tutorial posterior.)

Imagem05

O CoursesGridView controle mostra apenas alguns campos de curso. Para exibir todos os detalhes de um curso, você usará um DetailsView controle para o curso selecionado pelo usuário. Em Instructors.aspx, adicione a marcação a seguir após a marca de fechamento </div> (certifique-se de colocar essa marcação após a marca div de fechamento, não antes dela):

<div>
        <h3>Course Details</h3>
        <asp:EntityDataSource ID="CourseDetailsEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False" 
            EntitySetName="Courses"
            AutoGenerateWhereClause="False" Where="it.CourseID = @CourseID" Include="Department,OnlineCourse,OnsiteCourse,StudentGrades.Person"
            OnSelected="CourseDetailsEntityDataSource_Selected">
            <WhereParameters>
                <asp:ControlParameter ControlID="CoursesGridView" Type="Int32" Name="CourseID" PropertyName="SelectedValue" />
            </WhereParameters>
        </asp:EntityDataSource>
        <asp:DetailsView ID="CourseDetailsView" runat="server" AutoGenerateRows="False"
            DataSourceID="CourseDetailsEntityDataSource">
            <EmptyDataTemplate>
                <p>
                    No course selected.</p>
            </EmptyDataTemplate>
            <Fields>
                <asp:BoundField DataField="CourseID" HeaderText="ID" ReadOnly="True" SortExpression="CourseID" />
                <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
                <asp:BoundField DataField="Credits" HeaderText="Credits" SortExpression="Credits" />
                <asp:TemplateField HeaderText="Department">
                    <ItemTemplate>
                        <asp:Label ID="DetailsViewDepartmentLabel" runat="server" Text='<%# Eval("Department.Name") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Location">
                    <ItemTemplate>
                        <asp:Label ID="LocationLabel" runat="server" Text='<%# Eval("OnsiteCourse.Location") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="URL">
                    <ItemTemplate>
                        <asp:Label ID="URLLabel" runat="server" Text='<%# Eval("OnlineCourse.URL") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
            </Fields>
        </asp:DetailsView>
    </div>

Essa marcação cria um EntityDataSource controle associado ao Courses conjunto de entidades. A Where propriedade seleciona um curso usando o CourseID valor da linha selecionada no controle de cursos GridView . A marcação especifica um manipulador para o Selected evento, que você usará posteriormente para exibir as notas dos alunos, que é outro nível mais baixo na hierarquia.

Em Instructors.aspx.cs, crie o stub a seguir para o CourseDetailsEntityDataSource_Selected método . (Você preencherá este stub posteriormente no tutorial; por enquanto, você precisará dele para que a página seja compilada e executada.)

protected void CourseDetailsEntityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
}

Execute a página.

Image06

Inicialmente, não há detalhes do curso porque nenhum curso está selecionado. Selecione um instrutor que tenha um curso atribuído e selecione um curso para ver os detalhes.

Imagem07

Por fim, você deseja mostrar todos os alunos inscritos e suas notas para o curso selecionado. Para fazer isso, você usará o Selected evento do EntityDataSource controle associado ao curso DetailsView.

Em Instructors.aspx, adicione a seguinte marcação após o DetailsView controle:

<h3>Student Grades</h3>
        <asp:ListView ID="GradesListView" runat="server">
            <EmptyDataTemplate>
                <p>No student grades found.</p>
            </EmptyDataTemplate>
            <LayoutTemplate>
                <table border="1" runat="server" id="itemPlaceholderContainer">
                    <tr runat="server">
                        <th runat="server">
                            Name
                        </th>
                        <th runat="server">
                            Grade
                        </th>
                    </tr>
                    <tr id="itemPlaceholder" runat="server">
                    </tr>
                </table>
            </LayoutTemplate>
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:Label ID="StudentLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>' />,
                        <asp:Label ID="StudentFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>' />
                    </td>
                    <td>
                        <asp:Label ID="StudentGradeLabel" runat="server" Text='<%# Eval("Grade") %>' />
                    </td>
                </tr>
            </ItemTemplate>
        </asp:ListView>

Essa marcação cria um ListView controle que exibe uma lista de alunos e suas notas para o curso selecionado. Nenhuma fonte de dados é especificada porque você associará o controle no código. O EmptyDataTemplate elemento fornece uma mensagem a ser exibida quando nenhum curso é selecionado. Nesse caso, não há alunos a serem exibidos. O LayoutTemplate elemento cria uma tabela HTML para exibir a lista e especifica as ItemTemplate colunas a serem exibidas. A ID do aluno e a nota do aluno são da StudentGrade entidade e o nome do aluno é da Person entidade que o Entity Framework disponibiliza na Person propriedade de navegação da StudentGrade entidade.

Em Instructors.aspx.cs, substitua o método stubbed-out CourseDetailsEntityDataSource_Selected pelo seguinte código:

protected void CourseDetailsEntityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
    var course = e.Results.Cast<Course>().FirstOrDefault();
    if (course != null)
    {
        var studentGrades = course.StudentGrades.ToList();
        GradesListView.DataSource = studentGrades;
        GradesListView.DataBind();
    }
}

O argumento de evento para esse evento fornece os dados selecionados na forma de uma coleção, que terá zero itens se nada for selecionado ou um item se uma Course entidade for selecionada. Se uma Course entidade for selecionada, o código usará o First método para converter a coleção em um único objeto. Em seguida, ele obtém StudentGrade entidades da propriedade de navegação, converte-as em uma coleção e associa o GradesListView controle à coleção.

Isso é suficiente para exibir notas, mas você deseja garantir que a mensagem no modelo de dados vazio seja exibida na primeira vez que a página for exibida e sempre que um curso não for selecionado. Para fazer isso, crie o seguinte método, que você chamará de dois locais:

private void ClearStudentGradesDataSource()
{
    var emptyStudentGradesList = new List<StudentGrade>();
    GradesListView.DataSource = emptyStudentGradesList;
    GradesListView.DataBind();
}

Chame esse novo método do Page_Load método para exibir o modelo de dados vazio na primeira vez que a página for exibida. E chame-o do InstructorsGridView_SelectedIndexChanged método porque esse evento é gerado quando um instrutor é selecionado, o que significa que novos cursos são carregados no controle de cursos GridView e nenhum é selecionado ainda. Aqui estão as duas chamadas:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        ClearStudentGradesDataSource();                
    }
}
protected void InstructorsGridView_SelectedIndexChanged(object sender, EventArgs e)
{
    ClearStudentGradesDataSource();
}

Execute a página.

Imagem08

Selecione um instrutor que tenha um curso atribuído e selecione o curso.

Imagem09

Agora você viu algumas maneiras de trabalhar com dados relacionados. No tutorial a seguir, você aprenderá a adicionar relações entre entidades existentes, como remover relações e como adicionar uma nova entidade que tenha uma relação com uma entidade existente.