Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Filtros de consulta globais são predicados de consulta LINQ aplicados a Tipos de Entidade no modelo de metadados (geralmente em OnModelCreating
). Um predicado de consulta é uma expressão booliana normalmente passada para o operador de consulta LINQ Where
. O EF Core aplica esses filtros automaticamente a quaisquer consultas LINQ que envolvam esses tipos de entidade. O EF Core também os aplica aos Tipos de Entidade, referenciados indiretamente por meio do uso da propriedade Include ou de navegação. Alguns aplicativos comuns desse recurso são:
-
Exclusão reversível – um tipo de entidade define uma
IsDeleted
propriedade. -
Multi-tenancy – Um Tipo de Entidade define uma
TenantId
propriedade.
Exemplo
O exemplo a seguir mostra como usar filtros de consulta globais para implementar comportamentos de consulta de várias locações e exclusão reversível em um modelo de blog simples.
Dica
Você pode exibir o exemplo deste artigo no GitHub.
Observação
O multi-tenant é usado aqui como um exemplo simples. Há também um artigo com diretrizes abrangentes para multilocação em aplicativos EF Core.
Primeiro, defina as entidades:
public class Blog
{
#pragma warning disable IDE0051, CS0169 // Remove unused private members
private string _tenantId;
#pragma warning restore IDE0051, CS0169 // Remove unused private members
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public bool IsDeleted { get; set; }
public Blog Blog { get; set; }
}
Observe a declaração do campo _tenantId
na entidade Blog
. Esse campo será usado para associar cada instância de Blog a um locatário específico. Também é definida uma IsDeleted
propriedade do tipo de entidade Post
. Essa propriedade é usada para controlar se uma instância de postagem foi "excluída suavemente". Ou seja, a instância é marcada como excluída sem remover fisicamente os dados subjacentes.
Em seguida, configure os filtros de consulta em OnModelCreating
usando a API HasQueryFilter
.
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "_tenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
As expressões de predicado passadas para as chamadas HasQueryFilter
agora serão automaticamente aplicadas a quaisquer consultas LINQ que envolvam esses tipos.
Dica
Observe o uso de um campo de nível de instância DbContext: _tenantId
usado para definir o locatário atual. Os filtros de nível de modelo usarão o valor da instância de contexto correta (ou seja, a instância que está executando a consulta).
Observação
No momento, não é possível definir vários filtros de consulta na mesma entidade – somente o último será aplicado. No entanto, você pode definir um único filtro com várias condições usando o operador lógico AND
(&&
em C#).
Uso de navegações
Você também pode usar navegações para definir filtros globais de consulta. O uso de navegações nos filtros de consulta fará com que os filtros de consulta sejam aplicados recursivamente. Quando o EF Core expande as navegações usadas em filtros de consulta, ele também aplicará os filtros de consulta definidos em entidades referenciadas.
Para ilustrar isso, configure filtros OnModelCreating
de consulta da seguinte maneira:
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog);
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Posts.Count > 0);
modelBuilder.Entity<Post>().HasQueryFilter(p => p.Title.Contains("fish"));
Em seguida, consulte todas as Blog
entidades:
var filteredBlogs = await db.Blogs.ToListAsync();
Essa consulta produz o seguinte SQL, que aplica filtros de consulta definidos para ambas as Blog
entidades:Post
SELECT [b].[BlogId], [b].[Name], [b].[Url]
FROM [Blogs] AS [b]
WHERE (
SELECT COUNT(*)
FROM [Posts] AS [p]
WHERE ([p].[Title] LIKE N'%fish%') AND ([b].[BlogId] = [p].[BlogId])) > 0
Observação
Atualmente, o EF Core não detecta ciclos em definições globais de filtro de consulta, portanto, você deve ter cuidado ao defini-los. Se os ciclos forem especificados incorretamente, podem levar a loops infinitos durante a tradução de consultas.
Acessando a entidade com filtro de consulta usando a navegação obrigatória
Cuidado
Usar a navegação necessária para acessar a entidade que tem o filtro de consulta global definido pode levar a resultados inesperados.
A navegação necessária espera que a entidade relacionada esteja sempre presente. Se uma entidade relacionada necessária for filtrada pelo filtro de consulta, a entidade pai não estará no resultado. Portanto, você pode obter menos elementos do que o esperado no resultado.
Para ilustrar o problema, podemos usar as entidades Blog
e Post
especificadas acima, assim como o seguinte método OnModelCreating
:
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired();
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
O modelo pode ser inicializado com os seguintes dados:
db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/fish",
Posts = new List<Post>
{
new Post { Title = "Fish care 101" },
new Post { Title = "Caring for tropical fish" },
new Post { Title = "Types of ornamental fish" }
}
});
db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/cats",
Posts = new List<Post>
{
new Post { Title = "Cat care 101" },
new Post { Title = "Caring for tropical cats" },
new Post { Title = "Types of ornamental cats" }
}
});
O problema pode ser observado ao executar duas consultas:
var allPosts = await db.Posts.ToListAsync();
var allPostsWithBlogsIncluded = await db.Posts.Include(p => p.Blog).ToListAsync();
Com a configuração acima, a primeira consulta retorna todos os 6 Post
s, no entanto, a segunda consulta retorna apenas 3. Essa incompatibilidade ocorre porque Include
o método na segunda consulta carrega as entidades relacionadas Blog
. Como a navegação entre Blog
e Post
é necessária, o EF Core usa INNER JOIN
ao construir a consulta:
SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[IsDeleted], [p].[Title], [t].[BlogId], [t].[Name], [t].[Url]
FROM [Posts] AS [p]
INNER JOIN (
SELECT [b].[BlogId], [b].[Name], [b].[Url]
FROM [Blogs] AS [b]
WHERE [b].[Url] LIKE N'%fish%'
) AS [t] ON [p].[BlogId] = [t].[BlogId]
O uso do INNER JOIN
filtra todos os Post
cujos Blog
relacionados foram removidos por um filtro de consulta global.
Ele pode ser resolvido usando a navegação opcional em vez de necessária.
Dessa forma, a primeira consulta permanece a mesma de antes, no entanto, a segunda consulta agora gerará LEFT JOIN
e retornará 6 resultados.
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired(false);
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
A abordagem alternativa é especificar filtros consistentes em ambas as entidades Blog
e Post
.
Dessa forma, os filtros correspondentes são aplicados a ambos Blog
e Post
.
Post
s que podem terminar num estado inesperado são removidos e ambas as consultas retornam 3 resultados.
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired();
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
modelBuilder.Entity<Post>().HasQueryFilter(p => p.Blog.Url.Contains("fish"));
Desabilitando filtros
Os filtros podem ser desabilitados para consultas LINQ individuais usando o IgnoreQueryFilters operador.
blogs = await db.Blogs
.Include(b => b.Posts)
.IgnoreQueryFilters()
.ToListAsync();
Limitações
Os filtros de consulta globais têm as seguintes limitações:
- Os filtros só podem ser definidos para o tipo de entidade raiz de uma hierarquia de herança.