Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Genel sorgu filtreleri, bir varlık türüne filtre eklenmesine ve söz konusu varlık türündeki bir sorgu yürütülürken bu filtrenin uygulanmasını sağlar; bunları varlık türü sorgulandığında eklenen ek bir LINQ Where işleci olarak düşünün. Bu tür filtreler çeşitli durumlarda kullanışlıdır.
Tavsiye
Bu makalenin örneğini GitHub'da görüntüleyebilirsiniz.
Temel örnek - geçici silme
Bazı senaryolarda, veritabanından bir satırı silmek yerine, satırı silinmiş olarak işaretlemek için bir IsDeleted bayrak ayarlamak tercih edilir; bu desen geçici silme olarak adlandırılır. Geçici silme, gerekirse satırların silinmemesini veya silinen satırların hala erişilebilir olduğu bir denetim kaydının korunmasını sağlar. Genel sorgu filtreleri, geçici olarak silinen satırları varsayılan olarak filtrelemek için kullanılabilir, ancak yine de belirli bir sorgu için filtreyi devre dışı bırakarak belirli yerlerde bunlara erişmenizi sağlar.
Geçici silmeyi etkinleştirmek için Blog türümüze bir IsDeleted özellik ekleyelim:
public class Blog
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
public string Name { get; set; }
}
HasQueryFilter API'sini OnModelCreating içinde kullanarak şimdi bir genel sorgu filtresi ayarladık:
modelBuilder.Entity<Blog>().HasQueryFilter(b => !b.IsDeleted);
Artık varlıklarımızı Blog her zamanki gibi sorgulayabiliriz; yapılandırılan filtre, tüm sorguların varsayılan olarak doğru olan IsDeleted tüm örnekleri filtrelemesini sağlar.
Bu noktada, bir varlığı geçici olarak silmek için IsDeleted manuel olarak ayarlamanız gerektiğini unutmayın. Daha uçtan uca bir çözüm için, bağlam türünüzün SaveChangesAsync yöntemini geçersiz kılarak, kullanıcının sildiği tüm varlıkların üzerinden geçen ve bunları değiştirilecek şekilde değiştiren mantık ekleyebilir ve IsDeleted özelliğini true olarak ayarlayabilirsiniz.
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ChangeTracker.DetectChanges();
foreach (var item in ChangeTracker.Entries<Blog>().Where(e => e.State == EntityState.Deleted))
{
item.State = EntityState.Modified;
item.CurrentValues["IsDeleted"] = true;
}
return await base.SaveChangesAsync(cancellationToken);
}
Bu, bir varlık örneğini her zamanki gibi silmenizi ve bunun yerine geçici silme işlemi gerçekleştiren EF API'lerini kullanmanızı sağlar.
Bağlam verilerini kullanma - çoklu kiracı desteği
Genel sorgu filtreleri için bir diğer temel senaryo, uygulamanızın aynı tablodaki farklı kullanıcılara ait verileri depoladığı çoklu kiracılıktır. Böyle durumlarda, genellikle satırı belirli bir kiracıyla ilişkilendiren bir kiracı kimliği sütunu vardır ve genel sorgu filtreleri geçerli kiracının satırlarına otomatik olarak filtre uygulamak için kullanılabilir. Bu, sorgularınız için varsayılan olarak güçlü kiracı yalıtımı sağlar ve her sorguda kiracı için filtrelemeyi düşünme gereksinimini ortadan kaldırır.
Geçici silme işleminden farklı olarak, çok kiracılılık mevcut kiracı kimliğinin bilinmesini gerektirir; bu değer genellikle kullanıcı web üzerinden kimlik doğrularken belirlenir. EF'nin amaçları doğrultusunda, genel sorgu filtresinin buna başvurabilmesi ve sorgularken kullanabilmesi için bağlam örneğinde kiracı kimliğinin kullanılabilir olması gerekir. Bağlam türümüzün oluşturucusunda bir tenantId parametre kabul edelim ve filtremizden buna başvuralım:
public class MultitenancyContext(string tenantId) : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.TenantId == tenantId);
}
}
Bu, bağlam oluşturan herkesi ilişkili kiracı kimliğini belirtmeye zorlar ve varsayılan olarak sorgulardan yalnızca Blog bu kimliğe sahip varlıkların döndürülmesini sağlar.
Uyarı
Bu örnek yalnızca genel sorgu filtrelerini göstermek için gereken temel çok kiracılı kavramları göstermiştir. Çok kiracılılık ve EF hakkında daha fazla bilgi için bkz. EF Core uygulamalarında çok kiracılılık.
Birden çok sorgu filtresi kullanma
Basit bir filtreyle çağırmak HasQueryFilter önceki tüm filtrelerin üzerine yazılır, bu nedenle aynı varlık türünde şu şekilde birden çok filtre tanımlanamaz :
modelBuilder.Entity<Blog>().HasQueryFilter(b => !b.IsDeleted);
// The following overwrites the previous query filter:
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.TenantId == tenantId);
Uyarı
Bu özellik EF Core 10.0'da (önizlemede) kullanıma sunulmuştur.
Aynı varlık türünde birden çok sorgu filtresi tanımlamak için bunların adı verilmelidir:
modelBuilder.Entity<Blog>()
.HasQueryFilter("SoftDeletionFilter", b => !b.IsDeleted)
.HasQueryFilter("TenantFilter", b => b.TenantId == tenantId);
Bu, her filtreyi ayrı ayrı yönetmenize olanak tanır; bunlardan birini devre dışı bırakmanız, ancak diğerini devre dışı bırakmamanız mümkündür.
Filtreleri devre dışı bırakma
Filtreler, işleci kullanılarak IgnoreQueryFilters tek tek LINQ sorguları için devre dışı bırakılabilir:
var allBlogs = await context.Blogs.IgnoreQueryFilters().ToListAsync();
Birden çok adlandırılmış filtre yapılandırılırsa, bu tüm filtreleri devre dışı bırakır. Belirli filtreleri seçmeli olarak devre dışı bırakmak için (EF 10'dan başlayarak) devre dışı bırakılacak filtre adlarının listesini geçirin:
var allBlogs = await context.Blogs.IgnoreQueryFilters(["SoftDeletionFilter"]).ToListAsync();
Sorgu filtreleri ve gerekli navigasyonlar
Dikkat
Genel sorgu filtresi tanımlanmış olan varlığa erişmek için gerekli gezintinin kullanılması beklenmeyen sonuçlara neden olabilir.
EF'teki gerekli gezintiler, ilgili varlığın her zaman mevcut olmasını gerektirir. İç birleşimler ilgili varlıkları getirmek için kullanılabileceğinden, eğer bir sorgu filtresi tarafından gerekli bir ilgili varlık filtrelenirse, ana varlık da filtrelenebilir. Bu, beklenmeyen şekilde beklenenden daha az öğe alınmasına neden olabilir.
Sorunu göstermek için, Blog ve Post varlıklarını kullanabiliriz ve bunları aşağıdaki gibi yapılandırabiliriz:
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired();
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
Model, aşağıdaki verilerle tohumlanabilir:
db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/fish",
Posts =
[
new() { Title = "Fish care 101" },
new() { Title = "Caring for tropical fish" },
new() { Title = "Types of ornamental fish" }
]
});
db.Blogs.Add(
new Blog
{
Url = "http://sample.com/blogs/cats",
Posts =
[
new() { Title = "Cat care 101" },
new() { Title = "Caring for tropical cats" },
new() { Title = "Types of ornamental cats" }
]
});
Aşağıdaki iki sorgu yürütülürken sorun gözlemlenebilir:
var allPosts = await db.Posts.ToListAsync();
var allPostsWithBlogsIncluded = await db.Posts.Include(p => p.Blog).ToListAsync();
Yukarıdaki kurulumla, ilk sorgu 6 Post örneğin tümünü döndürür, ancak ikinci sorgu yalnızca 3 döndürür. Bu uyuşmazlık, ikinci sorgudaki Include yönteminin ilgili Blog varlıkları yüklemesinden kaynaklanır.
Blog ve Post arasında gezinti gerektiğinden, EF Core sorguyu oluştururken INNER JOIN kullanır:
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]
INNER JOIN, ilgili Post satırları sorgu filtresiyle filtrelenmiş olan tüm Blog satırlarını filtreler. Bu sorun, gezintiyi gerekli yerine isteğe bağlı olarak yapılandırarak ve EF'nin bir LEFT JOIN yerine bir INNER JOIN üretmesini sağlayarak giderilebilir.
modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).IsRequired(false);
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.Url.Contains("fish"));
Alternatif bir yaklaşım, hem Blog hem de Post varlık türlerinde tutarlı filtreler belirtmektir; hem Blog hem de Post üzerinde eşleşen filtreler uygulandığında, beklenmeyen bir durumda olabilecek Post satırlar kaldırılır ve her iki sorgu da 3 sonuç döndürür.
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"));
Sorgu filtreleri ve IEntityTypeConfiguration
Sorgu filtrenizin kiracı kimliğine veya benzer bağlam bilgilerine erişmesi gerekiyorsa, IEntityTypeConfiguration<TEntity> 'den farklı OnModelCreatingolarak ek bir karmaşıklık oluşturabilir; sorgu filtresinden başvurulabilecek bağlam türünüzün bir örneği yoktur. Geçici bir çözüm olarak, yapılandırma türünüze bir sahte bağlam ekleyin ve aşağıdaki şekilde ona başvurun:
private sealed class CustomerEntityConfiguration : IEntityTypeConfiguration<Customer>
{
private readonly SomeDbContext _context = null!;
public void Configure(EntityTypeBuilder<Customer> builder)
{
builder.HasQueryFilter(d => d.TenantId == _context.TenantId);
}
}
Sınırlamalar
Genel sorgu filtreleri aşağıdaki sınırlamalara sahiptir:
- Filtreler yalnızca devralma hiyerarşisinin kök varlık türü için tanımlanabilir.
- ŞU anda EF Core genel sorgu filtresi tanımlarındaki döngüleri algılamaz, bu nedenle bunları tanımlarken dikkatli olmanız gerekir. Yanlış belirtilirse, döngüler sorgu çevirisi sırasında sonsuz döngülere yol açabilir.