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.
DbContext havuzlama
A DbContext genellikle basit bir nesnedir: bir veritabanı işlemi oluşturma ve yok etme işlemi içermez ve çoğu uygulama bunu performans üzerinde belirgin bir etki olmadan yapabilir. Ancak, her bağlam örneği görevlerini gerçekleştirmek için gerekli çeşitli iç hizmetleri ve nesneleri ayarlar ve bunu sürekli yapma yükü yüksek performanslı senaryolarda önemli olabilir. Bu durumlarda, EF Core bağlam örneklerinizi havuzlayabilir: Bağlamınızı attığınızda, EF Core durumunu sıfırlar ve bir iç havuzda depolar; ardından yeni bir örnek istendiğinde, yeni bir örnek ayarlamak yerine, havuzdaki mevcut örnek döndürülür. Bağlam havuzu, bağlam kurulum maliyetlerini sürekli olarak değil, program başlangıcında yalnızca bir kez ödemenize olanak tanır.
Bağlam havuzunun, veritabanı sürücüsünde daha düşük bir düzeyde yönetilen veritabanı bağlantı havuzuna ortogonal olduğunu unutmayın.
bir ASP.NET Core uygulamasında EF Core kullanıldığında, DbContext türünü bağımlılık ekleme konteynerine AddDbContext aracılığıyla kayıt etmek tipik bir desendir. Ardından, bu türdeki örnekler denetleyicilerdeki veya Razor Sayfalarındaki oluşturucu parametreleri aracılığıyla elde edilir.
Bağlam havuzunu etkinleştirmek için, yalnızca AddDbContext öğesini AddDbContextPool ile değiştirmeniz yeterlidir.
builder.Services.AddDbContextPool<WeatherForecastContext>(
o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));
poolSize parametresiAddDbContextPool, havuz tarafından tutulan en fazla örnek sayısını ayarlar (varsayılan olarak 1024'tür). Bir kez poolSize aşıldığında, yeni bağlam örnekleri önbelleğe alınmaz ve EF isteğe bağlı örnek oluşturmanın havuz dışı davranışına geri döner.
Karşılaştırmalar
Aşağıda, bağlam havuzuyla ve bağlam havuzu olmadan aynı makinede yerel olarak çalışan bir SQL Server veritabanından tek bir satır getirmeye yönelik karşılaştırma sonuçları yer almaktadır. Her zaman olduğu gibi sonuçlar satır sayısı, veritabanı sunucunuzdaki gecikme süresi ve diğer faktörlerle değişir. Daha da önemlisi, bu, tek iş parçacıklı havuz performansını değerlendirirken, gerçek dünyada çatışmalı bir senaryoda farklı sonuçlar ortaya çıkabilir; herhangi bir karar vermeden önce platformunuzda test edin. Kaynak kodu burada bulabilirsiniz, kendi ölçümleriniz için temel olarak kullanmaktan çekinmeyin.
| Metot | NumBlogs | Ortalama | Hata | StdDev | 0. Nesil | 1. Nesil | 2. Nesil | Tahsis edilen |
|---|---|---|---|---|---|---|---|---|
| WithoutContextPooling | 1 | 701.6 bize | 26.62 bize | 78.48 bize | 11.7188 | - | - | 50,38 KB |
| WithContextPooling | 1 | 350.1 biz | 6.80 bize | 14.64 bize | 0.9766 | - | - | 4,63 KB |
Havuza alınan ortamlarda durumu yönetme
Bağlam havuzu, istekler arasında aynı bağlam örneğini yeniden kullanarak çalışır; bu, Singleton olarak etkili bir şekilde kaydedildiği ve aynı örneğin birden çok istekte (veya DI kapsamlarında) yeniden kullanıldığı anlamına gelir. Bu, bağlam istekler arasında değişebilecek herhangi bir durumu içerdiğinde özel özen gösterilmesi gerektiği anlamına gelir. Kritik öneme sahip olan bağlam OnConfiguring yalnızca bir kez çağrılır ( örnek bağlamı ilk oluşturulduğunda) ve bu nedenle değişiklik göstermesi gereken durumu ayarlamak için kullanılamaz (örneğin, kiracı kimliği).
Bağlam durumunu içeren tipik bir senaryo, bağlam örneğinin sorgular tarafından hesaba katılmış bir kiracı kimliğine sahip olduğu çok kiracılı bir ASP.NET Core uygulaması olacaktır (daha fazla ayrıntı için bkz. Genel Sorgu Filtreleri). Kiracı kimliğinin her web isteğiyle değişmesi gerektiğinden, bağlam havuzunu düzgün çalıştırabilmek için bazı ek adımlar izlememiz gerekir.
Uygulamanızın, kiracı kimliğini ve kiracıyla ilgili diğer bilgileri saran belirli kapsamlı ITenant bir hizmet kaydettiğini varsayalım:
// Below is a minimal tenant resolution strategy, which registers a scoped ITenant service in DI.
// In this sample, we simply accept the tenant ID as a request query, which means that a client can impersonate any
// tenant. In a real application, the tenant ID would be set based on secure authentication data.
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ITenant>(sp =>
{
var tenantIdString = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request.Query["TenantId"];
return tenantIdString != StringValues.Empty && int.TryParse(tenantIdString, out var tenantId)
? new Tenant(tenantId)
: null;
});
Yukarıda belirtildiği gibi, kiracı kimliğini nereden edindiğinize özellikle dikkat edin. Bu, uygulamanızın güvenliğinin önemli bir yönüdür.
Kapsamlı ITenant hizmetimizi sağladıktan sonra, her zamanki gibi bir havuz bağlamı fabrikasını tekil servis olarak kaydedin.
builder.Services.AddPooledDbContextFactory<WeatherForecastContext>(
o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));
Ardından, kaydettiğimiz Singleton fabrikasından birleştirilmiş bir bağlam elde eden ve kiracı kimliğini dağıttığı bağlam örneklerine enjekte eden özel bir bağlam fabrikası yazın.
public class WeatherForecastScopedFactory : IDbContextFactory<WeatherForecastContext>
{
private const int DefaultTenantId = -1;
private readonly IDbContextFactory<WeatherForecastContext> _pooledFactory;
private readonly int _tenantId;
public WeatherForecastScopedFactory(
IDbContextFactory<WeatherForecastContext> pooledFactory,
ITenant tenant)
{
_pooledFactory = pooledFactory;
_tenantId = tenant?.TenantId ?? DefaultTenantId;
}
public WeatherForecastContext CreateDbContext()
{
var context = _pooledFactory.CreateDbContext();
context.TenantId = _tenantId;
return context;
}
}
Özel bağlam fabrikamızı aldıktan sonra bunu Kapsamlı hizmet olarak kaydedin:
builder.Services.AddScoped<WeatherForecastScopedFactory>();
Son olarak, Scoped factory'den enjekte edilecek bir bağlam ayarlayın:
builder.Services.AddScoped(
sp => sp.GetRequiredService<WeatherForecastScopedFactory>().CreateDbContext());
Bu aşamada, denetleyicileriniz, doğru kiracı kimliğine sahip bir bağlam örneği ile otomatik olarak enjekte edilir ve bunun hakkında bir şey bilmek zorunda kalmazsınız.
Bu örneğin tam kaynak koduna buradan ulaşabilirsiniz.
Not
EF Core, DbContext ve ilgili hizmetlerin iç durumunu sıfırlamayı üstlenirken, genellikle Entity Framework dışında kalan temel veritabanı sürücüsündeki durumu sıfırlamaz. Örneğin, ADO.NET durumunu manuel olarak açar veya başka bir şekilde işlerseniz, bağlam örneğini havuza geri döndürmeden önce bu durumu geri yüklemek (örneğin bağlantıyı kapatarak) size bağlıdır. Bunun yapılmaması, durumun ilgisiz istekler arasında sızdırılmasına neden olabilir.
Bağlantı HavuzuYla İlgili Dikkat Edilmesi Gerekenler
Çoğu veritabanında, veritabanı işlemlerini gerçekleştirmek için uzun süreli bir bağlantı gereklidir ve bu tür bağlantıların açılması ve kapatılması pahalı olabilir. EF, bağlantı havuzunun kendisini uygulamaz, ancak veritabanı bağlantılarını yönetmek için temel alınan veritabanı sürücüsüne (örn. ADO.NET sürücü) dayanır. Bağlantı havuzu, bağlantıları tekrar tekrar açma ve kapatma yükünü azaltmak için mevcut veritabanı bağlantılarını yeniden kullanan bir istemci tarafı mekanizmasıdır. Bu mekanizma, Azure SQL Veritabanı, PostgreSQL ve diğerleri gibi EF tarafından desteklenen veritabanlarında genel olarak tutarlıdır. Ancak kaynak sınırları veya hizmet yapılandırmaları gibi veritabanına veya ortama özgü faktörler havuz verimliliğini etkileyebilir. Bağlantı havuzu genellikle varsayılan olarak etkindir ve tüm havuz yapılandırmaları bu sürücü tarafından belgelendiği gibi alt düzey sürücü düzeyinde gerçekleştirilmelidir; örneğin, ADO.NET kullanılırken, en düşük veya en büyük havuz boyutları gibi parametreler genellikle bağlantı dizesi aracılığıyla yapılandırılır.
Bağlantı havuzu, EF'nin yukarıda açıklanan DbContext havuzuna tamamen benzerdir: alt düzey veritabanı sürücüsü veritabanı bağlantılarını havuza alırken (bağlantıları açma/kapatma ek yükünü önlemek için), EF bağlam örneklerini havuza alabilir (bağlam belleği ayırma ve başlatma ek yüklerini önlemek için). Bağlam örneği havuzda olsun ya da olmasın, EF genellikle her işlemden (örneğin, sorgu) hemen önce bağlantıları açar ve hemen ardından kapatarak havuza geri döndürür; bu, bağlantıların gereğinden fazla süreyle havuz dışında kalmasını önlemek için yapılır.
Derlenmiş sorgular
EF yürütme için bir LINQ sorgu ağacı aldığında, önce bu ağacı "derlemeli", örneğin ondan SQL üretmelidir. Bu görev ağır bir işlem olduğundan, EF sorguları sorgu ağacı şekline göre önbelleğe alır, böylece aynı yapıya sahip sorgular dahili olarak önbelleğe alınmış derleme çıkışlarını yeniden kullanır. Bu önbelleğe alma, parametre değerleri farklı olsa bile aynı LINQ sorgusunu birden çok kez yürütmenin çok hızlı olmasını sağlar.
Ancak, EF'nin iç sorgu önbelleğini kullanabilmesi için bazı görevleri gerçekleştirmesi gerekir. Örneğin, doğru önbelleğe alınmış sorguyu bulmak için sorgunuzun ifade ağacı önbelleğe alınmış sorguların ifade ağaçlarıyla özyinelemeli olarak karşılaştırılmalıdır. Bu ilk işlemenin yükü, özellikle sorgu yürütmeyle ilişkili diğer maliyetlerle (ağ G/Ç, gerçek sorgu işleme ve veritabanında disk G/Ç...) karşılaştırıldığında EF uygulamalarının çoğunda göz ardı edilebilir. Ancak, bazı yüksek performanslı senaryolarda ortadan kaldırılması istenebilir.
EF, bir LINQ sorgusunun .NET temsilcisine açıkça derlenmesini sağlayan derlenmiş sorguları destekler. Bu temsilci alındıktan sonra, LINQ ifade ağacını sağlamadan sorguyu çalıştırmak için doğrudan kullanılabilir. Bu teknik, önbellek aramasını atlar ve EF Core'da sorgu yürütmenin en iyi duruma getirilmiş yolunu sağlar. Derlenmiş ve derlenmemiş sorgu performansını karşılaştıran bazı karşılaştırma sonuçları aşağıdadır; herhangi bir karar vermeden önce platformunuzda karşılaştırma yapın. Kaynak kodu burada bulabilirsiniz, kendi ölçümleriniz için temel olarak kullanmaktan çekinmeyin.
| Metot | NumBlogs | Ortalama | Hata | StdDev | 0. Nesil | Tahsis edilen |
|---|---|---|---|---|---|---|
| WithCompiledQuery | 1 | 564.2 bize | 6.75 bize | 5.99 bize | 1.9531 | 9 KB |
| WithoutCompiledQuery | 1 | 671.6 biz | 12.72 biz | 16.54 bize | 2.9297 | 13 KB |
| WithCompiledQuery | 10 | 645.3 bize | 10.00 bize | 9.35 mikrosaniye | 2.9297 | 13 KB |
| WithoutCompiledQuery | 10 | 709.8 biz | 25.20 bize | 73.10 bize | 3,9063 | 18 KB |
Derlenmiş sorguları kullanmak için önce aşağıdakiyle EF.CompileAsyncQuery bir sorgu derleyin (zaman uyumlu sorgular için kullanın EF.CompileQuery ):
private static readonly Func<BloggingContext, int, IAsyncEnumerable<Blog>> _compiledQuery
= EF.CompileAsyncQuery(
(BloggingContext context, int length) => context.Blogs.Where(b => b.Url.StartsWith("http://") && b.Url.Length == length));
Bu kod örneğinde EF'e bir örneği kabul eden bir DbContext lambda ve sorguya geçirilecek rastgele bir parametre sunuyoruz. Artık sorguyu yürütmek istediğinizde bu temsilciyi çağırabilirsiniz:
await foreach (var blog in _compiledQuery(context, 8))
{
// Do something with the results
}
Temsilcinin iş parçacığı açısından güvenli olduğunu ve farklı bağlam örneklerinde eşzamanlı olarak çağrılabileceğini unutmayın.
Sınırlamalar
- Derlenmiş sorgular yalnızca tek bir EF Core modelde kullanılabilir. Aynı türdeki farklı bağlam örnekleri bazen farklı modeller kullanacak şekilde yapılandırılabilir; bu senaryoda derlenmiş sorguların çalıştırılması desteklenmez.
- Derlenmiş sorgularda parametreleri kullanırken basit, skaler parametreler kullanın. Örneklerde üye/yöntem erişimi gibi daha karmaşık parametre ifadeleri desteklenmez.
Sorgu önbelleğe alma ve parametreleştirme
EF yürütme için bir LINQ sorgu ağacı aldığında, önce bu ağacı "derlemeli", örneğin ondan SQL üretmelidir. Bu görev ağır bir işlem olduğundan, EF sorguları sorgu ağacı şekline göre önbelleğe alır, böylece aynı yapıya sahip sorgular dahili olarak önbelleğe alınmış derleme çıkışlarını yeniden kullanır. Bu önbelleğe alma, parametre değerleri farklı olsa bile aynı LINQ sorgusunu birden çok kez yürütmenin çok hızlı olmasını sağlar.
Aşağıdaki iki sorguyu göz önünde bulundurun:
var post1 = await context.Posts.FirstOrDefaultAsync(p => p.Title == "post1");
var post2 = await context.Posts.FirstOrDefaultAsync(p => p.Title == "post2");
İfade ağaçları farklı sabitler içerdiğinden, ifade ağacı farklılık gösterir ve bu sorguların her biri EF Core tarafından ayrı ayrı derlenir. Buna ek olarak, her sorgu biraz farklı bir SQL komutu üretir:
SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Posts] AS [b]
WHERE [b].[Name] = N'post1'
SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Posts] AS [b]
WHERE [b].[Name] = N'post2'
SQL farklı olduğundan veritabanı sunucunuzun aynı planı yeniden kullanmak yerine her iki sorgu için de bir sorgu planı oluşturması gerekebilir.
Sorgularınızda küçük bir değişiklik yapılması işleri önemli ölçüde değiştirebilir:
var postTitle = "post1";
var post1 = await context.Posts.FirstOrDefaultAsync(p => p.Title == postTitle);
postTitle = "post2";
var post2 = await context.Posts.FirstOrDefaultAsync(p => p.Title == postTitle);
Blog adı artık parametreli hale getirildiğinden, her iki sorgu da aynı ağaç şekline sahiptir ve EF'nin yalnızca bir kez derlenmesi gerekir. Üretilen SQL de parametrelendirilerek veritabanının aynı sorgu planını yeniden kullanmasına olanak sağlar:
SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Posts] AS [b]
WHERE [b].[Name] = @__postTitle_0
Her sorguyu parametreleştirmeye gerek olmadığını unutmayın: sabitleri olan bazı sorguların olması son derece uygundur ve aslında veritabanları (ve EF) bazen sorgu parametrelendirildiğinde mümkün olmayan sabitler etrafında belirli iyileştirmeler gerçekleştirebilir. Düzgün parametreleştirmenin kritik öneme sahip olduğu bir örnek için dinamik olarak derlenmiş sorgular bölümüne bakın.
Not
EF Core'un ölçümleri Sorgu Önbelleği İsabet Oranını bildirir. Normal bir uygulamada, çoğu sorgu en az bir kez yürütüldükten sonra bu ölçüm program başlangıcından hemen sonra %100'e ulaşır. Bu ölçüm %100'ün altında kararlı kalırsa bu, uygulamanızın sorgu önbelleğini yenen bir şey yapıyor olabileceğinin bir göstergesidir. Bunu araştırmak iyi bir fikirdir.
Tip
EF Core, derlenmiş sorguların ve modellerin iç önbelleğe alınmasını kullanır IMemoryCache . Gerekirse önbellek boyutu sınırını yapılandırabilirsiniz. Daha fazla bilgi için bkz. Bellek Önbelleği Tümleştirmesi .
Not
Veritabanının önbellek sorgu planlarını yönetme şekli veritabanına bağlıdır. Örneğin, SQL Server örtük olarak bir LRU sorgu planı önbelleği tutarken PostgreSQL bunu yapmaz (ancak hazırlanan deyimler çok benzer bir son etki oluşturabilir). Daha fazla ayrıntı için veritabanı belgelerinize bakın.
Dinamik olarak derlenmiş sorgular
Bazı durumlarda, LINQ sorgularını kaynak kodda tam olarak belirtmek yerine dinamik olarak oluşturmak gerekir. Örneğin, açık uçlu sorgu işleçleriyle (sıralama, filtreleme, sayfalama...) istemciden rastgele sorgu ayrıntıları alan bir web sitesinde bu durum oluşabilir. Prensipte, doğru yapılırsa, dinamik olarak yapılandırılmış sorgular normal sorgular kadar verimli olabilir (ancak derlenmiş sorgu iyileştirmesini dinamik sorgularla kullanmak mümkün değildir). Ancak uygulamada, genellikle performans sorunlarının kaynağıdır, çünkü her seferinde farklı şekillere sahip ifade ağaçlarını yanlışlıkla üretmek kolaydır.
Aşağıdaki örnekte, sorgunun Where lambda ifadesini oluşturmak için üç teknik kullanılır:
- Sabit olan ifade API'si: İfade API'siyle ifadeyi dinamik olarak, sabit bir düğüm kullanarak oluşturun. Bu, ifade ağaçlarını dinamik olarak oluştururken sık yapılan bir hatadır ve EF'nin farklı bir sabit değerle her çağrıldığında sorguyu yeniden derlemesine neden olur (genellikle veritabanı sunucusunda plan önbelleği kirliliğine de neden olur).
- Parametresi olan ifade API'si: Sabiti bir parametreyle değiştiren daha iyi bir sürüm. Bu, sorgunun sağlanan değerden bağımsız olarak yalnızca bir kez derlendiğinden ve aynı (parametreli) SQL'in oluşturulmasından emin olur.
- Parametresiyle basit: Karşılaştırma için İfade API'sini kullanmayan ve yukarıdaki yöntemle aynı ağacı oluşturan ancak çok daha basit olan bir sürüm. Çoğu durumda İfade API'sine başvurmadan ifade ağacınızı dinamik olarak oluşturmak mümkündür ve bu da kolayca yanlış anlaşılır.
Sorguya yalnızca verilen parametre null değilse bir Where işleç ekleriz. Bunun bir sorguyu dinamik olarak oluşturmak için iyi bir kullanım örneği olmadığını, ancak basitlik için kullandığımızı unutmayın:
[Benchmark]
public async Task<int> ExpressionApiWithConstant()
{
var url = "blog" + Interlocked.Increment(ref _blogNumber);
using var context = new BloggingContext();
IQueryable<Blog> query = context.Blogs;
if (_addWhereClause)
{
var blogParam = Expression.Parameter(typeof(Blog), "b");
var whereLambda = Expression.Lambda<Func<Blog, bool>>(
Expression.Equal(
Expression.MakeMemberAccess(
blogParam,
typeof(Blog).GetMember(nameof(Blog.Url)).Single()),
Expression.Constant(url)),
blogParam);
query = query.Where(whereLambda);
}
return await query.CountAsync();
}
Bu iki tekniğin karşılaştırması aşağıdaki sonuçları verir:
| Metot | Ortalama | Hata | StdDev | Gen0 | 1. Nesil | Tahsis edilen |
|---|---|---|---|---|---|---|
| Sabit ile İfade API'si | 1,665,8 bize | 56.99 bize | 163.5 biz | 15.6250 | - | 109,92 KB |
| ExpressionApiWithParameter | 757.1 biz | 35.14 bize | 103.6 bize | 12,6953 | 0.9766 | 54,95 KB |
| SimpleWithParameter | 760.3 bize | 37.99 bize | 112.0 bize | 12,6953 | - | 55,03 KB |
Milisaniyenin altındaki fark küçük görünse bile, sabit sürümün önbelleği sürekli olarak kirlettiğine ve diğer sorguların yeniden derlenmesine neden olduğunu, bunları yavaşlattığını ve genel performansınızı genel olarak olumsuz etkilediğini unutmayın. Sürekli sorgu yeniden derlemesinden kaçınılması kesinlikle önerilir.
Not
Gerçekten gerekmedikçe ifade ağacı API'si ile sorgu oluşturmaktan kaçının. API'nin karmaşıklığının yanı sıra, bunları kullanırken yanlışlıkla önemli performans sorunlarına neden olmak çok kolaydır.
Derlenmiş modeller
Derlenmiş modeller, büyük modellere sahip uygulamalar için EF Core başlangıç süresini iyileştirebilir. Büyük bir model genellikle yüz binlerce varlık türü ve ilişkisi anlamına gelir. Uygulamada ilk kez DbContext türü kullanıldığında DbContext üzerinde ilk işlemi gerçekleştirme süresi burada başlangıç süresidir. Yalnızca örnek DbContext oluşturmanın EF modelinin başlatılmasına neden olmadığını unutmayın. Bunun yerine, modelin başlatılmasına neden olan tipik ilk işlemler ilk sorguyu çağırmak DbContext.Add veya yürütmektir.
Derlenen modeller komut satırı aracı kullanılarak dotnet ef oluşturulur.
Devam etmeden önce aracın en son sürümünü yüklediğinizden emin olun.
Derlenmiş modeli oluşturmak için yeni dbcontext optimize bir komut kullanılır. Örneğin:
dotnet ef dbcontext optimize
--output-dir ve --namespace seçenekleri, derlenen modelin oluşturulacağı dizini ve ad alanını belirtmek için kullanılabilir. Örneğin:
PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels> dotnet ef dbcontext optimize --output-dir MyCompiledModels --namespace MyCompiledModels
Build started...
Build succeeded.
Successfully generated a compiled model, to use it call 'options.UseModel(MyCompiledModels.BlogsContextModel.Instance)'. Run this command again when the model is modified.
PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels>
- Daha fazla bilgi için bkz.
dotnet ef dbcontext optimize. - Visual Studio'da daha rahat çalışıyorsanız Optimize-DbContext özelliğini de kullanabilirsiniz
Bu komutun çalıştırılmasından elde edilen çıkış, EF Core'un derlenmiş modeli kullanmasına neden olmak için yapılandırmanıza DbContext kopyalayıp yapıştıracak bir kod parçası içerir. Örneğin:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseModel(MyCompiledModels.BlogsContextModel.Instance)
.UseSqlite(@"Data Source=test.db");
Derlenmiş model önyüklemesi
Genellikle oluşturulan önyükleme koduna bakmak gerekmez. Ancak bazen modeli veya yüklemesini özelleştirmek yararlı olabilir. Bootstrapping kodu şuna benzer:
[DbContext(typeof(BlogsContext))]
partial class BlogsContextModel : RuntimeModel
{
private static BlogsContextModel _instance;
public static IModel Instance
{
get
{
if (_instance == null)
{
_instance = new BlogsContextModel();
_instance.Initialize();
_instance.Customize();
}
return _instance;
}
}
partial void Initialize();
partial void Customize();
}
Bu, modeli gerektiği gibi özelleştirmek için uygulanabilen kısmi yöntemlere sahip kısmi bir sınıftır.
Ayrıca, bazı çalışma zamanı ayarlarına bağlı olarak farklı modeller kullanabilecek DbContext türler için birden çok derlenmiş model oluşturulabilir. Bunlar, yukarıda gösterildiği gibi farklı klasörlere ve ad alanlarına yerleştirilmelidir. Daha sonra bağlantı dizesi gibi çalışma zamanı bilgileri incelenebilir ve gerektiğinde doğru model döndürülebilir. Örneğin:
public static class RuntimeModelCache
{
private static readonly ConcurrentDictionary<string, IModel> _runtimeModels
= new();
public static IModel GetOrCreateModel(string connectionString)
=> _runtimeModels.GetOrAdd(
connectionString, cs =>
{
if (cs.Contains("X"))
{
return BlogsContextModel1.Instance;
}
if (cs.Contains("Y"))
{
return BlogsContextModel2.Instance;
}
throw new InvalidOperationException("No appropriate compiled model found.");
});
}
Sınırlamalar
Derlenen modellerin bazı sınırlamaları vardır:
- Genel sorgu filtreleri desteklenmez.
- Gecikmeli yükleme ve değişiklik izleme proxy'leri desteklenmez.
- Özel yöntemlere başvuran değer dönüştürücüleri desteklenmez. Bunun yerine başvuruda bulunılan yöntemleri genel veya dahili yapın.
- Model tanımı veya yapılandırma değiştiğinde model el ile yeniden oluşturularak eşitlenmelidir.
- Özel IModelCacheKeyFactory uygulamaları desteklenmez. Ancak, birden çok modeli derleyebilir ve gerektiği gibi uygun olanı yükleyebilirsiniz.
Bu sınırlamalar nedeniyle, derlenmiş modelleri yalnızca EF Core başlangıç süreniz çok yavaşsa kullanmanız gerekir. Küçük modelleri derlemek genellikle buna değmez.
Bu özelliklerden herhangi birinin desteklenmesi başarınız için kritik önem taşıyorsa lütfen yukarıda belirtilen uygun sorunlar için oy verin.
Belirsiz tür başvuruları nedeniyle derleme hatalarını işleme
Aynı ada sahip ancak farklı ad alanlarına sahip türlere sahip modeller derlenirken, oluşturulan kod belirsiz tür başvuruları nedeniyle derleme hataları üretebilir. Bu sorunu çözmek için, kod oluşturmayı tam niteleyici tür adlarını kullanacak şekilde özelleştirmek amacıyla CSharpHelper.ShouldUseFullName'yi geçersiz kılarak true döndürebilirsiniz. gibi ICSharpHelper geçersiz kılma hakkında bilgi için bkz. Tasarım zamanı hizmetleri.
Çalışma zamanı ek yükünü azaltma
Her katmanda olduğu gibi EF Core da doğrudan alt düzey veritabanı API'lerine karşı kodlamaya kıyasla biraz çalışma zamanı yükü ekler. Bu çalışma zamanı ek yükünün gerçek dünya uygulamalarının çoğunu önemli ölçüde etkileme olasılığı düşüktür; bu performans kılavuzundaki sorgu verimliliği, dizin kullanımı ve gidiş dönüşleri en aza indirme gibi diğer konular çok daha önemlidir. Buna ek olarak, son derece optimize edilmiş uygulamalar için bile ağ gecikmesi ve veritabanı girdi/çıktı işlemleri, genellikle EF Core'un içinde harcanan süreye hakim olur. Ancak, her performans bitinin önemli olduğu yüksek performanslı, düşük gecikme süreli uygulamalarda EF Core ek yükünü en aza indirgemek için aşağıdaki öneriler kullanılabilir:
-
DbContext havuzunu açın; karşılaştırmalarımız bu özelliğin yüksek performans ve düşük gecikme süresine sahip uygulamalar üzerinde belirleyici bir etkiye sahip olabileceğini gösteriyor.
-
maxPoolSizeöğesinin kullanım senaryonuza uygun olduğundan emin olun; fazla düşükseDbContextörnekleri sürekli oluşturulup atılarak performansı düşürür. Çok yüksek olarak ayarlamak, kullanılmayanDbContextörnekler havuzda tutuldukçe gereksiz bir şekilde bellek tüketebilir. - Ekstra küçük bir performans artışı için, bağlam örneklerini doğrudan DI ile eklemek yerine
PooledDbContextFactorykullanmayı göz önünde bulundurun.DbContexthavuzlamanın DI yönetimi küçük bir ek yüke neden olur.
-
- Sık erişimli sorgular için önceden derlenmiş sorgular kullanın.
- LINQ sorgusu ne kadar karmaşıksa ve ne kadar çok işleç içeriyorsa ve sonuçta elde edilen ifade ağacı o kadar büyükse, derlenmiş sorguların kullanılmasından o kadar fazla kazanç beklenebilir.
- Bağlam yapılandırmanızda
EnableThreadSafetyChecksdeğerini false yaparak iş parçacığı güvenliği kontrollerini devre dışı bırakmayı düşünün.- Farklı iş parçacıklarından aynı
DbContextörneğinin eşzamanlı olarak kullanılması desteklenmez. EF Core' un çoğu durumda (tümü değil) bu programlama hatalarını algılayan ve hemen bilgilendirici bir özel durum oluşturan bir güvenlik özelliği vardır. Ancak, bu güvenlik özelliği çalışma zamanı ek yükü ekler. - UYARI: Yalnızca uygulamanızın bu tür eşzamanlılık hataları içermediğini kapsamlı bir şekilde test ettikten sonra iş parçacığı güvenliği denetimlerini devre dışı bırakın.
- Farklı iş parçacıklarından aynı
Bellek Önbelleği Tümleştirmesi
EF Core, sorgu derleme ve model oluşturma gibi iç önbelleğe alma işlemleri için kullanır IMemoryCache . Varsayılan olarak, EF Core kendi IMemoryCache boyutunu 10240 boyut sınırıyla yapılandırmaktadır. Başvuru için derlenmiş sorgunun önbellek boyutu 10 iken, oluşturulan modelin önbellek boyutu 100'tür.
Varsayılan önbellek boyutu sınırını değiştirmeniz gerekiyorsa, özel UseMemoryCache bir örnek sağlamak için kullanınIMemoryCache:
var memoryCache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 20480 });
services.AddSingleton<IDisposable>(memoryCache);
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseMemoryCache(memoryCache);
options.UseSqlServer(connectionString);
});
MemoryCache örneği, uygulama genelindeki IMemoryCache yerine geçmeden, hizmet sağlayıcısı atıldığında atılacak şekilde IDisposable olarak kaydedilir.
Alternatif olarak, DI aracılığıyla IMemoryCache özel bir AddMemoryCache kaydederseniz, bunu sağlayıcıdan çözümleyebilirsiniz. Bunun EF Core ile IMemoryCache ile diğer hizmetler arasında önbelleği paylaştığını unutmayın, bu nedenle boyut sınırı tüm tüketicileri hesaba katmalıdır.
SizeLimit ayarlandığında, her tüketiciden gelen tüm önbellek girdilerinin bir boyut belirtmesi gerekir; aksi takdirde, girdiler eklenirken IMemoryCache bir hata fırlatır.
services.AddMemoryCache(options => options.SizeLimit = 20480);
services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
{
options.UseMemoryCache(serviceProvider.GetRequiredService<IMemoryCache>());
options.UseSqlServer(connectionString);
});