Validação exclusiva do Entity Framework em local e banco de dados
-
21-12-2019 - |
Pergunta
Atualmente estou tentando lidar com a validação exclusiva para entidades como parte do meu método ValidateEntity no meu DbContext.O problema que estou tentando resolver é adicionar várias entidades ao mesmo tempo, detectando erros de restrição exclusivos antes que eles atinjam o banco de dados.Um exemplo é o caso de adicionar as entidades A e B certificando-se de que A e B não tenham o mesmo nome.Atualmente tenho os índices exclusivos aplicados para que pelo menos o banco de dados possa lidar com isso, e tenho o seguinte código que pode cobrir quando A já estiver no banco de dados:
if (this.Components.Any(x => x.Id != item.Id && x.Name == item.Name))
{
result.ValidationErrors.Add(new DbValidationError("Name", "There already exists another component with that name."));
}
Existe algo mais simples do que fazer o seguinte?
Expression<Func<Component, bool>> predicate = x => x.Name == item.Name;
if (this.Components.Where(x => x.Id != item.Id).Any(predicate) || this.Components.Local.Where(x => x != item).Any(predicate.Compile()))
{
result.ValidationErrors.Add(new DbValidationError("Name", "There already exists another component with that name."));
}
Editar
O caso em que a “chave única” é composta por uma chave estrangeira é uma situação mais complexa.Ao acessar o banco de dados, você precisa usar o campo de chave estrangeira, embora ao acessar o cache local nem sempre seja possível dizer quando ReferenceId == ReferenceId
devido a ambos serem zero se as entidades de referência também tiverem sido adicionadas.A maneira correta de verificar o cache local seria a seguinte ou eu precisaria carregar antecipadamente a referência, já que durante a validação o carregamento lento está desativado?
this.Components.Local.Any(x => x != item && x.Name == item.Name && x.ReferenceId == item.ReferenceId && x.Reference == item.Reference)
Solução
Para resolver meu problema e limitar a reutilização, adicionei as seguintes extensões para ajudar na validação exclusiva.
public static bool UniqueCheck<TSource>(this DbSet<TSource> set, TSource item, Expression<Func<TSource, bool>> uniquePredicate) where TSource : class, IAuditEntity
{
var function = uniquePredicate.Compile();
var localContains = set.Local.Where(x => x != item).Any(function);
if (localContains) return localContains;
var remoteContains = set.Where(x => x.Id != item.Id).Any(uniquePredicate);
return remoteContains;
}
public static bool UniqueCheckWithReference<TSource>(this DbSet<TSource> set, TSource item, Expression<Func<TSource, bool>> uniquePredicate, Expression<Func<TSource, bool>> referenceUniquePredicate) where TSource : class, IAuditEntity
{
var localContains = set.Local.Where(x => x != item).Where(uniquePredicate.Compile()).Where(referenceUniquePredicate.Compile()).Any();
if (localContains) return localContains;
var remoteContains = set.Where(x => x.Id != item.Id).Where(uniquePredicate);
return false;
}
A segunda função trata do caso da chave única ser composta por referências de chave estrangeira.