Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
A DbContext só pode rastrear uma instância de entidade com um valor de chave primária específico. Isso significa que várias instâncias de uma entidade com o mesmo valor de chave devem ser resolvidas para uma única instância. Isso é chamado de "resolução de identidade". A resolução de identidade garante que o Entity Framework Core (EF Core) esteja rastreando um gráfico consistente sem ambiguidades sobre os relacionamentos ou valores de propriedade das entidades.
Sugestão
Este documento pressupõe que os estados da entidade e os conceitos básicos do controle de alterações do EF Core sejam compreendidos. Consulte Controle de alterações no EF Core para obter mais informações sobre esses tópicos.
Sugestão
Você pode executar e depurar todo o código neste documento baixando o código de exemplo do GitHub.
Introdução
O código a seguir consulta uma entidade e, em seguida, tenta anexar uma instância diferente com o mesmo valor de chave primária:
using var context = new BlogsContext();
var blogA = await context.Blogs.SingleAsync(e => e.Id == 1);
var blogB = new Blog { Id = 1, Name = ".NET Blog (All new!)" };
try
{
context.Update(blogB); // This will throw
}
catch (Exception e)
{
Console.WriteLine($"{e.GetType().FullName}: {e.Message}");
}
A execução desse código resulta na seguinte exceção:
System.InvalidOperationException: A instância do tipo de entidade 'Blog' não pode ser rastreada porque outra instância com o valor de chave '{Id: 1}' já está sendo rastreada. Ao anexar entidades existentes, certifique-se de que apenas uma instância de entidade com um determinado valor de chave esteja anexada.
O EF Core requer uma única instância porque:
- Os valores de propriedade podem ser diferentes entre várias instâncias. Ao atualizar o banco de dados, o EF Core precisa saber quais valores de propriedade usar.
- As relações com outras entidades podem ser diferentes entre várias instâncias. Por exemplo, "blogA" pode estar relacionado a uma coleção diferente de posts do que "blogB".
A exceção acima é comumente encontrada nestas situações:
- Ao tentar atualizar uma entidade
- Ao tentar rastrear um grafo serializado de entidades
- Ao não definir um valor de chave que não é gerado automaticamente
- Ao reutilizar uma instância DbContext para várias unidades de trabalho
Cada uma dessas situações é discutida nas seções a seguir.
Atualizar uma entidade
Há várias abordagens diferentes para atualizar uma entidade com novos valores, conforme abordado em Controle de alterações no EF Core e Explicitamente controlando entidades. Essas abordagens são descritas abaixo no contexto da resolução de identidade. Um ponto importante a notar é que cada uma das abordagens usa uma consulta ou uma chamada para Update ou Attach, mas nunca os dois.
Atualização da chamada
Muitas vezes, a entidade a atualizar não provém de uma consulta no DbContext que vamos utilizar para o método SaveChanges. Por exemplo, em um aplicativo Web, uma instância de entidade pode ser criada a partir das informações em uma solicitação POST. A maneira mais simples de lidar com isso é usar DbContext.Update ou DbSet<TEntity>.Update. Por exemplo:
public static async Task UpdateFromHttpPost1(Blog blog)
{
using var context = new BlogsContext();
context.Update(blog);
await context.SaveChangesAsync();
}
Neste caso:
- Apenas uma única instância da entidade é criada.
- A instância da entidade não é consultada do banco de dados como parte da atualização.
- Todos os valores de propriedade serão atualizados no banco de dados, independentemente de terem sido realmente alterados ou não.
- Uma operação de ida e volta à base de dados é realizada.
Executar consulta e depois aplicar alterações
Normalmente, não se sabe quais valores de propriedade foram realmente alterados quando uma entidade é criada a partir de informações em uma solicitação POST ou similar. Muitas vezes é bom apenas atualizar todos os valores no banco de dados, como fizemos no exemplo anterior. No entanto, se o aplicativo estiver lidando com muitas entidades e apenas um pequeno número delas tiver alterações reais, pode ser útil limitar as atualizações enviadas. Isso pode ser feito executando uma consulta para controlar as entidades como elas existem atualmente no banco de dados e, em seguida, aplicando alterações a essas entidades controladas. Por exemplo:
public static async Task UpdateFromHttpPost2(Blog blog)
{
using var context = new BlogsContext();
var trackedBlog = await context.Blogs.FindAsync(blog.Id);
trackedBlog.Name = blog.Name;
trackedBlog.Summary = blog.Summary;
await context.SaveChangesAsync();
}
Neste caso:
- Uma única instância da entidade é rastreada; a que é retornada do banco de dados pela consulta
Find. -
Update,Attach, etc. não são utilizados. - Somente os valores de propriedade que realmente foram alterados são atualizados no banco de dados.
- São feitas duas viagens de ida e volta à base de dados.
O EF Core tem alguns auxiliares para transferir valores de propriedade como este. Por exemplo, PropertyValues.SetValues copiará todos os valores de um determinado objeto e os definirá no objeto rastreado:
public static async Task UpdateFromHttpPost3(Blog blog)
{
using var context = new BlogsContext();
var trackedBlog = await context.Blogs.FindAsync(blog.Id);
context.Entry(trackedBlog).CurrentValues.SetValues(blog);
await context.SaveChangesAsync();
}
SetValues aceita vários tipos de objeto, incluindo DTOs (objetos de transferência de dados) com nomes de propriedade que correspondem às propriedades do tipo de entidade. Por exemplo:
public static async Task UpdateFromHttpPost4(BlogDto dto)
{
using var context = new BlogsContext();
var trackedBlog = await context.Blogs.FindAsync(dto.Id);
context.Entry(trackedBlog).CurrentValues.SetValues(dto);
await context.SaveChangesAsync();
}
Ou um dicionário com entradas de nome/valor para os valores de propriedade:
public static async Task UpdateFromHttpPost5(Dictionary<string, object> propertyValues)
{
using var context = new BlogsContext();
var trackedBlog = await context.Blogs.FindAsync(propertyValues["Id"]);
context.Entry(trackedBlog).CurrentValues.SetValues(propertyValues);
await context.SaveChangesAsync();
}
Consulte Acedendo entidades acompanhadas para obter mais informações sobre como trabalhar com este tipo de valores de propriedade.
Usar valores originais
Até agora, cada abordagem executou uma consulta antes de fazer a atualização ou atualizou todos os valores de propriedade, independentemente de terem sido alterados ou não. Para atualizar apenas os valores que foram alterados sem realizar consultas durante a atualização requer informações específicas sobre quais valores das propriedades foram alterados. Uma maneira comum de obter essas informações é enviar de volta os valores atuais e originais no HTTP Post ou similar. Por exemplo:
public static async Task UpdateFromHttpPost6(Blog blog, Dictionary<string, object> originalValues)
{
using var context = new BlogsContext();
context.Attach(blog);
context.Entry(blog).OriginalValues.SetValues(originalValues);
await context.SaveChangesAsync();
}
Neste código, a entidade com valores modificados é anexada primeiro. Isso faz com que o EF Core rastreie a entidade no estado Unchanged, ou seja, sem valores de propriedade marcados como modificados. O dicionário de valores originais é então aplicado a essa entidade controlada. Isto irá marcar propriedades como modificadas quando os valores atuais forem diferentes dos originais. As propriedades que têm os mesmos valores atuais e originais não serão marcadas como modificadas.
Neste caso:
- Apenas uma única instância da entidade é rastreada, usando Attach.
- A instância da entidade não é consultada do banco de dados como parte da atualização.
- A aplicação dos valores originais garante que apenas os valores de propriedade que realmente foram alterados sejam atualizados no banco de dados.
- Uma operação de ida e volta à base de dados é realizada.
Tal como acontece com os exemplos na secção anterior, não é necessário passar os valores originais como um dicionário; uma instância de entidade ou um objeto de transferência de dados (DTO) também funcionará.
Sugestão
Embora essa abordagem tenha características atraentes, ela requer o envio dos valores originais da entidade de e para o web client. Pondere cuidadosamente se esta complexidade adicional vale a pena os benefícios; Para muitas aplicações, uma das abordagens mais simples é mais pragmática.
Anexando um gráfico serializado
O EF Core funciona com gráficos de entidades conectadas por meio de chaves estrangeiras e propriedades de navegação, conforme descrito em Alterando chaves estrangeiras e navegações. Se esses gráficos forem criados fora do EF Core usando, por exemplo, a partir de um arquivo JSON, eles poderão ter várias instâncias da mesma entidade. Essas duplicatas precisam ser resolvidas em instâncias únicas antes que o gráfico possa ser rastreado.
Gráficos sem duplicados
Antes de ir mais longe, é importante reconhecer que:
- Os serializadores geralmente têm opções para manipular loops e instâncias duplicadas no gráfico.
- A escolha do objeto usado como raiz do gráfico pode muitas vezes ajudar a reduzir ou remover duplicatas.
Se possível, use opções de serialização e escolha raízes que não resultem em duplicatas. Por exemplo, o código a seguir usa Json.NET para serializar uma lista de blogs, cada um com suas postagens associadas:
using var context = new BlogsContext();
var blogs = await context.Blogs.Include(e => e.Posts).ToListAsync();
var serialized = JsonConvert.SerializeObject(
blogs,
new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented });
Console.WriteLine(serialized);
O JSON gerado a partir deste código é:
[
{
"Id": 1,
"Name": ".NET Blog",
"Summary": "Posts about .NET",
"Posts": [
{
"Id": 1,
"Title": "Announcing the Release of EF Core 5.0",
"Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
"BlogId": 1
},
{
"Id": 2,
"Title": "Announcing F# 5",
"Content": "F# 5 is the latest version of F#, the functional programming language...",
"BlogId": 1
}
]
},
{
"Id": 2,
"Name": "Visual Studio Blog",
"Summary": "Posts about Visual Studio",
"Posts": [
{
"Id": 3,
"Title": "Disassembly improvements for optimized managed debugging",
"Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
"BlogId": 2
},
{
"Id": 4,
"Title": "Database Profiling with Visual Studio",
"Content": "Examine when database queries were executed and measure how long the take using...",
"BlogId": 2
}
]
}
]
Observe que não há blogs ou postagens duplicadas no JSON. Isso significa que chamadas simples para Update funcionarão para atualizar essas entidades no banco de dados:
public static async Task UpdateBlogsFromJson(string json)
{
using var context = new BlogsContext();
var blogs = JsonConvert.DeserializeObject<List<Blog>>(json);
foreach (var blog in blogs)
{
context.Update(blog);
}
await context.SaveChangesAsync();
}
Tratamento de duplicados
O código no exemplo anterior serializou cada blog com suas postagens associadas. Se isso for alterado para serializar cada postagem com seu blog associado, duplicatas serão introduzidas no JSON serializado. Por exemplo:
using var context = new BlogsContext();
var posts = await context.Posts.Include(e => e.Blog).ToListAsync();
var serialized = JsonConvert.SerializeObject(
posts,
new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented });
Console.WriteLine(serialized);
O JSON serializado agora tem esta aparência:
[
{
"Id": 1,
"Title": "Announcing the Release of EF Core 5.0",
"Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
"BlogId": 1,
"Blog": {
"Id": 1,
"Name": ".NET Blog",
"Summary": "Posts about .NET",
"Posts": [
{
"Id": 2,
"Title": "Announcing F# 5",
"Content": "F# 5 is the latest version of F#, the functional programming language...",
"BlogId": 1
}
]
}
},
{
"Id": 2,
"Title": "Announcing F# 5",
"Content": "F# 5 is the latest version of F#, the functional programming language...",
"BlogId": 1,
"Blog": {
"Id": 1,
"Name": ".NET Blog",
"Summary": "Posts about .NET",
"Posts": [
{
"Id": 1,
"Title": "Announcing the Release of EF Core 5.0",
"Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
"BlogId": 1
}
]
}
},
{
"Id": 3,
"Title": "Disassembly improvements for optimized managed debugging",
"Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
"BlogId": 2,
"Blog": {
"Id": 2,
"Name": "Visual Studio Blog",
"Summary": "Posts about Visual Studio",
"Posts": [
{
"Id": 4,
"Title": "Database Profiling with Visual Studio",
"Content": "Examine when database queries were executed and measure how long the take using...",
"BlogId": 2
}
]
}
},
{
"Id": 4,
"Title": "Database Profiling with Visual Studio",
"Content": "Examine when database queries were executed and measure how long the take using...",
"BlogId": 2,
"Blog": {
"Id": 2,
"Name": "Visual Studio Blog",
"Summary": "Posts about Visual Studio",
"Posts": [
{
"Id": 3,
"Title": "Disassembly improvements for optimized managed debugging",
"Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
"BlogId": 2
}
]
}
}
]
Observe que o gráfico agora inclui várias instâncias de Blog com o mesmo valor de chave, bem como várias instâncias de Post com o mesmo valor de chave. Tentar rastrear este gráfico como fizemos no exemplo anterior lançará:
System.InvalidOperationException: A instância do tipo de entidade 'Post' não pode ser rastreada porque outra instância com o valor de chave '{Id: 2}' já está sendo rastreada. Ao anexar entidades existentes, certifique-se de que apenas uma instância de entidade com um determinado valor de chave esteja anexada.
Podemos corrigir isso de duas maneiras:
- Usar opções de serialização JSON que preservam referências
- Realizar resolução de identidade durante o rastreamento do grafo
Preservar referências
Json.NET fornece a PreserveReferencesHandling opção para lidar com isso. Por exemplo:
var serialized = JsonConvert.SerializeObject(
posts,
new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All, Formatting = Formatting.Indented
});
O JSON resultante agora tem esta aparência:
{
"$id": "1",
"$values": [
{
"$id": "2",
"Id": 1,
"Title": "Announcing the Release of EF Core 5.0",
"Content": "Announcing the release of EF Core 5.0, a full featured cross-platform...",
"BlogId": 1,
"Blog": {
"$id": "3",
"Id": 1,
"Name": ".NET Blog",
"Summary": "Posts about .NET",
"Posts": [
{
"$ref": "2"
},
{
"$id": "4",
"Id": 2,
"Title": "Announcing F# 5",
"Content": "F# 5 is the latest version of F#, the functional programming language...",
"BlogId": 1,
"Blog": {
"$ref": "3"
}
}
]
}
},
{
"$ref": "4"
},
{
"$id": "5",
"Id": 3,
"Title": "Disassembly improvements for optimized managed debugging",
"Content": "If you are focused on squeezing out the last bits of performance for your .NET service or...",
"BlogId": 2,
"Blog": {
"$id": "6",
"Id": 2,
"Name": "Visual Studio Blog",
"Summary": "Posts about Visual Studio",
"Posts": [
{
"$ref": "5"
},
{
"$id": "7",
"Id": 4,
"Title": "Database Profiling with Visual Studio",
"Content": "Examine when database queries were executed and measure how long the take using...",
"BlogId": 2,
"Blog": {
"$ref": "6"
}
}
]
}
},
{
"$ref": "7"
}
]
}
Observe que este JSON substituiu duplicatas por referências, como "$ref": "5", que se referem à instância já existente no gráfico. Este gráfico pode ser novamente rastreado usando as chamadas simples para Update, como mostrado acima.
O System.Text.Json suporte nas bibliotecas de classes base (BCL) do .NET tem uma opção semelhante que produz o mesmo resultado. Por exemplo:
var serialized = JsonSerializer.Serialize(
posts, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, WriteIndented = true });
Eliminar duplicados
Se não for possível eliminar duplicatas no processo de serialização, então ChangeTracker.TrackGraph fornece uma maneira de lidar com isso. O TrackGraph funciona como Add, Attach e Update, exceto por gerar um retorno de chamada para cada instância de entidade antes de o rastrear. Este callback pode ser usado para rastrear a entidade ou ignorá-la. Por exemplo:
public static async Task UpdatePostsFromJsonWithIdentityResolution(string json)
{
using var context = new BlogsContext();
var posts = JsonConvert.DeserializeObject<List<Post>>(json);
foreach (var post in posts)
{
context.ChangeTracker.TrackGraph(
post, node =>
{
var keyValue = node.Entry.Property("Id").CurrentValue;
var entityType = node.Entry.Metadata;
var existingEntity = node.Entry.Context.ChangeTracker.Entries()
.FirstOrDefault(
e => Equals(e.Metadata, entityType)
&& Equals(e.Property("Id").CurrentValue, keyValue));
if (existingEntity == null)
{
Console.WriteLine($"Tracking {entityType.DisplayName()} entity with key value {keyValue}");
node.Entry.State = EntityState.Modified;
}
else
{
Console.WriteLine($"Discarding duplicate {entityType.DisplayName()} entity with key value {keyValue}");
}
});
}
await context.SaveChangesAsync();
}
Para cada entidade no gráfico, este código irá:
- Encontre o tipo de entidade e o valor da chave da entidade
- Procurar a entidade com essa chave no rastreador de alterações
- Se a entidade for encontrada, nenhuma ação adicional será tomada, pois a entidade é uma duplicata
- Se a entidade não for encontrada, ela será rastreada definindo o estado como
Modified
A saída da execução deste código é:
Tracking EntityType: Post entity with key value 1
Tracking EntityType: Blog entity with key value 1
Tracking EntityType: Post entity with key value 2
Discarding duplicate EntityType: Post entity with key value 2
Tracking EntityType: Post entity with key value 3
Tracking EntityType: Blog entity with key value 2
Tracking EntityType: Post entity with key value 4
Discarding duplicate EntityType: Post entity with key value 4
Importante
Este código pressupõe que todas as duplicatas são idênticas. Isso torna seguro escolher arbitrariamente uma das duplicatas para rastrear enquanto descarta as outras. Se as duplicatas puderem diferir, o código precisará decidir qual delas deverá ser usada e como combinar os valores de atributo e de navegação.
Observação
Para simplificar, esse código pressupõe que cada entidade tenha uma propriedade de chave primária chamada Id. Isso pode ser codificado em uma classe base abstrata ou interface. Como alternativa, a propriedade ou propriedades da chave primária poderiam ser obtidas dos metadados, IEntityType de modo que esse código funcionasse com qualquer tipo de entidade.
Falha ao definir valores-chave
Os tipos de entidade geralmente são configurados para usar valores de chave gerados automaticamente. Este é o padrão para propriedades inteiras e GUID de chaves não compostas. No entanto, se o tipo de entidade não estiver configurado para usar valores de chave gerados automaticamente, um valor de chave explícito deverá ser definido antes de rastrear a entidade. Por exemplo, usando o seguinte tipo de entidade:
public class Pet
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
}
Considere o código que tenta controlar duas novas instâncias de entidade sem definir valores de chave:
using var context = new BlogsContext();
context.Add(new Pet { Name = "Smokey" });
try
{
context.Add(new Pet { Name = "Clippy" }); // This will throw
}
catch (Exception e)
{
Console.WriteLine($"{e.GetType().FullName}: {e.Message}");
}
Este código lançará:
System.InvalidOperationException: A instância do tipo de entidade 'Pet' não pode ser rastreada porque outra instância com o valor de chave '{Id: 0}' já está sendo rastreada. Ao anexar entidades existentes, certifique-se de que apenas uma instância de entidade com um determinado valor de chave esteja anexada.
A correção para isso é definir valores de chave explicitamente ou configurar a propriedade de chave para usar valores de chave gerados. Consulte Valores gerados para obter mais informações.
Uso excessivo de uma única instância DbContext
DbContext foi projetado para representar uma unidade de trabalho de curta duração, conforme descrito em Inicialização e configuração do DbContext e elaborado no Controle de Alterações no EF Core. Não seguir esta orientação facilita o surgimento de situações em que se tenta rastrear várias instâncias da mesma entidade. Alguns exemplos comuns são:
- Usando a mesma instância DbContext para configurar o estado de teste e, em seguida, executar o teste. Isso geralmente resulta no DbContext ainda rastreando uma instância de entidade da configuração de teste, enquanto tenta anexar uma nova instância no teste propriamente dito. Em vez disso, use uma instância DbContext diferente para configurar o estado de teste e o código de teste propriamente dito.
- Usando uma instância DbContext compartilhada em um repositório ou código semelhante. Em vez disso, certifique-se de que seu repositório use uma única instância DbContext para cada unidade de trabalho.
Resolução de identidade e consultas
A resolução de identidade acontece automaticamente quando as entidades são rastreadas a partir de uma consulta. Isso significa que, se uma instância de entidade com um determinado valor de chave já estiver rastreada, essa instância controlada existente será usada em vez de criar uma nova instância. Isso tem uma consequência importante: se os dados foram alterados no banco de dados, isso não será refletido nos resultados da consulta. Esse é um bom motivo para usar uma nova instância DbContext para cada unidade de trabalho, conforme descrito em Inicialização e configuração de DbContext e elaborado em Controle de alterações no EF Core.
Importante
É importante entender que o EF Core sempre executa uma consulta LINQ em um DbSet no banco de dados e só retorna resultados com base no que está no banco de dados. No entanto, para uma consulta de rastreamento, se as entidades retornadas já estiverem acompanhadas, as instâncias acompanhadas serão usadas em vez de criar instâncias a partir dos dados no banco de dados.
Reload() ou GetDatabaseValues() pode ser usado quando as entidades controladas precisam ser atualizadas com os dados mais recentes do banco de dados. Consulte Aceder a entidades monitorizadas para obter mais informações.
Ao contrário das consultas de rastreamento, as consultas sem rastreamento não executam a resolução de identidade. Isso significa que consultas sem rastreamento podem retornar duplicatas, assim como no caso de serialização JSON descrito anteriormente. Isso geralmente não é um problema se os resultados da consulta forem serializados e enviados para o cliente.
Sugestão
Não execute rotineiramente uma consulta sem controle e, em seguida, anexe as entidades retornadas ao mesmo contexto. Isso será mais lento e mais difícil de acertar do que usar uma consulta de rastreamento.
As consultas sem rastreamento não executam a resolução de identidade porque isso afeta o desempenho do streaming de um grande número de entidades de uma consulta. Isso ocorre porque a resolução de identidade requer o controle de cada instância retornada para que ela possa ser usada em vez de criar posteriormente uma duplicata.
As consultas sem rastreamento podem ser forçadas a executar a resolução de identidade usando AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). A consulta controlará as instâncias retornadas (sem rastreá-las da maneira normal) e garantirá que nenhuma duplicata seja criada nos resultados da consulta.
Sobrepondo-se à igualdade de objetos
O EF Core usa a igualdade de referência ao comparar instâncias de entidade. Este é o caso mesmo se os tipos de entidade substituírem Object.Equals(Object) ou alterarem de outra forma a igualdade de objeto. No entanto, há um lugar onde a igualdade de substituição pode afetar o comportamento do EF Core: quando as navegações de coleção usam a igualdade substituída em vez da igualdade de referência e, portanto, relatam várias instâncias como o mesmo.
Por este motivo, recomenda-se que se evite a igualdade das entidades prevalecentes. Se for usado, certifique-se de criar navegações de coleção que forcem a igualdade de referência. Por exemplo, crie um comparador de igualdade que use a igualdade de referência:
public sealed class ReferenceEqualityComparer : IEqualityComparer<object>
{
private ReferenceEqualityComparer()
{
}
public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer();
bool IEqualityComparer<object>.Equals(object x, object y) => x == y;
int IEqualityComparer<object>.GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}
(A partir do .NET 5, isso é incluído na BCL como ReferenceEqualityComparer.)
Esse comparador pode ser usado ao criar navegações de coleção. Por exemplo:
public ICollection<Order> Orders { get; set; }
= new HashSet<Order>(ReferenceEqualityComparer.Instance);
Comparando as principais propriedades
Além das comparações de igualdade, os valores-chave também precisam ser ordenados. Isso é importante para evitar bloqueios ao atualizar várias entidades em uma única chamada para SaveChanges. Todos os tipos usados para propriedades de chave primária, alternativa ou estrangeira, bem como aqueles usados para índices exclusivos, devem implementar IComparable<T> e IEquatable<T>. Tipos normalmente usados como teclas (int, Guid, string, etc.) já suportam essas interfaces. Tipos de chave personalizados podem adicionar essas interfaces.