Manipulando simultaneidade com o Entity Framework 4.0 em um aplicativo Web ASP.NET 4
por Tom Dykstra
Esta série de tutoriais se baseia no aplicativo Web da Contoso University criado pelo Introdução com a série de tutoriais do Entity Framework 4.0. Se você não concluiu os tutoriais anteriores, como ponto de partida para este tutorial, poderá baixar o aplicativo que teria criado. Você também pode baixar o aplicativo criado pela série de tutoriais completa. Se você tiver dúvidas sobre os tutoriais, poderá postá-los no fórum ASP.NET Entity Framework.
No tutorial anterior, você aprendeu a classificar e filtrar dados usando o ObjectDataSource
controle e o Entity Framework. Este tutorial mostra opções para lidar com simultaneidade em um aplicativo Web ASP.NET que usa o Entity Framework. Você criará uma nova página da Web dedicada à atualização das atribuições do escritório do instrutor. Você lidará com problemas de simultaneidade nessa página e na página Departamentos que você criou anteriormente.
Conflitos de simultaneidade
Um conflito de simultaneidade ocorre quando um usuário edita um registro e outro usuário edita o mesmo registro antes que a alteração do primeiro usuário seja gravada no banco de dados. Se você não configurar o Entity Framework para detectar esses conflitos, quem atualizar o banco de dados substituirá as alterações do outro usuário pela última vez. Em muitos aplicativos, esse risco é aceitável e você não precisa configurar o aplicativo para lidar com possíveis conflitos de simultaneidade. (Se houver poucos usuários ou poucas atualizações, ou se não for realmente crítico se algumas alterações forem substituídas, o custo da programação para simultaneidade poderá superar o benefício.) Se você não precisar se preocupar com conflitos de simultaneidade, ignore este tutorial; os dois tutoriais restantes da série não dependem de nada que você crie neste.
Simultaneidade pessimista (bloqueio)
Se o aplicativo precisar evitar a perda acidental de dados em cenários de simultaneidade, uma maneira de fazer isso será usar bloqueios de banco de dados. Isso é chamado de simultaneidade pessimista. Por exemplo, antes de ler uma linha de um banco de dados, você solicita um bloqueio para o acesso somente leitura ou de atualização. Se você bloquear uma linha para o acesso de atualização, nenhum outro usuário terá permissão para bloquear a linha para o acesso somente leitura ou de atualização, porque ele obterá uma cópia dos dados que estão sendo alterados. Se você bloquear uma linha para o acesso somente leitura, outros também poderão bloqueá-la para o acesso somente leitura, mas não para atualização.
O gerenciamento de bloqueios tem algumas desvantagens. Ele pode ser complexo de ser programado. Ele requer recursos significativos de gerenciamento de banco de dados e pode causar problemas de desempenho à medida que o número de usuários de um aplicativo aumenta (ou seja, ele não dimensiona bem). Por esses motivos, nem todos os sistemas de gerenciamento de banco de dados dão suporte à simultaneidade pessimista. O Entity Framework não fornece suporte interno para ele e este tutorial não mostra como implementá-lo.
Simultaneidade otimista
A alternativa à simultaneidade pessimista é a simultaneidade otimista. Simultaneidade otimista significa permitir que conflitos de simultaneidade ocorram e responder adequadamente se eles ocorrerem. Por exemplo, John executa a página Department.aspx , clica no link Editar para o departamento de Histórico e reduz o valor de Orçamento de US$ 1.000.000,00 para US$ 125.000,00. (John administra um departamento concorrente e quer liberar dinheiro para seu próprio departamento.)
Antes de João clicar em Atualizar, Jane executa a mesma página, clica no link Editar para o departamento de Histórico e, em seguida, altera o campo Data de Início de 1/10/2011 para 1/1/1999. (Jane administra o departamento de História e quer dar-lhe mais antiguidade.)
João clica em Atualizar primeiro e, em seguida, Jane clica em Atualizar. O navegador de Jane agora lista o valor do Orçamento como US$ 1.000.000,00, mas isso está incorreto porque o valor foi alterado por John para US$ 125.000,00.
Algumas das ações que você pode executar neste cenário incluem o seguinte:
Controle qual propriedade um usuário modificou e atualize apenas as colunas correspondentes no banco de dados. No cenário de exemplo, nenhum dado é perdido, porque propriedades diferentes foram atualizadas pelos dois usuários. Na próxima vez que alguém procurar no departamento de História, ele verá 1/1/1999 e $125.000,00.
Esse é o comportamento padrão no Entity Framework e pode reduzir substancialmente o número de conflitos que podem resultar em perda de dados. No entanto, esse comportamento não evitará a perda de dados se forem feitas alterações concorrentes na mesma propriedade de uma entidade. Além disso, esse comportamento nem sempre é possível; quando você mapeia procedimentos armazenados para um tipo de entidade, todas as propriedades de uma entidade são atualizadas quando quaisquer alterações na entidade são feitas no banco de dados.
Você pode deixar que a mudança de Jane substitua a mudança de John. Depois que Jane clicar em Atualizar, o valor do Orçamento volta para US$ 1.000.000,00. Isso é chamado de um cenário O cliente vence ou O último vence. (Os valores do cliente têm precedência sobre o que está no armazenamento de dados.)
Você pode impedir que a alteração de Jane seja atualizada no banco de dados. Normalmente, você exibiria uma mensagem de erro, mostraria a ela o estado atual dos dados e permitiria que ela reentresse nas alterações se ela ainda quisesse fazê-las. Você poderia automatizar ainda mais o processo salvando sua entrada e dando a ela a oportunidade de reaplicar sem ter que reenterá-lo. Isso é chamado de um cenário O armazenamento vence. (Os valores do armazenamento de dados têm precedência sobre os valores enviados pelo cliente.)
Detectando conflitos de simultaneidade
No Entity Framework, você pode resolve conflitos manipulando OptimisticConcurrencyException
exceções geradas pelo Entity Framework. Para saber quando gerar essas exceções, o Entity Framework precisa poder detectar conflitos. Portanto, é necessário configurar o banco de dados e o modelo de dados de forma adequada. Algumas opções para habilitar a detecção de conflitos incluem as seguintes:
No banco de dados, inclua uma coluna de tabela que pode ser usada para determinar quando uma linha foi alterada. Em seguida, você pode configurar o Entity Framework para incluir essa coluna na
Where
cláusula de SQLUpdate
ouDelete
comandos.Essa é a finalidade da
Timestamp
coluna naOfficeAssignment
tabela.O tipo de dados da
Timestamp
coluna também é chamadoTimestamp
de . No entanto, a coluna não contém um valor de data ou hora. Em vez disso, o valor é um número sequencial incrementado sempre que a linha é atualizada. Em umUpdate
comando ouDelete
, aWhere
cláusula inclui o valor originalTimestamp
. Se a linha que está sendo atualizada tiver sido alterada por outro usuário, o valor emTimestamp
será diferente do valor original, portanto, aWhere
cláusula não retornará nenhuma linha a ser atualizada. Quando o Entity Framework descobre que nenhuma linha foi atualizada pelo comando atualUpdate
ouDelete
(ou seja, quando o número de linhas afetadas é zero), ele interpreta isso como um conflito de simultaneidade.Configure o Entity Framework para incluir os valores originais de cada coluna na tabela na
Where
cláusula deUpdate
comandos eDelete
.Como na primeira opção, se alguma coisa na linha tiver sido alterada desde que a linha foi lida pela primeira vez, a
Where
cláusula não retornará uma linha para atualizar, o que o Entity Framework interpreta como um conflito de simultaneidade. Esse método é tão eficaz quanto usar umTimestamp
campo, mas pode ser ineficiente. Para tabelas de banco de dados que têm muitas colunas, isso pode resultar em cláusulas muito grandesWhere
e, em um aplicativo Web, pode exigir que você mantenha grandes quantidades de estado. Manter grandes quantidades de estado pode afetar o desempenho do aplicativo porque requer recursos do servidor (por exemplo, estado de sessão) ou deve ser incluído na própria página da Web (por exemplo, estado de exibição).
Neste tutorial, você adicionará tratamento de erros para conflitos de simultaneidade otimistas para uma entidade que não tem uma propriedade de acompanhamento (a Department
entidade) e para uma entidade que tem uma propriedade de rastreamento (a OfficeAssignment
entidade).
Manipulando simultaneidade otimista sem uma propriedade de acompanhamento
Para implementar a simultaneidade otimista para a Department
entidade, que não tem uma propriedade de acompanhamento (Timestamp
), você concluirá as seguintes tarefas:
- Altere o modelo de dados para habilitar o acompanhamento de simultaneidade para
Department
entidades. SchoolRepository
Na classe , manipule exceções de simultaneidade noSaveChanges
método .- Na página Departments.aspx , manipule exceções de simultaneidade exibindo uma mensagem para o usuário avisando que as tentativas de alterações não foram bem-sucedidas. Em seguida, o usuário poderá ver os valores atuais e repetir as alterações se ainda forem necessários.
Habilitando o rastreamento de simultaneidade no modelo de dados
No Visual Studio, abra o aplicativo Web da Contoso University com o qual você estava trabalhando no tutorial anterior desta série.
Abra SchoolModel.edmx e, no designer de modelo de dados, clique com o botão direito do Name
mouse na propriedade na Department
entidade e clique em Propriedades. Na janela Propriedades , altere a ConcurrencyMode
propriedade para Fixed
.
Faça o mesmo para as outras propriedades escalares não primárias (Budget
, StartDate
e Administrator
.) (Você não pode fazer isso para propriedades de navegação.) Isso especifica que sempre que o Entity Framework gera um Update
comando ou Delete
SQL para atualizar a Department
entidade no banco de dados, essas colunas (com valores originais) devem ser incluídas na Where
cláusula . Se nenhuma linha for encontrada quando o Update
comando ou Delete
for executado, o Entity Framework gerará uma exceção de simultaneidade otimista.
Salve e feche o modelo de dados.
Tratamento de exceções de simultaneidade no DAL
Abra SchoolRepository.cs e adicione a seguinte using
instrução para o System.Data
namespace:
using System.Data;
Adicione o novo SaveChanges
método a seguir, que lida com exceções de simultaneidade otimistas:
public void SaveChanges()
{
try
{
context.SaveChanges();
}
catch (OptimisticConcurrencyException ocex)
{
context.Refresh(RefreshMode.StoreWins, ocex.StateEntries[0].Entity);
throw ocex;
}
}
Se ocorrer um erro de simultaneidade quando esse método for chamado, os valores de propriedade da entidade na memória serão substituídos pelos valores atualmente no banco de dados. A exceção de simultaneidade é relançada para que a página da Web possa lidar com ela.
DeleteDepartment
Nos métodos e UpdateDepartment
, substitua a chamada existente para context.SaveChanges()
por uma chamada para SaveChanges()
para invocar o novo método.
Tratamento de exceções de simultaneidade na camada de apresentação
Abra Departments.aspx e adicione um OnDeleted="DepartmentsObjectDataSource_Deleted"
atributo ao DepartmentsObjectDataSource
controle . A marca de abertura do controle agora será semelhante ao exemplo a seguir.
<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server"
TypeName="ContosoUniversity.BLL.SchoolBL" DataObjectTypeName="ContosoUniversity.DAL.Department"
SelectMethod="GetDepartmentsByName" DeleteMethod="DeleteDepartment" UpdateMethod="UpdateDepartment"
ConflictDetection="CompareAllValues" OldValuesParameterFormatString="orig{0}"
OnUpdated="DepartmentsObjectDataSource_Updated" SortParameterName="sortExpression"
OnDeleted="DepartmentsObjectDataSource_Deleted" >
DepartmentsGridView
No controle , especifique todas as colunas de tabela no DataKeyNames
atributo , conforme mostrado no exemplo a seguir. Observe que isso criará campos de estado de exibição muito grandes, o que é um dos motivos pelos quais usar um campo de acompanhamento geralmente é a maneira preferida de acompanhar conflitos de simultaneidade.
<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="DepartmentsObjectDataSource"
DataKeyNames="DepartmentID,Name,Budget,StartDate,Administrator"
OnRowUpdating="DepartmentsGridView_RowUpdating"
OnRowDataBound="DepartmentsGridView_RowDataBound"
AllowSorting="True" >
Abra Departments.aspx.cs e adicione a seguinte using
instrução para o System.Data
namespace:
using System.Data;
Adicione o novo método a seguir, que você chamará dos manipuladores de eventos e Deleted
do controle da Updated
fonte de dados para lidar com exceções de simultaneidade:
private void CheckForOptimisticConcurrencyException(ObjectDataSourceStatusEventArgs e, string function)
{
if (e.Exception.InnerException is OptimisticConcurrencyException)
{
var concurrencyExceptionValidator = new CustomValidator();
concurrencyExceptionValidator.IsValid = false;
concurrencyExceptionValidator.ErrorMessage =
"The record you attempted to edit or delete was modified by another " +
"user after you got the original value. The edit or delete operation was canceled " +
"and the other user's values have been displayed so you can " +
"determine whether you still want to edit or delete this record.";
Page.Validators.Add(concurrencyExceptionValidator);
e.ExceptionHandled = true;
}
}
Esse código verifica o tipo de exceção e, se for uma exceção de simultaneidade, o código criará dinamicamente um CustomValidator
controle que, por sua vez, exibe uma mensagem no ValidationSummary
controle.
Chame o novo método do Updated
manipulador de eventos que você adicionou anteriormente. Além disso, crie um novo Deleted
manipulador de eventos que chame o mesmo método (mas não faz mais nada):
protected void DepartmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception != null)
{
CheckForOptimisticConcurrencyException(e, "update");
// ...
}
}
protected void DepartmentsObjectDataSource_Deleted(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception != null)
{
CheckForOptimisticConcurrencyException(e, "delete");
}
}
Testando a simultaneidade otimista na página Departamentos
Execute a página Departments.aspx .
Clique em Editar em uma linha e altere o valor na coluna Orçamento . (Lembre-se de que você só pode editar registros criados para este tutorial, pois os registros de banco de dados existentes School
contêm alguns dados inválidos. O registro para o departamento de Economia é seguro para experimentar.)
Abra uma nova janela do navegador e execute a página novamente (copie a URL da caixa de endereço da primeira janela do navegador para a segunda janela do navegador).
Clique em Editar na mesma linha editada anteriormente e altere o valor orçamento para algo diferente.
Na segunda janela do navegador, clique em Atualizar. O valor do Orçamento foi alterado com êxito para esse novo valor.
Na primeira janela do navegador, clique em Atualizar. A atualização falha. O valor de Orçamento é reproduzido usando o valor definido na segunda janela do navegador e você vê uma mensagem de erro.
Manipulando simultaneidade otimista usando uma propriedade de acompanhamento
Para lidar com a simultaneidade otimista de uma entidade que tem uma propriedade de acompanhamento, você concluirá as seguintes tarefas:
- Adicione procedimentos armazenados ao modelo de dados para gerenciar
OfficeAssignment
entidades. (As propriedades de acompanhamento e os procedimentos armazenados não precisam ser usados juntos; eles são apenas agrupados aqui para ilustração.) - Adicione métodos CRUD ao DAL e à BLL para
OfficeAssignment
entidades, incluindo código para lidar com exceções de simultaneidade otimistas no DAL. - Crie uma página da Web de atribuições de escritório.
- Teste a simultaneidade otimista na nova página da Web.
Adicionando procedimentos armazenados do OfficeAssignment ao modelo de dados
Abra o arquivo SchoolModel.edmx no designer de modelo, clique com o botão direito do mouse na superfície de design e clique em Atualizar Modelo do Banco de Dados. Na guia Adicionar da caixa de diálogo Escolher Objetos de Banco de Dados , expanda Procedimentos Armazenados e selecione os três OfficeAssignment
procedimentos armazenados (consulte a captura de tela a seguir) e clique em Concluir. (Esses procedimentos armazenados já estavam no banco de dados quando você o baixou ou o criou usando um script.)
Clique com o botão direito do mouse na OfficeAssignment
entidade e selecione Mapeamento de Procedimento Armazenado.
Defina as funções Inserir, Atualizar e Excluir para usar os procedimentos armazenados correspondentes. Para o OrigTimestamp
parâmetro da Update
função, defina a Propriedade como Timestamp
e selecione a opção Usar Valor Original .
Quando o Entity Framework chamar o UpdateOfficeAssignment
procedimento armazenado, ele passará o valor original da Timestamp
coluna no OrigTimestamp
parâmetro . O procedimento armazenado usa esse parâmetro em sua Where
cláusula:
ALTER PROCEDURE [dbo].[UpdateOfficeAssignment]
@InstructorID int,
@Location nvarchar(50),
@OrigTimestamp timestamp
AS
UPDATE OfficeAssignment SET Location=@Location
WHERE InstructorID=@InstructorID AND [Timestamp]=@OrigTimestamp;
IF @@ROWCOUNT > 0
BEGIN
SELECT [Timestamp] FROM OfficeAssignment
WHERE InstructorID=@InstructorID;
END
O procedimento armazenado também seleciona o novo valor da Timestamp
coluna após a atualização para que o Entity Framework possa manter a OfficeAssignment
entidade que está na memória em sincronia com a linha de banco de dados correspondente.
(Observe que o procedimento armazenado para excluir uma atribuição de escritório não tem um OrigTimestamp
parâmetro. Por isso, o Entity Framework não pode verificar se uma entidade está inalterada antes de excluí-la.)
Salve e feche o modelo de dados.
Adicionando métodos OfficeAssignment ao DAL
Abra ISchoolRepository.cs e adicione os seguintes métodos CRUD para o OfficeAssignment
conjunto de entidades:
IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression);
void InsertOfficeAssignment(OfficeAssignment OfficeAssignment);
void DeleteOfficeAssignment(OfficeAssignment OfficeAssignment);
void UpdateOfficeAssignment(OfficeAssignment OfficeAssignment, OfficeAssignment origOfficeAssignment);
Adicione os novos métodos a seguir a SchoolRepository.cs. UpdateOfficeAssignment
No método , você chama o método local SaveChanges
em vez de context.SaveChanges
.
public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
return new ObjectQuery<OfficeAssignment>("SELECT VALUE o FROM OfficeAssignments AS o", context).Include("Person").OrderBy("it." + sortExpression).ToList();
}
public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
context.OfficeAssignments.AddObject(officeAssignment);
context.SaveChanges();
}
public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
context.OfficeAssignments.Attach(officeAssignment);
context.OfficeAssignments.DeleteObject(officeAssignment);
context.SaveChanges();
}
public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
context.OfficeAssignments.Attach(origOfficeAssignment);
context.ApplyCurrentValues("OfficeAssignments", officeAssignment);
SaveChanges();
}
No projeto de teste, abra MockSchoolRepository.cs e adicione a coleção OfficeAssignment
e os métodos CRUD a seguir. (O repositório fictício deve implementar a interface do repositório ou a solução não será compilada.)
List<OfficeAssignment> officeAssignments = new List<OfficeAssignment>();
public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
return officeAssignments;
}
public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
officeAssignments.Add(officeAssignment);
}
public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
officeAssignments.Remove(officeAssignment);
}
public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
officeAssignments.Remove(origOfficeAssignment);
officeAssignments.Add(officeAssignment);
}
Adicionando métodos OfficeAssignment à BLL
No projeto main, abra SchoolBL.cs e adicione os seguintes métodos CRUD para a OfficeAssignment
entidade definida a ele:
public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
if (string.IsNullOrEmpty(sortExpression)) sortExpression = "Person.LastName";
return schoolRepository.GetOfficeAssignments(sortExpression);
}
public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
try
{
schoolRepository.InsertOfficeAssignment(officeAssignment);
}
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 DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
try
{
schoolRepository.DeleteOfficeAssignment(officeAssignment);
}
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 UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
try
{
schoolRepository.UpdateOfficeAssignment(officeAssignment, origOfficeAssignment);
}
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;
}
}
Criando uma página da Web OfficeAssignments
Crie uma nova página da Web que use a página master Site.Master e nomeie-a como OfficeAssignments.aspx. Adicione a seguinte marcação ao Content
controle chamado Content2
:
<h2>Office Assignments</h2>
<asp:ObjectDataSource ID="OfficeAssignmentsObjectDataSource" runat="server" TypeName="ContosoUniversity.BLL.SchoolBL"
DataObjectTypeName="ContosoUniversity.DAL.OfficeAssignment" SelectMethod="GetOfficeAssignments"
DeleteMethod="DeleteOfficeAssignment" UpdateMethod="UpdateOfficeAssignment" ConflictDetection="CompareAllValues"
OldValuesParameterFormatString="orig{0}"
SortParameterName="sortExpression" OnUpdated="OfficeAssignmentsObjectDataSource_Updated">
</asp:ObjectDataSource>
<asp:ValidationSummary ID="OfficeAssignmentsValidationSummary" runat="server" ShowSummary="true"
DisplayMode="BulletList" Style="color: Red; width: 40em;" />
<asp:GridView ID="OfficeAssignmentsGridView" runat="server" AutoGenerateColumns="False"
DataSourceID="OfficeAssignmentsObjectDataSource" DataKeyNames="InstructorID,Timestamp"
AllowSorting="True">
<Columns>
<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" ItemStyle-VerticalAlign="Top">
<ItemStyle VerticalAlign="Top"></ItemStyle>
</asp:CommandField>
<asp:TemplateField HeaderText="Instructor" SortExpression="Person.LastName">
<ItemTemplate>
<asp:Label ID="InstructorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
<asp:Label ID="InstructorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:DynamicField DataField="Location" HeaderText="Location" SortExpression="Location"/>
</Columns>
<SelectedRowStyle BackColor="LightGray"></SelectedRowStyle>
</asp:GridView>
Observe que, no DataKeyNames
atributo , a marcação especifica a Timestamp
propriedade, bem como a chave de registro (InstructorID
). Especificar propriedades no DataKeyNames
atributo faz com que o controle as salve no estado de controle (que é semelhante ao estado de exibição) para que os valores originais estejam disponíveis durante o processamento de postback.
Se você não salvou o Timestamp
valor, o Entity Framework não o teria para a Where
cláusula do comando SQL Update
. Consequentemente, nada seria encontrado para atualizar. Como resultado, o Entity Framework geraria uma exceção de simultaneidade otimista sempre que uma OfficeAssignment
entidade fosse atualizada.
Abra OfficeAssignments.aspx.cs e adicione a seguinte using
instrução para a camada de acesso a dados:
using ContosoUniversity.DAL;
Adicione o método a seguir Page_Init
, que habilita a funcionalidade de Dados Dinâmicos. Adicione também o seguinte manipulador para o ObjectDataSource
evento do Updated
controle para marcar para erros de simultaneidade:
protected void Page_Init(object sender, EventArgs e)
{
OfficeAssignmentsGridView.EnableDynamicData(typeof(OfficeAssignment));
}
protected void OfficeAssignmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.Exception != null)
{
var concurrencyExceptionValidator = new CustomValidator();
concurrencyExceptionValidator.IsValid = false;
concurrencyExceptionValidator.ErrorMessage = "The record you attempted to " +
"update has been modified by another user since you last visited this page. " +
"Your update was canceled to allow you to review the other user's " +
"changes and determine if you still want to update this record.";
Page.Validators.Add(concurrencyExceptionValidator);
e.ExceptionHandled = true;
}
}
Testando a simultaneidade otimista na página OfficeAssignments
Execute a página OfficeAssignments.aspx .
Clique em Editar em uma linha e altere o valor na coluna Localização .
Abra uma nova janela do navegador e execute a página novamente (copie a URL da primeira janela do navegador para a segunda janela do navegador).
Clique em Editar na mesma linha editada anteriormente e altere o valor Local para algo diferente.
Na segunda janela do navegador, clique em Atualizar.
Alterne para a primeira janela do navegador e clique em Atualizar.
Você verá uma mensagem de erro e o valor Local foi atualizado para mostrar o valor para o qual você a alterou na segunda janela do navegador.
Manipulando simultaneidade com o controle EntityDataSource
O EntityDataSource
controle inclui lógica interna que reconhece as configurações de simultaneidade no modelo de dados e manipula as operações de atualização e exclusão adequadamente. No entanto, como acontece com todas as exceções, você deve lidar com OptimisticConcurrencyException
exceções por conta própria para fornecer uma mensagem de erro amigável.
Em seguida, você configurará a página Courses.aspx (que usa um EntityDataSource
controle) para permitir operações de atualização e exclusão e exibir uma mensagem de erro se ocorrer um conflito de simultaneidade. A Course
entidade não tem uma coluna de acompanhamento de simultaneidade, portanto, você usará o mesmo método que fez com a Department
entidade: acompanhe os valores de todas as propriedades não chave.
Abra o arquivo SchoolModel.edmx . Para as propriedades não chave da Course
entidade (Title
, Credits
e DepartmentID
), defina a propriedade Modo de Simultaneidade como Fixed
. Em seguida, salve e feche o modelo de dados.
Abra a página Courses.aspx e faça as seguintes alterações:
CoursesEntityDataSource
No controle, adicioneEnableUpdate="true"
atributos eEnableDelete="true"
. A marca de abertura desse controle agora se assemelha ao seguinte exemplo:<asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false" AutoGenerateWhereClause="True" EntitySetName="Courses" EnableUpdate="true" EnableDelete="true">
CoursesGridView
No controle , altere o valor doDataKeyNames
atributo para"CourseID,Title,Credits,DepartmentID"
. Em seguida, adicione umCommandField
elemento ao elemento que mostra osColumns
botões Editar e Excluir (<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
). OGridView
controle agora se assemelha ao seguinte exemplo:<asp:GridView ID="CoursesGridView" runat="server" AutoGenerateColumns="False" DataKeyNames="CourseID,Title,Credits,DepartmentID" DataSourceID="CoursesEntityDataSource" > <Columns> <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" /> <asp:BoundField DataField="CourseID" HeaderText="CourseID" ReadOnly="True" SortExpression="CourseID" /> <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" /> <asp:BoundField DataField="Credits" HeaderText="Credits" SortExpression="Credits" /> </Columns> </asp:GridView>
Execute a página e crie uma situação de conflito como você fez antes na página Departamentos. Execute a página em duas janelas do navegador, clique em Editar na mesma linha em cada janela e faça uma alteração diferente em cada uma delas. Clique em Atualizar em uma janela e clique em Atualizar na outra janela. Ao clicar em Atualizar na segunda vez, você verá a página de erro resultante de uma exceção de simultaneidade sem tratamento.
Você lida com esse erro de uma maneira muito semelhante à maneira como lidou com ele para o ObjectDataSource
controle. Abra a página Courses.aspx e, no CoursesEntityDataSource
controle, especifique manipuladores para os Deleted
eventos e Updated
. A marca de abertura do controle agora se assemelha ao seguinte exemplo:
<asp:EntityDataSource ID="CoursesEntityDataSource" runat="server"
ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false"
AutoGenerateWhereClause="true" EntitySetName="Courses"
EnableUpdate="true" EnableDelete="true"
OnDeleted="CoursesEntityDataSource_Deleted"
OnUpdated="CoursesEntityDataSource_Updated">
Antes do CoursesGridView
controle, adicione o seguinte ValidationSummary
controle:
<asp:ValidationSummary ID="CoursesValidationSummary" runat="server"
ShowSummary="true" DisplayMode="BulletList" />
Em Courses.aspx.cs, adicione uma using
instrução para o System.Data
namespace, adicione um método que verifica exceções de simultaneidade e adicione manipuladores para os EntityDataSource
manipuladores e Deleted
do Updated
controle. O código será semelhante ao seguinte:
using System.Data;
protected void CoursesEntityDataSource_Updated(object sender, EntityDataSourceChangedEventArgs e)
{
CheckForOptimisticConcurrencyException(e, "update");
}
protected void CoursesEntityDataSource_Deleted(object sender, EntityDataSourceChangedEventArgs e)
{
CheckForOptimisticConcurrencyException(e, "delete");
}
private void CheckForOptimisticConcurrencyException(EntityDataSourceChangedEventArgs e, string function)
{
if (e.Exception != null && e.Exception is OptimisticConcurrencyException)
{
var concurrencyExceptionValidator = new CustomValidator();
concurrencyExceptionValidator.IsValid = false;
concurrencyExceptionValidator.ErrorMessage =
"The record you attempted to edit or delete was modified by another " +
"user after you got the original value. The edit or delete operation was canceled " +
"and the other user's values have been displayed so you can " +
"determine whether you still want to edit or delete this record.";
Page.Validators.Add(concurrencyExceptionValidator);
e.ExceptionHandled = true;
}
}
A única diferença entre esse código e o que você fez para o ObjectDataSource
controle é que, nesse caso, a exceção de simultaneidade está na Exception
propriedade do objeto de argumentos de evento e não na propriedade dessa InnerException
exceção.
Execute a página e crie um conflito de simultaneidade novamente. Desta vez, você verá uma mensagem de erro:
Isso conclui a introdução à manipulação de conflitos de simultaneidade. O próximo tutorial fornecerá diretrizes sobre como melhorar o desempenho em um aplicativo Web que usa o Entity Framework.