Recuperação de dados e operações CUD em aplicativos de n camadas (LINQ to SQL)
Quando você serializa objetos de entidade, como Clientes ou Pedidos, para um cliente em uma rede, essas entidades são separadas de seu contexto de dados. O contexto de dados não controla mais suas alterações ou suas associações com outros objetos. Isso não é um problema, desde que os clientes estejam apenas lendo os dados. Também é relativamente simples permitir que os clientes adicionem novas linhas a um banco de dados. No entanto, se seu aplicativo exigir que os clientes possam atualizar ou excluir dados, você deverá anexar as entidades a um novo contexto de dados antes de chamar DataContext.SubmitChanges. Além disso, se você estiver usando uma verificação de simultaneidade otimista com valores originais, também precisará de uma maneira de fornecer ao banco de dados a entidade original e a entidade conforme modificada. Os Attach
métodos são fornecidos para permitir que você coloque entidades em um novo contexto de dados depois que elas forem desanexadas.
Mesmo se você estiver serializando objetos proxy no lugar das entidades LINQ to SQL, ainda terá que construir uma entidade na camada de acesso a dados (DAL) e anexá-la a um novo System.Data.Linq.DataContext, para enviar os dados ao banco de dados.
O LINQ to SQL é completamente indiferente sobre como as entidades são serializadas. Para obter mais informações sobre como usar o Object Relational Designer e as ferramentas SQLMetal para gerar classes que são serializáveis usando o Windows Communication Foundation (WCF), consulte Como tornar entidades serializáveis.
Nota
Chame apenas os Attach
métodos em entidades novas ou desserializadas. A única maneira de uma entidade ser separada de seu contexto de dados original é serializá-la. Se você tentar anexar uma entidade não separada a um novo contexto de dados e essa entidade ainda tiver carregadores adiados de seu contexto de dados anterior, o LINQ to SQL lançará uma exceção. Uma entidade com carregadores adiados de dois contextos de dados diferentes pode causar resultados indesejados quando você executa operações de inserção, atualização e exclusão nessa entidade. Para obter mais informações sobre carregadores adiados, consulte Carregamento adiado versus carregamento imediato.
Recuperar dados
Chamada de método de cliente
Os exemplos a seguir mostram uma chamada de método de exemplo para a DAL de um cliente Windows Forms. Neste exemplo, a DAL é implementada como uma Biblioteca de Serviços do Windows:
Private Function GetProdsByCat_Click(ByVal sender As Object, ByVal e _
As EventArgs)
' Create the WCF client proxy.
Dim proxy As New NorthwindServiceReference.Service1Client
' Call the method on the service.
Dim products As NorthwindServiceReference.Product() = _
proxy.GetProductsByCategory(1)
' If the database uses original values for concurrency checks,
' the client needs to store them and pass them back to the
' middle tier along with the new values when updating data.
For Each v As NorthwindClient1.NorthwindServiceReference.Product _
In products
' Persist to a List(Of Product) declared at class scope.
' Additional change-tracking logic is the responsibility
' of the presentation tier and/or middle tier.
originalProducts.Add(v)
Next
' (Not shown) Bind the products list to a control
' and/or perform whatever processing is necessary.
End Function
private void GetProdsByCat_Click(object sender, EventArgs e)
{
// Create the WCF client proxy.
NorthwindServiceReference.Service1Client proxy =
new NorthwindClient.NorthwindServiceReference.Service1Client();
// Call the method on the service.
NorthwindServiceReference.Product[] products =
proxy.GetProductsByCategory(1);
// If the database uses original values for concurrency checks,
// the client needs to store them and pass them back to the
// middle tier along with the new values when updating data.
foreach (var v in products)
{
// Persist to a list<Product> declared at class scope.
// Additional change-tracking logic is the responsibility
// of the presentation tier and/or middle tier.
originalProducts.Add(v);
}
// (Not shown) Bind the products list to a control
// and/or perform whatever processing is necessary.
}
Implementação de nível intermediário
O exemplo a seguir mostra uma implementação do método de interface na camada intermediária. Seguem-se os dois principais pontos a observar:
- O DataContext é declarado no escopo do método.
- O método retorna uma IEnumerable coleção dos resultados reais. O serializador executará a consulta para enviar os resultados de volta para a camada cliente/apresentação. Para acessar os resultados da consulta localmente na camada intermediária, você pode forçar a execução chamando
ToList
ouToArray
na variável de consulta. Em seguida, você pode retornar essa lista ou matriz como umIEnumerable
arquivo .
Public Function GetProductsByCategory(ByVal categoryID As Integer) _
As IEnumerable(Of Product)
Dim db As New NorthwindClasses1DataContext(connectionString)
Dim productQuery = _
From prod In db.Products _
Where prod.CategoryID = categoryID _
Select prod
Return productQuery.AsEnumerable()
End Function
public IEnumerable<Product> GetProductsByCategory(int categoryID)
{
NorthwindClasses1DataContext db =
new NorthwindClasses1DataContext(connectionString);
IEnumerable<Product> productQuery =
from prod in db.Products
where prod.CategoryID == categoryID
select prod;
return productQuery.AsEnumerable();
}
Uma instância de um contexto de dados deve ter um tempo de vida de uma "unidade de trabalho". Em um ambiente de acoplamento flexível, uma unidade de trabalho é tipicamente pequena, talvez uma transação otimista, incluindo uma única chamada para SubmitChanges
. Portanto, o contexto de dados é criado e descartado no escopo do método. Se a unidade de trabalho incluir chamadas para a lógica de regras de negócios, geralmente você desejará manter a instância para toda essa DataContext
operação. Em qualquer caso, DataContext
as instâncias não se destinam a ser mantidas vivas por longos períodos de tempo em números arbitrários de transações.
Esse método retornará objetos Product, mas não a coleção de objetos Order_Detail associados a cada Product. Use o DataLoadOptions objeto para alterar esse comportamento padrão. Para obter mais informações, consulte Como controlar a quantidade de dados relacionados que são recuperados.
Inserir Dados
Para inserir um novo objeto, a camada de apresentação apenas chama o método relevante na interface da camada intermediária e passa o novo objeto a ser inserido. Em alguns casos, pode ser mais eficiente para o cliente passar apenas alguns valores e fazer com que a camada intermediária construa o objeto completo.
Implementação de nível intermediário
Na camada intermediária, um novo DataContext é criado, o objeto é anexado DataContext ao usando o InsertOnSubmit método e o objeto é inserido quando SubmitChanges é chamado. Exceções, retornos de chamada e condições de erro podem ser tratados como em qualquer outro cenário de serviço Web.
' No call to Attach is necessary for inserts.
Public Sub InsertOrder(ByVal o As Order)
Dim db As New NorthwindClasses1DataContext(connectionString)
db.Orders.InsertOnSubmit(o)
' Exception handling not shown.
db.SubmitChanges()
End Sub
// No call to Attach is necessary for inserts.
public void InsertOrder(Order o)
{
NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
db.Orders.InsertOnSubmit(o);
// Exception handling not shown.
db.SubmitChanges();
}
Excluir dados
Para excluir um objeto existente do banco de dados, a camada de apresentação chama o método relevante na interface da camada intermediária e passa sua cópia que inclui valores originais do objeto a ser excluído.
As operações de exclusão envolvem verificações de simultaneidade otimistas, e o objeto a ser excluído deve primeiro ser anexado ao novo contexto de dados. Neste exemplo, o Boolean
parâmetro é definido para false
indicar que o objeto não tem um carimbo de data/hora (RowVersion). Se a tabela do banco de dados gerar carimbos de data/hora para cada registro, as verificações de simultaneidade serão muito mais simples, especialmente para o cliente. Basta passar o objeto original ou modificado e definir o Boolean
parâmetro como true
. Em qualquer caso, na camada intermediária normalmente é necessário pegar o ChangeConflictException. Para obter mais informações sobre como lidar com conflitos de simultaneidade otimistas, consulte Simultaneidade otimista: visão geral.
Ao excluir entidades que têm restrições de chave estrangeira em tabelas associadas, você deve primeiro excluir todos os objetos em suas EntitySet<TEntity> coleções.
' Attach is necessary for deletes.
Public Sub DeleteOrder(ByVal order As Order)
Dim db As New NorthwindClasses1DataContext(connectionString)
db.Orders.Attach(order, False)
' This will throw an exception if the order has order details.
db.Orders.DeleteOnSubmit(order)
Try
' ConflictMode is an optional parameter.
db.SubmitChanges(ConflictMode.ContinueOnConflict)
Catch ex As ChangeConflictException
' Get conflict information, and take actions
' that are appropriate for your application.
' See MSDN Article "How to: Manage Change
' Conflicts (LINQ to SQL).
End Try
End Sub
// Attach is necessary for deletes.
public void DeleteOrder(Order order)
{
NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
db.Orders.Attach(order, false);
// This will throw an exception if the order has order details.
db.Orders.DeleteOnSubmit(order);
try
{
// ConflictMode is an optional parameter.
db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e)
{
// Get conflict information, and take actions
// that are appropriate for your application.
// See MSDN Article How to: Manage Change Conflicts (LINQ to SQL).
}
}
Atualizar dados
O LINQ to SQL oferece suporte a atualizações nestes cenários envolvendo simultaneidade otimista:
- Simultaneidade otimista com base em carimbos de data/hora ou números RowVersion.
- Simultaneidade otimista com base nos valores originais de um subconjunto de propriedades da entidade.
- Simultaneidade otimista com base nas entidades originais e modificadas completas.
Você também pode realizar atualizações ou exclusões em uma entidade juntamente com suas relações, por exemplo, um Cliente e uma coleção de seus objetos Order associados. Quando você faz modificações no cliente em um gráfico de objetos de entidade e suas coleções filhas (EntitySet
), e as verificações de simultaneidade otimistas exigem valores originais, o cliente deve fornecer esses valores originais para cada entidade e EntitySet<TEntity> objeto. Se quiser permitir que os clientes façam um conjunto de atualizações, exclusões e inserções relacionadas em uma única chamada de método, você deve fornecer ao cliente uma maneira de indicar que tipo de operação executar em cada entidade. Na camada intermediária, você deve chamar o método apropriado Attach e, em seguida InsertOnSubmit, , DeleteAllOnSubmitou InsertOnSubmit (sem Attach
, para inserções) para cada entidade antes de chamar SubmitChanges. Não recupere dados do banco de dados como uma maneira de obter valores originais antes de tentar atualizações.
Para obter mais informações sobre simultaneidade otimista, consulte Simultaneidade otimista: visão geral. Para obter informações detalhadas sobre como resolver conflitos de alteração de simultaneidade otimistas, consulte Como gerenciar conflitos de alteração.
Os exemplos a seguir demonstram cada cenário:
Simultaneidade otimista com carimbos de data/hora
' Assume that "customer" has been sent by client.
' Attach with "true" to say this is a modified entity
' and it can be checked for optimistic concurrency
' because it has a column that is marked with the
' "RowVersion" attribute.
db.Customers.Attach(customer, True)
Try
' Optional: Specify a ConflictMode value
' in call to SubmitChanges.
db.SubmitChanges()
Catch ex As ChangeConflictException
' Handle conflict based on options provided.
' See MSDN article "How to: Manage Change
' Conflicts (LINQ to SQL)".
End Try
// Assume that "customer" has been sent by client.
// Attach with "true" to say this is a modified entity
// and it can be checked for optimistic concurrency because
// it has a column that is marked with "RowVersion" attribute
db.Customers.Attach(customer, true)
try
{
// Optional: Specify a ConflictMode value
// in call to SubmitChanges.
db.SubmitChanges();
}
catch(ChangeConflictException e)
{
// Handle conflict based on options provided
// See MSDN article How to: Manage Change Conflicts (LINQ to SQL).
}
Com subconjunto de valores originais
Nessa abordagem, o cliente retorna o objeto serializado completo, juntamente com os valores a serem modificados.
Public Sub UpdateProductInventory(ByVal p As Product, ByVal _
unitsInStock As Short?, ByVal unitsOnOrder As Short?)
Using db As New NorthwindClasses1DataContext(connectionString)
' p is the original unmodified product
' that was obtained from the database.
' The client kept a copy and returns it now.
db.Products.Attach(p, False)
' Now that the original values are in the data context,
' apply the changes.
p.UnitsInStock = unitsInStock
p.UnitsOnOrder = unitsOnOrder
Try
' Optional: Specify a ConflictMode value
' in call to SubmitChanges.
db.SubmitChanges()
Catch ex As Exception
' Handle conflict based on options provided.
' See MSDN article "How to: Manage Change Conflicts
' (LINQ to SQL)".
End Try
End Using
End Sub
public void UpdateProductInventory(Product p, short? unitsInStock, short? unitsOnOrder)
{
using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
{
// p is the original unmodified product
// that was obtained from the database.
// The client kept a copy and returns it now.
db.Products.Attach(p, false);
// Now that the original values are in the data context, apply the changes.
p.UnitsInStock = unitsInStock;
p.UnitsOnOrder = unitsOnOrder;
try
{
// Optional: Specify a ConflictMode value
// in call to SubmitChanges.
db.SubmitChanges();
}
catch (ChangeConflictException e)
{
// Handle conflict based on provided options.
// See MSDN article How to: Manage Change Conflicts
// (LINQ to SQL).
}
}
}
Com Entidades Completas
Public Sub UpdateProductInfo(ByVal newProd As Product, ByVal _
originalProd As Product)
Using db As New NorthwindClasses1DataContext(connectionString)
db.Products.Attach(newProd, originalProd)
Try
' Optional: Specify a ConflictMode value
' in call to SubmitChanges.
db.SubmitChanges()
Catch ex As Exception
' Handle potential change conflict in whatever way
' is appropriate for your application.
' For more information, see the MSDN article
' "How to: Manage Change Conflicts (LINQ to
' SQL)".
End Try
End Using
End Sub
public void UpdateProductInfo(Product newProd, Product originalProd)
{
using (NorthwindClasses1DataContext db = new
NorthwindClasses1DataContext(connectionString))
{
db.Products.Attach(newProd, originalProd);
try
{
// Optional: Specify a ConflictMode value
// in call to SubmitChanges.
db.SubmitChanges();
}
catch (ChangeConflictException e)
{
// Handle potential change conflict in whatever way
// is appropriate for your application.
// For more information, see the MSDN article
// How to: Manage Change Conflicts (LINQ to SQL)/
}
}
}
Para atualizar uma coleção, chame AttachAll em vez de Attach
.
Membros esperados da entidade
Como dito anteriormente, apenas determinados membros do objeto de entidade precisam ser definidos antes de chamar os Attach
métodos. Os membros da entidade que devem ser definidos devem cumprir os seguintes critérios:
- Faça parte da identidade da entidade.
- Espera-se que seja modificado.
- Seja um carimbo de data/hora ou tenha seu UpdateCheck atributo definido para algo além de
Never
.
Se uma tabela usar um carimbo de data/hora ou número de versão para uma verificação de simultaneidade otimista, você deverá definir esses membros antes de chamar Attach. Um membro é dedicado para verificação de simultaneidade otimista quando a IsVersion propriedade é definida como true nesse atributo Column. Quaisquer atualizações solicitadas serão enviadas somente se o número da versão ou os valores de carimbo de data/hora forem os mesmos no banco de dados.
Um membro também é usado na verificação de simultaneidade otimista, desde que o membro não tenha UpdateCheck definido como Never
. O valor padrão é Always
se nenhum outro valor for especificado.
Se algum desses membros necessários estiver faltando, um ChangeConflictException será lançado durante SubmitChanges ("Linha não encontrada ou alterada").
Estado
Depois que um objeto de entidade é anexado DataContext à instância, o objeto é considerado como estando no PossiblyModified
estado. Há três maneiras de forçar um objeto anexado a ser considerado Modified
.
Anexe-o como não modificado e, em seguida, modifique diretamente os campos.
Anexe-o com a Attach sobrecarga que usa instâncias de objeto atuais e originais. Isso fornece ao controlador de alterações valores antigos e novos para que ele saiba automaticamente quais campos foram alterados.
Anexe-o com a Attach sobrecarga que leva um segundo parâmetro booleano (definido como true). Isso dirá ao rastreador de alterações para considerar o objeto modificado sem ter que fornecer nenhum valor original. Nessa abordagem, o objeto deve ter um campo de versão/carimbo de data/hora.
Para obter mais informações, consulte Estados do objeto e controle de alterações.
Se um objeto de entidade já ocorrer no Cache de ID com a mesma identidade do objeto que está sendo anexado, um DuplicateKeyException será lançado.
Quando você anexa com um IEnumerable
conjunto de objetos, um DuplicateKeyException é lançado quando uma chave já existente está presente. Os objetos restantes não são anexados.