Aracılığıyla paylaş


EF Core 9'daki Yenilikler

EF Core 9 (EF9), EF Core 8'den sonraki sürümdür ve Kasım 2024'te kullanıma sunulacaktır.

EF9, en son EF9 özelliklerini ve API ince ayarlarını içeren günlük derlemeler olarak kullanılabilir. Buradaki örneklerde bu günlük derlemeler kullanılır.

İpucu

GitHub'dan örnek kodu indirerek örnekleri çalıştırabilir ve hata ayıklayabilirsiniz. Aşağıdaki her bölüm, o bölüme özgü kaynak koduna bağlanır.

EF9, .NET 8'i hedefler ve bu nedenle .NET 8 (LTS) veya .NET 9 ile kullanılabilir.

İpucu

Yenilikler belgeleri her önizleme için güncelleştirilir. Tüm örnekler ef9 günlük derlemelerini kullanacak şekilde ayarlanmıştır ve bu derlemeler genellikle en son önizlemeye kıyasla birkaç hafta daha tamamlanan çalışmaya sahiptir. Testinizi eski bitlere karşı yapmamanız için yeni özellikleri test ederken günlük derlemelerin kullanılmasını kesinlikle öneririz.

NoSQL için Azure Cosmos DB

EF 9.0, Azure Cosmos DB için EF Core sağlayıcısına önemli geliştirmeler getirir; sağlayıcının önemli bölümleri yeni işlevler sağlamak, yeni sorgu biçimlerine izin vermek ve sağlayıcıyı Azure Cosmos DB en iyi yöntemleriyle daha iyi hizalamak için yeniden yazılmıştır. Ana üst düzey iyileştirmeler aşağıda listelenmiştir; tam liste için bu epic sorununa bakın.

Uyarı

Sağlayıcıya giden geliştirmelerin bir parçası olarak, bir dizi yüksek etkili hataya neden olan değişikliklerin yapılması gerekiyordu; Mevcut bir uygulamayı yükseltiyorsanız, lütfen hataya neden olan değişiklikler bölümünü dikkatle okuyun.

Bölüm anahtarları ve belge kimlikleriyle sorgulama iyileştirmeleri

Azure Cosmos DB veritabanında depolanan her belgenin benzersiz bir kaynak kimliği vardır. Buna ek olarak, her belge, veritabanının etkili bir şekilde ölçeklendirilebileceği şekilde verilerin mantıksal bölümlemesi belirleyen bir "bölüm anahtarı" içerebilir. Bölüm anahtarlarını seçme hakkında daha fazla bilgi için Bkz. Azure Cosmos DB'de bölümleme ve yatay ölçeklendirme.

EF 9.0'da, Azure Cosmos DB sağlayıcısı LINQ sorgularınızdaki bölüm anahtarı karşılaştırmalarını belirleme ve sorgularınızın yalnızca ilgili bölüme gönderilmesini sağlamak için bunları ayıklama konusunda önemli ölçüde daha iyidir; bu, sorgularınızın performansını büyük ölçüde artırabilir ve RU ücretlerini azaltabilir. Örneğin:

var sessions = await context.Sessions
    .Where(b => b.PartitionKey == "someValue" && b.Username.StartsWith("x"))
    .ToListAsync();

Bu sorguda sağlayıcı karşılaştırmayı PartitionKeyotomatik olarak tanır; günlükleri incelediğimizde aşağıdakileri göreceğiz:

Executed ReadNext (189.8434 ms, 2.8 RU) ActivityId='8cd669ed-2ca5-4f2b-8923-338899071361', Container='test', Partition='["someValue"]', Parameters=[]
SELECT VALUE c
FROM root c
WHERE STARTSWITH(c["Username"], "x")

yan tümcesinin WHERE içermediğini PartitionKeyunutmayın: karşılaştırma "kaldırıldı" ve sorguyu yalnızca ilgili bölüme karşı yürütmek için kullanılır. Önceki sürümlerde karşılaştırma birçok durumda yan tümcesinde WHERE bırakılarak sorgunun tüm bölümlerde yürütülmesine ve maliyetlerin artmasına ve performansın düşmesine neden oldu.

Ayrıca, sorgunuz belgenin KIMLIK özelliği için bir değer de sağlıyorsa ve başka sorgu işlemleri içermiyorsa sağlayıcı ek bir iyileştirme uygulayabilir:

var somePartitionKey = "someValue";
var someId = 8;
var sessions = await context.Sessions
    .Where(b => b.PartitionKey == somePartitionKey && b.Id == someId)
    .SingleAsync();

Günlükler bu sorgu için aşağıdakileri gösterir:

Executed ReadItem (73 ms, 1 RU) ActivityId='13f0f8b8-d481-47f0-bf41-67f7deb008b2', Container='test', Id='8', Partition='["someValue"]'

Burada hiçbir SQL sorgusu gönderilmez. Bunun yerine sağlayıcı, bölüm anahtarı ve kimliği verilen belgeyi doğrudan getiren son derece verimli bir nokta okuma (ReadItem API) gerçekleştirir. Bu, Azure Cosmos DB'de gerçekleştirebileceğiniz en verimli ve uygun maliyetli okuma türüdür; Nokta okumaları hakkında daha fazla bilgi için Azure Cosmos DB belgelerine bakın.

Bölüm anahtarları ve nokta okumalarıyla sorgulama hakkında daha fazla bilgi edinmek için sorgulama belgeleri sayfasına bakın.

Hiyerarşik bölüm anahtarları

İpucu

Burada gösterilen kod HierarchicalPartitionKeysSample.cs.

Azure Cosmos DB başlangıçta tek bir bölüm anahtarını desteklemiştir, ancak o zamandan beri bölüm anahtarında en fazla üç hiyerarşi düzeyi belirtimi aracılığıyla alt bölümlemesi destekleyecek şekilde bölümleme özelliklerini genişletmiştir. EF Core 9, hiyerarşik bölüm anahtarları için tam destek sağlayarak bu özellik ile ilişkili daha iyi performans ve maliyet tasarruflarından yararlanmanızı sağlar.

Bölüm anahtarları, genellikle içinde model oluşturma API'si DbContext.OnModelCreatingkullanılarak belirtilir. Bölüm anahtarının her düzeyi için varlık türünde eşlenmiş bir özellik olmalıdır. Örneğin, bir UserSession varlık türünü göz önünde bulundurun:

public class UserSession
{
    // Item ID
    public Guid Id { get; set; }

    // Partition Key
    public string TenantId { get; set; } = null!;
    public Guid UserId { get; set; }
    public int SessionId { get; set; }

    // Other members
    public string Username { get; set; } = null!;
}

Aşağıdaki kod, , TenantIdve UserId özelliklerini kullanan SessionIdüç düzeyli bir bölüm anahtarı belirtir:

modelBuilder
    .Entity<UserSession>()
    .HasPartitionKey(e => new { e.TenantId, e.UserId, e.SessionId });

İpucu

Bu bölüm anahtarı tanımı, Azure Cosmos DB belgelerinden hiyerarşik bölüm anahtarlarınızı seçme bölümünde verilen örneği izler.

EF Core 9'dan başlayarak, bölüm anahtarında eşlenmiş herhangi bir türün özelliklerinin nasıl kullanılabildiğini görebilirsiniz. ve bool özelliği gibi sayısal türler için int SessionId değeri doğrudan bölüm anahtarında kullanılır. özelliği gibi Guid UserId diğer türler otomatik olarak dizelere dönüştürülür.

Sorgularken EF, sorgulardan bölüm anahtarı değerlerini otomatik olarak ayıklar ve sorguların mümkün olan en az sayıda bölümle uygun şekilde kısıtlandığından emin olmak için bunları Azure Cosmos DB sorgu API'sine uygular. Örneğin, hiyerarşideki üç bölüm anahtarı değerini de sağlayan aşağıdaki LINQ sorgusunu göz önünde bulundurun:

var tenantId = "Microsoft";
var sessionId = 7;
var userId = new Guid("99A410D7-E467-4CC5-92DE-148F3FC53F4C");

var sessions = await context.Sessions
    .Where(
        e => e.TenantId == tenantId
             && e.UserId == userId
             && e.SessionId == sessionId
             && e.Username.Contains("a"))
    .ToListAsync();

Ef Core bu sorguyu yürütürken , tenantIdve parametrelerinin userIddeğerlerini ayıklar ve sessionId bunları bölüm anahtarı değeri olarak Azure Cosmos DB sorgu API'sine geçirir. Örneğin, yukarıdaki sorguyu yürütme günlüklerine bakın:

info: 6/10/2024 19:06:00.017 CosmosEventId.ExecutingSqlQuery[30100] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executing SQL query for container 'UserSessionContext' in partition '["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",7.0]' [Parameters=[]]
      SELECT c
      FROM root c
      WHERE ((c["Discriminator"] = "UserSession") AND CONTAINS(c["Username"], "a"))

Bölüm anahtarı karşılaştırmalarının yan tümcesinden kaldırıldığına WHERE ve bunun yerine verimli yürütme için bölüm anahtarı olarak kullanıldığına dikkat edin: ["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",7.0].

Daha fazla bilgi için bölüm anahtarlarıyla sorgulama belgelerine bakın.

Önemli ölçüde iyileştirilmiş LINQ sorgulama özellikleri

EF 9.0'da, Azure Cosmos DB sağlayıcısının LINQ çeviri özellikleri büyük ölçüde genişletilmiştir ve sağlayıcı artık çok daha fazla sorgu türü yürütebilir. Sorgu geliştirmelerinin tam listesi listelenemeyecek kadar uzun, ancak önemli noktalar şunlardır:

  • EF'nin ilkel koleksiyonları için tam destek, örneğin int veya dize koleksiyonlarında LINQ sorgulaması gerçekleştirmenizi sağlar. Daha fazla bilgi için bkz . EF8'deki yenilikler: temel koleksiyonlar .
  • İlkel olmayan koleksiyonlar üzerinde rastgele sorgulama desteği.
  • Artık birçok ek LINQ işleci desteklenmektedir: koleksiyonlar, Length/Count, ElementAt, Containsve diğerleri için dizin oluşturma.
  • ve Countgibi Sum toplama işleçleri desteği.
  • Ek işlev çevirileri (desteklenen çevirilerin tam listesi için işlev eşlemeleri belgelerine bakın):
    • ve DateTime bileşen üyeleri (DateTimeOffset, DateTime.Year...) için DateTimeOffset.Month çeviriler.
    • EF.Functions.IsDefined ve EF.Functions.CoalesceUndefined şimdi değerlerle undefined ilgilenmeye izin verin.
    • string.Containsve StartsWithEndsWith şimdi destekle StringComparison.OrdinalIgnoreCase.

Sorgulama iyileştirmelerinin tam listesi için şu soruna bakın:

Azure Cosmos DB ve JSON standartlarına uygun geliştirilmiş modelleme

EF 9.0, JSON tabanlı bir belge veritabanı için daha doğal yollarla Azure Cosmos DB belgeleriyle eşlenir ve belgelerinize erişen diğer sistemlerle birlikte çalışmanıza yardımcı olur. Bu hataya neden olan değişikliklere neden olsa da, api'ler her durumda 9.0 öncesi davranışa geri dönülmesine olanak sağlar.

Ayrımcı olmadan basitleştirilmiş id özellikler

İlk olarak, EF'nin önceki sürümleri ayrıştırıcı değerini JSON id özelliğine ekleyerek aşağıdaki gibi belgeler üretti:

{
    "id": "Blog|1099",
    ...
}

Bu, farklı türlerdeki belgelerin (örneğin Blog ve Gönderi) ve aynı anahtar değerinin (1099) aynı kapsayıcı bölümünde mevcut olmasını sağlamak için yapıldı. EF 9.0'dan başlayarak özelliği id yalnızca anahtar değerini içerir:

{
    "id": 1099,
    ...
}

Bu, JSON ile eşlemenin daha doğal bir yoludur ve dış araçların ve sistemlerin EF tarafından oluşturulan JSON belgeleriyle etkileşim kurmasını kolaylaştırır; bu tür dış sistemler, varsayılan olarak .NET türlerinden türetilen EF ayırıcı değerlerinin genel olarak farkında değildir.

EF artık var olan belgeleri eski id biçimde sorgulayamayacağından, bu hataya neden olan bir değişikliktir. Önceki davranışa geri dönmek için bir API kullanıma sunulmuştur. Daha fazla ayrıntı için hataya neden olan değişiklik notunuve belgelere bakın .

Discriminator özelliği olarak yeniden adlandırıldı $type

Varsayılan ayrıştırıcı özelliği daha önce olarak adlandırılmıştı Discriminator. EF 9.0 varsayılanı olarak $typedeğiştirir:

{
    "id": 1099,
    "$type": "Blog",
    ...
}

Bu, JSON polimorfizmi için ortaya çıkan standardı izler ve diğer araçlarla daha iyi birlikte çalışabilirlik sağlar. Mesela. NET'in System.Text.Json'u, varsayılan ayrımcı özellik adı ($type) olarak kullanarak polimorfizmi de destekler.

EF artık eski ayrımcı özellik adıyla mevcut belgeleri sorgulayamayacağından, bunun hataya neden olan bir değişiklik olduğunu unutmayın. Önceki adlandırmaya geri dönme hakkında ayrıntılı bilgi için hataya neden olan değişiklik notunu inceleyin.

Vektör benzerlik araması (önizleme)

Azure Cosmos DB artık vektör benzerliği araması için önizleme desteği sunuyor. Vektör araması, yapay zeka, anlamsal arama ve diğerleri gibi bazı uygulama türlerinin temel bir parçasıdır. Azure Cosmos DB, vektörleri verilerinizin geri kalanıyla birlikte doğrudan belgelerinizde depolamanıza olanak tanır. Bu sayede tüm sorgularınızı tek bir veritabanında gerçekleştirebilirsiniz. Bu, mimarinizi önemli ölçüde basitleştirebilir ve yığınınızda ek, ayrılmış bir vektör veritabanı çözümü gereksinimini ortadan kaldırabilir. Azure Cosmos DB vektör araması hakkında daha fazla bilgi edinmek için belgelere bakın.

Azure Cosmos DB kapsayıcınız düzgün bir şekilde ayarlandıktan sonra EF aracılığıyla vektör araması kullanmak, vektör özelliği eklemek ve yapılandırmak için basit bir işlemdir:

public class Blog
{
    ...

    public float[] Vector { get; set; }
}

public class BloggingContext
{
    ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Embeddings)
            .IsVector(DistanceFunction.Cosine, dimensions: 1536);
    }
}

İşlem tamamlandıktan sonra, vektör benzerliği araması yapmak için LINQ sorgularındaki işlevini kullanın EF.Functions.VectorDistance() :

var blogs = await context.Blogs
    .OrderBy(s => EF.Functions.VectorDistance(s.Vector, vector))
    .Take(5)
    .ToListAsync();

Daha fazla bilgi için vektör arama belgelerine bakın.

Sayfalandırma desteği

Azure Cosmos DB sağlayıcısı artık süreklilik belirteçleri aracılığıyla sorgu sonuçları arasında sayfalandırmaya olanak tanır. Bu, ve 'nin geleneksel kullanımına SkipTakekıyasla çok daha verimli ve uygun maliyetlidir:

var firstPage = await context.Posts
    .OrderBy(p => p.Id)
    .ToPageAsync(pageSize: 10, continuationToken: null);

var continuationToken = firstPage.ContinuationToken;
foreach (var post in page.Values)
{
    // Display/send the posts to the user
}

Yeni ToPageAsync işleç, sonraki bir noktada sorguyu verimli bir şekilde sürdürmek için kullanılabilecek bir devamlılık belirtecini kullanıma sunan ve sonraki 10 öğeyi getiren bir döndürür CosmosPage:

var nextPage = await context.Sessions.OrderBy(s => s.Id).ToPageAsync(10, continuationToken);

Daha fazla bilgi için sayfalandırma ile ilgili belgeler bölümüne bakın.

Daha güvenli SQL sorgulaması için FromSql

Azure Cosmos DB sağlayıcısı aracılığıyla FromSqlRawSQL sorgulamaya izin verdi. Ancak bu API, kullanıcı tarafından sağlanan veriler SQL ile birleştirildiğinde veya birleştirildiğinde SQL ekleme saldırılarına karşı hassas olabilir. EF 9.0'da artık parametreleştirilmiş verileri her zaman SQL dışında bir parametre olarak tümleştiren yeni FromSql yöntemi kullanabilirsiniz:

var maxAngle = 8;
_ = await context.Blogs
    .FromSql($"SELECT VALUE c FROM root c WHERE c.Angle1 <= {maxAngle}")
    .ToListAsync();

Daha fazla bilgi için sayfalandırma ile ilgili belgeler bölümüne bakın.

Rol tabanlı erişim

NoSQL için Azure Cosmos DB yerleşik rol tabanlı erişim denetimi (RBAC) sistemi içerir. Bu artık EF9 tarafından tüm veri düzlemi işlemleri için desteklenmektedir. Ancak Azure Cosmos DB SDK'sı, Azure Cosmos DB'deki yönetim düzlemi işlemleri için RBAC'yi desteklemez. RBAC yerine Azure Yönetim API'sini EnsureCreatedAsync kullanın.

Zaman uyumlu G/Ç artık varsayılan olarak engellendi

NoSQL için Azure Cosmos DB, uygulama kodundan gelen zaman uyumlu (engelleme) API'leri desteklemez. Daha önce EF, zaman uyumsuz çağrılarda sizin için engelleyerek bunu maskelemişti. Ancak, bu hem zaman uyumlu G/Ç kullanımını teşvik eder, bu kötü bir uygulamadır ve kilitlenmelere neden olabilir. Bu nedenle EF 9'dan başlayarak, zaman uyumlu erişim denendiğinde bir özel durum oluşturulur. Örneğin:

Zaman uyumlu G/Ç, uyarı düzeyini uygun şekilde yapılandırarak şimdilik kullanılabilir. Örneğin, türünüzde OnConfiguringDbContext :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Ignore(CosmosEventId.SyncNotSupported));

Ancak EF 11'de eşitleme desteğini tamamen kaldırmayı planladığımızı unutmayın, bu nedenle ve ToListAsync gibi SaveChangesAsync zaman uyumsuz yöntemleri en kısa sürede kullanmak için güncelleştirmeye başlayın!

AOT ve önceden derlenmiş sorgular

Uyarı

NativeAOT ve sorgu ön derlemesi son derece deneysel özelliklerdir ve henüz üretim kullanımı için uygun değildir. Aşağıda açıklanan destek, büyük olasılıkla EF 10 ile yayınlanacak olan son özelliğe yönelik altyapı olarak görüntülenmelidir. Mevcut destekle deneme yapmanızı ve deneyimlerinizi bildirmenizi öneririz, ancak ef nativeaot uygulamalarını üretim ortamında dağıtmamanızı öneririz.

EF 9.0, veritabanlarına erişmek için EF kullanan önceden derlenmiş uygulamaların yayımlanmasına olanak tanıyan .NET NativeAOT için ilk ve deneysel desteği getirir. NATIVEAOT modunda LINQ sorgularını desteklemek için EF, sorgu ön derlemesine dayanır: Bu mekanizma, EF LINQ sorgularını statik olarak tanımlar ve her bir sorguyu yürütmek için kod içeren C# kesme makineleri oluşturur. LinQ sorgularınızı işleme ve SQL'e derleme işlemlerinin yoğun bir şekilde kaldırılması artık uygulamanız her başlatıldığında gerçekleşmediğinden, bu durum uygulamanızın başlangıç süresini önemli ölçüde azaltabilir. Bunun yerine, her sorgunun kesicisi bu sorgu için son haline getirilmiş SQL'i ve veritabanı sonuçlarını .NET nesneleri olarak gerçekleştirmeye yönelik iyileştirilmiş kodu içerir.

Örneğin, aşağıdaki EF sorgusuna sahip bir program verilmiştir:

var blogs = await context.Blogs.Where(b => b.Name == "foo").ToListAsync();

EF, projenizde bir C# kesme noktası oluşturur ve bu da sorgu yürütmesini devralacaktır. Sorguyu işlemek ve program her başlatıldığında SQL'e çevirmek yerine, kesme noktası sql'i doğrudan içine katıştırır (bu örnekte SQL Server için), programınızın çok daha hızlı başlatılmasını sağlar:

var relationalCommandTemplate = ((IRelationalCommandTemplate)(new RelationalCommand(materializerLiftableConstantContext.CommandBuilderDependencies, "SELECT [b].[Id], [b].[Name]\nFROM [Blogs] AS [b]\nWHERE [b].[Name] = N'foo'", new IRelationalParameter[] { })));

Ayrıca, aynı kesme noktası veritabanı sonuçlarından .NET nesnenizi gerçekleştirmeye ilişkin kod içerir:

var instance = new Blog();
UnsafeAccessor_Blog_Id_Set(instance) = dataReader.GetInt32(0);
UnsafeAccessor_Blog_Name_Set(instance) = dataReader.GetString(1);

Bu işlem, veritabanından nesnenizin özel alanlarına veri eklemek için başka bir yeni .NET özelliği ( güvenli olmayan erişimciler) kullanır.

NativeAOT ile ilgileniyorsanız ve son teknoloji özelliklerle denemeler yapmayı seviyorsanız, bunu deneyin! Özelliğin kararsız olarak kabul edilmesi gerektiğini ve şu anda birçok sınırlaması olduğunu unutmayın; bunu dengelemeyi ve EF 10'da üretim kullanımına daha uygun hale getirmeyi bekliyoruz.

Diğer ayrıntılar için NativeAOT belgeleri sayfasına bakın.

LINQ ve SQL çevirisi

Her sürümde olduğu gibi EF9 da LINQ sorgulama özelliklerinde çok sayıda geliştirme içerir. Yeni sorgular çevrilebilir ve hem daha iyi performans hem de okunabilirlik için desteklenen senaryolar için birçok SQL çevirisi geliştirilmiştir.

İyileştirme sayısı, bunların tümünü burada listelemek için çok fazla. Aşağıda, daha önemli iyileştirmelerden bazıları vurgulanır; 9.0'da yapılan işlerin daha eksiksiz bir listesi için bu soruna bakın.

Andrea Canciani'yi (@ranma42) EF Core tarafından oluşturulan SQL'i iyileştirmeye yönelik çok sayıda, yüksek kaliteli katkılarından dolayı çağırmak istiyoruz!

Karmaşık türler: GroupBy ve ExecuteUpdate desteği

GroupBy

İpucu

Burada gösterilen kod ComplexTypesSample.cs.

EF9, karmaşık bir tür örneğine göre gruplandırma işlemini destekler. Örneğin:

var groupedAddresses = await context.Stores
    .GroupBy(b => b.StoreAddress)
    .Select(g => new { g.Key, Count = g.Count() })
    .ToListAsync();

EF bunu karmaşık türün her üyesine göre gruplandırma olarak çevirir ve bu, karmaşık türlerin semantiğiyle değer nesneleri olarak hizalanır. Örneğin, Azure SQL'de:

SELECT [s].[StoreAddress_City], [s].[StoreAddress_Country], [s].[StoreAddress_Line1], [s].[StoreAddress_Line2], [s].[StoreAddress_PostCode], COUNT(*) AS [Count]
FROM [Stores] AS [s]
GROUP BY [s].[StoreAddress_City], [s].[StoreAddress_Country], [s].[StoreAddress_Line1], [s].[StoreAddress_Line2], [s].[StoreAddress_PostCode]

ExecuteUpdate

İpucu

Burada gösterilen kod ExecuteUpdateSample.cs gelir.

Benzer şekilde, EF9'da ExecuteUpdate da karmaşık tür özelliklerini kabul etmek için geliştirilmiştir. Ancak, karmaşık türün her üyesi açıkça belirtilmelidir. Örneğin:

var newAddress = new Address("Gressenhall Farm Shop", null, "Beetley", "Norfolk", "NR20 4DR");

await context.Stores
    .Where(e => e.Region == "Germany")
    .ExecuteUpdateAsync(s => s.SetProperty(b => b.StoreAddress, newAddress));

Bu, karmaşık türe eşlenen her sütunu güncelleştiren SQL oluşturur:

UPDATE [s]
SET [s].[StoreAddress_City] = @__complex_type_newAddress_0_City,
    [s].[StoreAddress_Country] = @__complex_type_newAddress_0_Country,
    [s].[StoreAddress_Line1] = @__complex_type_newAddress_0_Line1,
    [s].[StoreAddress_Line2] = NULL,
    [s].[StoreAddress_PostCode] = @__complex_type_newAddress_0_PostCode
FROM [Stores] AS [s]
WHERE [s].[Region] = N'Germany'

Daha önce, aramanızdaki karmaşık türün farklı özelliklerini el ile listelemeniz ExecuteUpdate gerekiyordu.

SQL'den gereksiz öğeleri ayıklama

Daha önce EF bazen gerçekten gerekli olmayan öğeler içeren SQL üretmişti; çoğu durumda, sql işlemenin daha önceki bir aşamasında bunlara ihtiyaç duyuldu ve geride bırakıldı. EF9 artık bu tür öğelerin çoğunu burarak daha kompakt ve bazı durumlarda daha verimli SQL elde eder.

Tablo ayıklama

İlk örnek olarak, EF tarafından oluşturulan SQL bazen sorguda gerçekten gerekli olmayan tabloların JOIN'lerini içeriyordu. Tür başına tablo (TPT) devralma eşlemesi kullanan aşağıdaki modeli göz önünde bulundurun:

public class Order
{
    public int Id { get; set; }
    ...

    public Customer Customer { get; set; }
}

public class DiscountedOrder : Order
{
    public double Discount { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    ...

    public List<Order> Orders { get; set; }
}

public class BlogContext : DbContext
{
    ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>().UseTptMappingStrategy();
    }
}

Ardından en az bir Siparişe sahip tüm Müşterileri almak için aşağıdaki sorguyu yürütürsek:

var customers = await context.Customers.Where(o => o.Orders.Any()).ToListAsync();

EF8 aşağıdaki SQL'i oluşturdu:

SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE EXISTS (
    SELECT 1
    FROM [Orders] AS [o]
    LEFT JOIN [DiscountedOrders] AS [d] ON [o].[Id] = [d].[Id]
    WHERE [c].[Id] = [o].[CustomerId])

Hiçbir sütuna başvurulmamasına rağmen sorgunun DiscountedOrders tabloya bir birleşim içerdiğini unutmayın. EF9, birleştirme olmadan ayıklamalı bir SQL oluşturur:

SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE EXISTS (
    SELECT 1
    FROM [Orders] AS [o]
    WHERE [c].[Id] = [o].[CustomerId])

Projeksiyon ayıklama

Benzer şekilde, aşağıdaki sorguyu inceleyelim:

var orders = await context.Orders
    .Where(o => o.Amount > 10)
    .Take(5)
    .CountAsync();

EF8'de bu sorgu aşağıdaki SQL'i oluşturdu:

SELECT COUNT(*)
FROM (
    SELECT TOP(@__p_0) [o].[Id]
    FROM [Orders] AS [o]
    WHERE [o].[Amount] > 10
) AS [t]

[o].[Id] Dış SELECT ifadesi satırları saydığından alt sorguda projeksiyona gerek olmadığını unutmayın. EF9 bunun yerine aşağıdakileri oluşturur:

SELECT COUNT(*)
FROM (
    SELECT TOP(@__p_0) 1 AS empty
    FROM [Orders] AS [o]
    WHERE [o].[Amount] > 10
) AS [s]

... ve projeksiyon boş. Bu çok fazla görünmeyebilir, ancak bazı durumlarda SQL'i önemli ölçüde basitleştirebilir; Etkisini görmek için testlerdeki bazı SQL değişikliklerini gözden geçirebilirsiniz.

GREATEST/LEAST içeren çeviriler

İpucu

Burada gösterilen kod LeastGreatestSample.cs gelir.

ve GREATEST SQL işlevlerini kullanan LEAST birkaç yeni çeviri kullanıma sunulmuştur.

Önemli

GREATEST ve LEAST işlevleri SQL Server/Azure SQL veritabanlarına 2022 sürümünde sunulmuştur. Visual Studio 2022 varsayılan olarak SQL Server 2019'u yükler. BU yeni çevirileri EF9'da denemek için SQL Server Developer Edition 2022'yi yüklemenizi öneririz.

Örneğin, veya Math.Max kullanan Math.Min sorgular artık sırasıyla ve GREATEST kullanılarak LEAST Azure SQL için çevrilir. Örneğin:

var walksUsingMin = await context.Walks
    .Where(e => Math.Min(e.DaysVisited.Count, e.ClosestPub.Beers.Length) > 4)
    .ToListAsync();

BU sorgu, SQL Server 2022'de YÜRÜTÜLEN EF9 kullanılırken aşağıdaki SQL'e çevrilir:

SELECT [w].[Id], [w].[ClosestPubId], [w].[DaysVisited], [w].[Name], [w].[Terrain]
FROM [Walks] AS [w]
INNER JOIN [Pubs] AS [p] ON [w].[ClosestPubId] = [p].[Id]
WHERE LEAST((
    SELECT COUNT(*)
    FROM OPENJSON([w].[DaysVisited]) AS [d]), (
    SELECT COUNT(*)
    FROM OPENJSON([p].[Beers]) AS [b])) >

Math.Min ve Math.Max ilkel bir koleksiyonun değerleri üzerinde de kullanılabilir. Örneğin:

var pubsInlineMax = await context.Pubs
    .SelectMany(e => e.Counts)
    .Where(e => Math.Max(e, threshold) > top)
    .ToListAsync();

BU sorgu, SQL Server 2022'de YÜRÜTÜLEN EF9 kullanılırken aşağıdaki SQL'e çevrilir:

SELECT [c].[value]
FROM [Pubs] AS [p]
CROSS APPLY OPENJSON([p].[Counts]) WITH ([value] int '$') AS [c]
WHERE GREATEST([c].[value], @__threshold_0) > @__top_1

RelationalDbFunctionsExtensions.Least Son olarak ve RelationalDbFunctionsExtensions.Greatest doğrudan SQL'de veya Least işlevini çağırmak Greatest için kullanılabilir. Örneğin:

var leastCount = await context.Pubs
    .Select(e => EF.Functions.Least(e.Counts.Length, e.DaysVisited.Count, e.Beers.Length))
    .ToListAsync();

BU sorgu, SQL Server 2022'de YÜRÜTÜLEN EF9 kullanılırken aşağıdaki SQL'e çevrilir:

SELECT LEAST((
    SELECT COUNT(*)
    FROM OPENJSON([p].[Counts]) AS [c]), (
    SELECT COUNT(*)
    FROM OPENJSON([p].[DaysVisited]) AS [d]), (
    SELECT COUNT(*)
    FROM OPENJSON([p].[Beers]) AS [b]))
FROM [Pubs] AS [p]

Sorgu parametreleştirmesini zorlama veya engelleme

İpucu

Burada gösterilen kod QuerySample.cs gelir.

Bazı özel durumlar dışında EF Core, LINQ sorgusunda kullanılan değişkenleri parametreleştirir, ancak oluşturulan SQL'deki sabitleri içerir. Örneğin, aşağıdaki sorgu yöntemini göz önünde bulundurun:

async Task<List<Post>> GetPosts(int id)
    => await context.Posts
        .Where(e => e.Title == ".NET Blog" && e.Id == id)
        .ToListAsync();

Bu, Azure SQL kullanılırken aşağıdaki SQL'e ve parametrelere çevrilir:

Executed DbCommand (1ms) [Parameters=[@__id_0='1'], CommandType='Text', CommandTimeout='30']
SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata]
FROM [Posts] AS [p]
WHERE [p].[Title] = N'.NET Blog' AND [p].[Id] = @__id_0

Bu değer sorgudan sorguya değişmeyecek olduğundan EF'nin SQL'de ".NET Blogu" için bir sabit oluşturduğuna dikkat edin. Sabit kullanmak, sorgu planı oluşturulurken bu değerin veritabanı altyapısı tarafından incelenmesine olanak tanır ve bu da daha verimli bir sorguya neden olabilir.

Öte yandan, aynı sorgu için idbirçok farklı değerle id yürütülebileceğinden değeri parametresizleştirilir. Bu durumda bir sabit oluşturmak, yalnızca id değerlerde farklılık gösteren çok sayıda sorguyla sorgu önbelleğinin kirlenmesine neden olabilir. Bu, veritabanının genel performansı için çok kötüdür.

Genel olarak bakıldığında, bu varsayılanlar değiştirilmemelidir. Ancak EF Core 8.0.2, varsayılan olarak bir EF.Constant parametre kullanılsa bile EF'yi sabit kullanmaya zorlayan bir yöntem ekler. Örneğin:

async Task<List<Post>> GetPostsForceConstant(int id)
    => await context.Posts
        .Where(e => e.Title == ".NET Blog" && e.Id == EF.Constant(id))
        .ToListAsync();

Çeviri artık değer için id bir sabit içerir:

Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata]
FROM [Posts] AS [p]
WHERE [p].[Title] = N'.NET Blog' AND [p].[Id] = 1

EF.Parameter yöntemi

EF9, tersini EF.Parameter yapmak için yöntemini tanıtır. Başka bir ifadeyle, değer kodda sabit olsa bile EF'yi parametre kullanmaya zorlar. Örneğin:

async Task<List<Post>> GetPostsForceParameter(int id)
    => await context.Posts
        .Where(e => e.Title == EF.Parameter(".NET Blog") && e.Id == id)
        .ToListAsync();

Çeviri artık ".NET Blogu" dizesi için bir parametre içeriyor:

Executed DbCommand (1ms) [Parameters=[@__p_0='.NET Blog' (Size = 4000), @__id_1='1'], CommandType='Text', CommandTimeout='30']
SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata]
FROM [Posts] AS [p]
WHERE [p].[Title] = @__p_0 AND [p].[Id] = @__id_1

Parametreli ilkel koleksiyonlar

EF8, temel koleksiyonları kullanan bazı sorguların çevrilmesi şeklini değiştirdi. LINQ sorgusu parametreli bir temel koleksiyon içerdiğinde EF, içeriğini JSON'a dönüştürür ve sorgunun tek bir parametre değeri olarak geçirir:

async Task<List<Post>> GetPostsPrimitiveCollection(int[] ids)
    => await context.Posts
        .Where(e => e.Title == ".NET Blog" && ids.Contains(e.Id))
        .ToListAsync();

Bu, SQL Server'da aşağıdaki çeviriyle sonuçlanır:

Executed DbCommand (5ms) [Parameters=[@__ids_0='[1,2,3]' (Size = 4000)], CommandType='Text', CommandTimeout='30']
SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Rating], [p].[Title], [p].[PromoText], [p].[Metadata]
FROM [Posts] AS [p]
WHERE [p].[Title] = N'.NET Blog' AND [p].[Id] IN (
    SELECT [i].[value]
    FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
)

Bu, farklı parametreli koleksiyonlar için aynı SQL sorgusuna sahip olmayı sağlar (yalnızca parametre değeri değişir), ancak bazı durumlarda veritabanı sorgu için en uygun şekilde planlayamadığından performans sorunlarına yol açabilir. EF.Constant yöntemi önceki çeviriye geri dönmek için kullanılabilir.

Bu etki için aşağıdaki sorgu kullanılır EF.Constant :

async Task<List<Post>> GetPostsForceConstantCollection(int[] ids)
    => await context.Posts
        .Where(
            e => e.Title == ".NET Blog" && EF.Constant(ids).Contains(e.Id))
        .ToListAsync();

Sonuçta elde edilen SQL aşağıdaki gibidir:

SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Rating], [p].[Title], [p].[PromoText], [p].[Metadata]
FROM [Posts] AS [p]
WHERE [p].[Title] = N'.NET Blog' AND [p].[Id] IN (1, 2, 3)

Ayrıca EF9, tüm sorgular TranslateParameterizedCollectionsToConstantsiçin ilkel koleksiyon parametreleştirmesini önlemek için kullanılabilecek bağlam seçeneğini de tanıtır. Ayrıca, ilkel koleksiyonların parametreleştirilmesini açıkça zorlayan bir tamamlayıcı TranslateParameterizedCollectionsToParameters da ekledik (bu varsayılan davranıştır).

İpucu

yöntemi bağlam EF.Parameter seçeneğini geçersiz kılar. Sorgularınızın çoğu (tümü değil) için ilkel koleksiyonların parametreleştirilmesini önlemek istiyorsanız, bağlam seçeneğini TranslateParameterizedCollectionsToConstants ayarlayabilir ve parametreleştirmek istediğiniz sorgular veya bağımsız değişkenler için kullanabilirsiniz EF.Parameter .

Sıralı olmayan alt sorgular

İpucu

Burada gösterilen kod QuerySample.cs gelir.

EF8'de, başka bir sorguda başvurulan bir IQueryable ayrı bir veritabanı gidiş dönüş olarak yürütülebilir. Örneğin, aşağıdaki LINQ sorgusunu göz önünde bulundurun:

var dotnetPosts = context
    .Posts
    .Where(p => p.Title.Contains(".NET"));

var results = await dotnetPosts
    .Where(p => p.Id > 2)
    .Select(p => new { Post = p, TotalCount = dotnetPosts.Count() })
    .Skip(2).Take(10)
    .ToArrayAsync();

EF8'de sorgusu dotnetPosts tek gidiş dönüş olarak yürütülür ve son sonuçlar ikinci sorgu olarak yürütülür. Örneğin, SQL Server'da:

SELECT COUNT(*)
FROM [Posts] AS [p]
WHERE [p].[Title] LIKE N'%.NET%'

SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata]
FROM [Posts] AS [p]
WHERE [p].[Title] LIKE N'%.NET%' AND [p].[Id] > 2
ORDER BY (SELECT 1)
OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY

EF9'da IQueryable içindekilerdotnetPosts, tek bir veritabanı gidiş dönüş ile sonuçlanır:

SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata], (
    SELECT COUNT(*)
    FROM [Posts] AS [p0]
    WHERE [p0].[Title] LIKE N'%.NET%')
FROM [Posts] AS [p]
WHERE [p].[Title] LIKE N'%.NET%' AND [p].[Id] > 2
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY

SQL Server'da alt sorgular ve toplamalar üzerinde toplama işlevleri

EF9, alt sorgular veya diğer toplama işlevleri üzerinde oluşturulan toplama işlevlerini kullanarak bazı karmaşık sorguların çevirisini geliştirir. Aşağıda bu tür bir sorgu örneği verilmiştir:

var latestPostsAverageRatingByLanguage = await context.Blogs
    .Select(x => new
    {
        x.Language,
        LatestPostRating = x.Posts.OrderByDescending(xx => xx.PublishedOn).FirstOrDefault()!.Rating
    })
    .GroupBy(x => x.Language)
    .Select(x => x.Average(xx => xx.LatestPostRating))
    .ToListAsync();

İlk olarak, SQL'e Select çevrilirken bir alt sorgu gerektiren her LatestPostRating bir işlem için hesaplamalarPost. Sorgunun sonraki bölümlerinde bu sonuçlar işlem kullanılarak Average toplanır. Sonuçta elde edilen SQL, SQL Server'da çalıştırıldığında aşağıdaki gibi görünür:

SELECT AVG([s].[Rating])
FROM [Blogs] AS [b]
OUTER APPLY (
    SELECT TOP(1) [p].[Rating]
    FROM [Posts] AS [p]
    WHERE [b].[Id] = [p].[BlogId]
    ORDER BY [p].[PublishedOn] DESC
) AS [s]
GROUP BY [b].[Language]

Önceki sürümlerde EF Core, toplama işlemini doğrudan alt sorgu üzerinden uygulamaya çalışarak benzer sorgular için geçersiz SQL oluşturur. SQL Server'da buna izin verilmez ve bir özel durumla sonuçlanır. Aynı ilke, başka bir toplama üzerinde toplama kullanan sorgular için de geçerlidir:

var topRatedPostsAverageRatingByLanguage = await context.Blogs.
Select(x => new
{
    x.Language,
    TopRating = x.Posts.Max(x => x.Rating)
})
.GroupBy(x => x.Language)
.Select(x => x.Average(xx => xx.TopRating))
.ToListAsync();

Not

Bu değişiklik, alt sorgular (veya diğer toplamalar) üzerinden toplamaları destekleyen ve (LATERAL JOIN) desteklemeyen APPLY Sqlite'ı etkilemez. Aşağıda Sqlite üzerinde çalışan ilk sorgunun SQL'i yer almaktadır:

SELECT ef_avg((
    SELECT "p"."Rating"
    FROM "Posts" AS "p"
    WHERE "b"."Id" = "p"."BlogId"
    ORDER BY "p"."PublishedOn" DESC
    LIMIT 1))
FROM "Blogs" AS "b"
GROUP BY "b"."Language"

Count != 0 kullanan sorgular iyileştirildi

İpucu

Burada gösterilen kod QuerySample.cs gelir.

EF8'de aşağıdaki LINQ sorgusu SQL COUNT işlevini kullanacak şekilde çevrildi:

var blogsWithPost = await context.Blogs
    .Where(b => b.Posts.Count > 0)
    .ToListAsync();

EF9 artık kullanarak EXISTSdaha verimli bir çeviri oluşturur:

SELECT "b"."Id", "b"."Name", "b"."SiteUri"
FROM "Blogs" AS "b"
WHERE EXISTS (
    SELECT 1
    FROM "Posts" AS "p"
    WHERE "b"."Id" = "p"."BlogId")

Null değerlerle ilgili karşılaştırma işlemleri için C# semantiği

EF8'de null atanabilir öğeler arasındaki karşılaştırmalar bazı senaryolar için doğru şekilde gerçekleştirilmedi. C# dilinde işlenenlerden biri veya her ikisi de null ise, karşılaştırma işleminin sonucu false olur; aksi takdirde işlenenlerin kapsanan değerleri karşılaştırılır. EF8'de veritabanı null semantiği kullanarak karşılaştırmaları çevirmek için kullanılır. Bu, LINQ to Objects kullanarak benzer sorgudan farklı sonuçlar üretir. Ayrıca, karşılaştırma filtre ve projeksiyonda yapıldığında farklı sonuçlar üretirdik. Bazı sorgular da Sql Server ile Sqlite/Postgres arasında farklı sonuçlar üretir.

Örneğin, sorgu:

var negatedNullableComparisonFilter = await context.Entities
    .Where(x => !(x.NullableIntOne > x.NullableIntTwo))
    .Select(x => new { x.NullableIntOne, x.NullableIntTwo }).ToListAsync();

aşağıdaki SQL'i oluşturur:

SELECT [e].[NullableIntOne], [e].[NullableIntTwo]
FROM [Entities] AS [e]
WHERE NOT ([e].[NullableIntOne] > [e].[NullableIntTwo])

veya null olarak ayarlanmış varlıkları NullableIntOneNullableIntTwo filtreler.

EF9'da üretiyoruz:

SELECT [e].[NullableIntOne], [e].[NullableIntTwo]
FROM [Entities] AS [e]
WHERE CASE
    WHEN [e].[NullableIntOne] > [e].[NullableIntTwo] THEN CAST(0 AS bit)
    ELSE CAST(1 AS bit)
END = CAST(1 AS bit)

Projeksiyonda gerçekleştirilen benzer karşılaştırma:

var negatedNullableComparisonProjection = await context.Entities.Select(x => new
{
    x.NullableIntOne,
    x.NullableIntTwo,
    Operation = !(x.NullableIntOne > x.NullableIntTwo)
}).ToListAsync();

sonuç olarak aşağıdaki SQL elde edildi:

SELECT [e].[NullableIntOne], [e].[NullableIntTwo], CASE
    WHEN NOT ([e].[NullableIntOne] > [e].[NullableIntTwo]) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END AS [Operation]
FROM [Entities] AS [e]

, veya false null olarak ayarlanmış varlıklar NullableIntOne için (C# dilinde beklenenden çokNullableIntTwo) döndürürtrue. Aynı senaryonun Sqlite üzerinde çalıştırılması oluşturuldu:

SELECT "e"."NullableIntOne", "e"."NullableIntTwo", NOT ("e"."NullableIntOne" > "e"."NullableIntTwo") AS "Operation"
FROM "Entities" AS "e"

bu durum, çevirinin Nullable object must have a value null veya null null olduğu NullableIntOne durumlar için değer ürettiği NullableIntTwo için özel durumla sonuçlanıyor.

EF9 artık bu senaryoları düzgün bir şekilde işleyip LINQ to Objects ve farklı sağlayıcılar arasında tutarlı sonuçlar üretir.

Bu geliştirme, @ranma42 tarafından katkıda bulundu. Çok teşekkürler!

ve Order LINQ işleçlerinin OrderDescending çevirisi

EF9, LINQ basitleştirilmiş sıralama işlemlerinin (Order ve OrderDescending) çevirisini sağlar. Bunlar benzer OrderBy/OrderByDescending şekilde çalışır ancak bağımsız değişken gerektirmez. Bunun yerine, varsayılan sıralama uygularlar- varlıklar için bu, birincil anahtar değerlerine göre sıralama ve diğer türler için değerlerin kendilerine göre sıralama anlamına gelir.

Aşağıda, basitleştirilmiş sıralama işleçlerinden yararlanan örnek bir sorgu verilmiştir:

var orderOperation = await context.Blogs
    .Order()
    .Select(x => new
    {
        x.Name,
        OrderedPosts = x.Posts.OrderDescending().ToList(),
        OrderedTitles = x.Posts.Select(xx => xx.Title).Order().ToList()
    })
    .ToListAsync();

Bu sorgu aşağıdakine eşdeğerdir:

var orderByEquivalent = await context.Blogs
    .OrderBy(x => x.Id)
    .Select(x => new
    {
        x.Name,
        OrderedPosts = x.Posts.OrderByDescending(xx => xx.Id).ToList(),
        OrderedTitles = x.Posts.Select(xx => xx.Title).OrderBy(xx => xx).ToList()
    })
    .ToListAsync();

ve aşağıdaki SQL'i üretir:

SELECT [b].[Name], [b].[Id], [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Rating], [p].[Title], [p].[PromoText], [p].[Metadata], [p0].[Title], [p0].[Id]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
LEFT JOIN [Posts] AS [p0] ON [b].[Id] = [p0].[BlogId]
ORDER BY [b].[Id], [p].[Id] DESC, [p0].[Title]

Not

Order ve OrderDescending yöntemleri yalnızca varlık koleksiyonları, karmaşık türler veya skalerler için desteklenir. Bunlar, birden çok özellik içeren anonim tür koleksiyonları gibi daha karmaşık projeksiyonlar üzerinde çalışmaz.

Bu geliştirme EF Team mezunları @bricelam tarafından katkıda bulundu. Çok teşekkürler!

Mantıksal olumsuzlama işlecinin (!) çevirisi geliştirildi

EF9, SQL CASE/WHEN, , COALESCEolumsuzlama ve diğer çeşitli yapıların çevresinde birçok iyileştirme getirir; bunların çoğu Andrea Canciani (@ranma42) tarafından katkıda bulundu - hepsi için çok teşekkürler! Aşağıda, mantıksal olumsuzlamayla ilgili bu iyileştirmelerden yalnızca birkaçını ayrıntılı olarak inceleyeceğiz.

Şimdi aşağıdaki sorguyu inceleyelim:

var negatedContainsSimplification = await context.Posts
    .Where(p => !p.Content.Contains("Announcing"))
    .Select(p => new { p.Content }).ToListAsync();

EF8'de aşağıdaki SQL'i oluştururuz:

SELECT "p"."Content"
FROM "Posts" AS "p"
WHERE NOT (instr("p"."Content", 'Announcing') > 0)

EF9'da karşılaştırmaya "gönderme" NOT işlemi yapıyoruz:

SELECT "p"."Content"
FROM "Posts" AS "p"
WHERE instr("p"."Content", 'Announcing') <= 0

SQL Server için geçerli olan başka bir örnek, olumsuzlanmış bir koşullu işlemdir.

var caseSimplification = await context.Blogs
    .Select(b => !(b.Id > 5 ? false : true))
    .ToListAsync();

EF8'de iç içe CASE bloklara neden olmak için kullanılır:

SELECT CASE
    WHEN CASE
        WHEN [b].[Id] > 5 THEN CAST(0 AS bit)
        ELSE CAST(1 AS bit)
    END = CAST(0 AS bit) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END
FROM [Blogs] AS [b]

EF9'da iç içe yerleştirmeyi kaldırdık:

SELECT CASE
    WHEN [b].[Id] > 5 THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END
FROM [Blogs] AS [b]

SQL Server'da, olumsuz bir bool özelliği yansıtılırken:

var negatedBoolProjection = await context.Posts.Select(x => new { x.Title, Active = !x.Archived }).ToListAsync();

Karşılaştırmalar doğrudan SQL Server sorgularında yansıtmada görünemediğinden EF8 bir CASE blok oluşturur:

SELECT [p].[Title], CASE
   WHEN [p].[Archived] = CAST(0 AS bit) THEN CAST(1 AS bit)
   ELSE CAST(0 AS bit)
END AS [Active]
FROM [Posts] AS [p]

EF9'da bu çeviri basitleştirilmiştir ve bit düzeyinde DEĞİl (~):

SELECT [p].[Title], ~[p].[Archived] AS [Active]
FROM [Posts] AS [p]

Azure SQL ve Azure Synapse için daha iyi destek

EF9, hedeflenen SQL Server türünü belirtirken daha fazla esneklik sağlar. EF'yi ile UseSqlServeryapılandırmak yerine artık veya UseAzureSqlbelirtebilirsinizUseAzureSynapse. Bu, EF'in Azure SQL veya Azure Synapse kullanırken daha iyi SQL üretmesine olanak tanır. EF, veritabanına özgü özelliklerden yararlanabilir (örneğin, Azure SQL'de JSON için ayrılmış tür) veya sınırlamalarına geçici çözüm sağlayabilir (örneğinLIKE kullanırken yan tümce kullanılamaz).

Diğer sorgu geliştirmeleri

  • EF8'de sunulan ilkel koleksiyon sorgulama desteği, tüm ICollection<T> türleri destekleyecek şekilde genişletildi. Bunun yalnızca parametre ve satır içi koleksiyonlar için geçerli olduğunu unutmayın. Varlıkların parçası olan ilkel koleksiyonlar yine de diziler, listeler ve EF9'da salt okunur diziler/listeler ile sınırlıdır.
  • Sorgunun sonuçlarını (ToHashSetAsync, @wertzuiHashSetkatkıda bulunan) olarak döndürmek için yeni işlevler.
  • TimeOnly.FromDateTime ve FromTimeSpan artık SQL Server'da çevrilmiştir (#33678).
  • ToStringsabit listeleri artık çevrildi (#33706, @Danevandy99 tarafından katkıda bulundu).
  • string.Joinşimdi SQL Server'da (#28899) toplama olmayan bağlamda CONCAT_WS çevirir.
  • EF.Functions.PatIndex şimdi bir desenin ilk oluşumunun başlangıç konumunu döndüren SQL Server PATINDEX işlevine çevrilir (#33702, @smnsht).
  • Sumve Average şimdi SQLite'te ondalık değerler için çalışıyor (#33721, @ranma42 tarafından katkıda bulunan).
  • ve için düzeltmeler ve iyileştirmeler string.StartsWithEndsWith (#31482).
  • Convert.To*yöntemleri artık tür object bağımsız değişkenini kabul edebilir (#33891, @imangd tarafından katkıda bulunan).
  • Exclusive-Or (XOR) işlemi artık SQL Server'da çevrilmiştir (#34071, @ranma42 tarafından katkıda bulunulur).
  • ve işlemleri için COLLATE null atanabilirlik ile ilgili iyileştirmeler (AT TIME ZONE, katkıda bulunan @ranma42).
  • üzerinde DISTINCTIN için iyileştirmeler EXISTS ve ayarlama işlemleri (#34381, @ranma42 tarafından katkıda bulundu).

Yukarıdakiler EF9'daki en önemli sorgu geliştirmelerinden yalnızca bazılarıydı; daha eksiksiz bir liste için bu soruna bakın.

Geçişler

Eşzamanlı geçişlere karşı koruma

EF9, veritabanını bozuk durumda bırakabileceğinden aynı anda gerçekleşen birden çok geçiş yürütmesine karşı koruma sağlayan bir kilitleme mekanizmasını tanıtır. Bu durum, geçişler önerilen yöntemler kullanılarak üretim ortamına dağıtıldığında gerçekleşmez, ancak geçişler çalışma zamanında yöntemi kullanılarak DbContext.Database.MigrateAsync() uygulanırsa gerçekleşebilir. Geçişleri uygulama başlatmanın bir parçası olarak değil dağıtımda uygulamanızı öneririz, ancak bu durum daha karmaşık uygulama mimarilerine (örn. .NET Aspire projeleri kullanılırken) neden olabilir.

Not

Sqlite veritabanı kullanıyorsanız bu özellikle ilişkili olası sorunlara bakın.

Birden çok geçiş işlemi bir işlem içinde çalıştırılamıyorsa uyarır

Geçişler sırasında gerçekleştirilen işlemlerin çoğu bir işlem tarafından korunur. Bu, bir nedenden dolayı geçiş başarısız olursa veritabanının bozuk durumda olmamasını sağlar. Ancak, bazı işlemler bir işleme sarmalanmaz (örneğin , SQL Server bellek için iyileştirilmiş tablolardaki işlemler veya veritabanı harmanlamasını değiştirme gibi veritabanı değiştirme işlemleri). Geçiş hatası durumunda veritabanının bozulmasını önlemek için, bu işlemlerin ayrı bir geçiş kullanılarak yalıtılmış olarak gerçekleştirilmesi önerilir. EF9 artık bir geçiş birden çok işlem içerdiğinde bir senaryo algılar ve bunlardan biri bir işlemde sarmalanamaz ve bir uyarı oluşturur.

Geliştirilmiş veri tohumlama

EF9, veritabanını ilk verilerle dolduran veri tohumlama gerçekleştirmek için kullanışlı bir yol sunar. DbContextOptionsBuilderşimdi DbContext başlatıldığında yürütülen ve UseSeeding yöntemlerini içerir UseAsyncSeeding (öğesinin EnsureCreatedAsyncbir parçası olarak).

Not

Uygulama daha önce çalıştırıldıysa, veritabanı zaten örnek verileri içerebilir (bağlamın ilk başlatılmasına eklenirdi). Bu nedenle, UseSeedingUseAsyncSeeding veritabanını doldurmaya çalışmadan önce verilerin var olup olmadığını denetlemelidir. Bu, basit bir EF sorgusu düzenlenerek elde edilebilir.

Bu yöntemlerin nasıl kullanılabileceğini gösteren bir örnek aşağıda verilmiştir:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFDataSeeding;Trusted_Connection=True;ConnectRetryCount=0")
        .UseSeeding((context, _) =>
        {
            var testBlog = context.Set<Blog>().FirstOrDefault(b => b.Url == "http://test.com");
            if (testBlog == null)
            {
                context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
                context.SaveChanges();
            }
        })
        .UseAsyncSeeding(async (context, _, cancellationToken) =>
        {
            var testBlog = await context.Set<Blog>().FirstOrDefaultAsync(b => b.Url == "http://test.com", cancellationToken);
            if (testBlog == null)
            {
                context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
                await context.SaveChangesAsync(cancellationToken);
            }
        });

Burada daha fazla bilgi bulabilirsiniz.

Diğer geçiş geliştirmeleri

  • Var olan bir tabloyu SQL Server zamana bağlı tablosuna değiştirirken, geçiş kodu boyutu önemli ölçüde azaltılmıştır.

Model oluşturma

Otomatik olarak derlenen modeller

İpucu

Burada gösterilen kod NewInEFCore9.CompiledModels örneğinden gelir.

Derlenen modeller, 100'lü veya 1000'lerdeki varlık türü sayıları gibi büyük modellere sahip uygulamalar için başlangıç süresini iyileştirebilir. EF Core'un önceki sürümlerinde, komut satırı kullanılarak el ile derlenmiş bir modelin oluşturulması gerekiyordu. Örneğin:

dotnet ef dbcontext optimize

Komutu çalıştırdıktan sonra EF Core'a .UseModel(MyCompiledModels.BlogsContextModel.Instance) derlenmiş modeli kullanmasını söylemek için öğesine gibi OnConfiguring bir satır eklenmelidir.

EF9'dan başlayarak, uygulamanın .UseModel türü derlenen modelle aynı proje/derlemede olduğunda bu DbContext satır artık gerekli değildir. Bunun yerine, derlenen model otomatik olarak algılanır ve kullanılır. Bu, modeli oluştururken EF günlüğünün olmasıyla görülebilir. Basit bir uygulama çalıştırıldığında, uygulama başlatıldığında EF'nin modeli oluşturması gösterilir:

Starting application...
>> EF is building the model...
Model loaded with 2 entity types.

Model projesinde çalıştırılan dotnet ef dbcontext optimize çıkış:

PS D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model> dotnet ef dbcontext optimize

Build succeeded in 0.3s

Build succeeded in 0.3s
Build started...
Build succeeded.
>> EF is building the model...
>> EF is building the model...
Successfully generated a compiled model, it will be discovered automatically, but you can also call 'options.UseModel(BlogsContextModel.Instance)'. Run this command again when the model is modified.
PS D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model> 

Günlük çıkışının, komutu çalıştırırken modelin oluşturulduğuna işaret ettiğini unutmayın. Uygulamayı yeniden derledikten sonra ancak herhangi bir kod değişikliği yapmadan yeniden çalıştırırsak çıkış şöyle olur:

Starting application...
Model loaded with 2 entity types.

Derlenen model algılandığından ve otomatik olarak kullanıldığından, uygulama başlatılırken modelin oluşturulmamış olduğuna dikkat edin.

MSBuild tümleştirmesi

Yukarıdaki yaklaşımla, varlık türleri veya DbContext yapılandırması değiştirildiğinde derlenen modelin el ile yeniden oluşturulması gerekir. Ancak EF9, model projesi oluşturulduğunda derlenen modeli otomatik olarak güncelleştirebilen bir MSBuild görev paketiyle birlikte sunulur! Başlamak için Microsoft.EntityFrameworkCore.Tasks NuGet paketini yükleyin. Örneğin:

dotnet add package Microsoft.EntityFrameworkCore.Tasks --version 9.0.0

İpucu

Yukarıdaki komutta kullandığınız EF Core sürümüyle eşleşen paket sürümünü kullanın.

Ardından dosyanızdaki EFOptimizeContext ve EFScaffoldModelStage özelliklerini ayarlayarak tümleştirmeyi .csproj etkinleştirin. Örneğin:

<PropertyGroup>
    <EFOptimizeContext>true</EFOptimizeContext>
    <EFScaffoldModelStage>build</EFScaffoldModelStage>
</PropertyGroup>

Şimdi projeyi oluşturursak derleme zamanında derlenen modelin derlendiğini belirten günlüğe kaydetme işlemini görebiliriz:

Optimizing DbContext...
dotnet exec --depsfile D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App\bin\Release\net8.0\App.deps.json
  --additionalprobingpath G:\packages 
  --additionalprobingpath "C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages" 
  --runtimeconfig D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App\bin\Release\net8.0\App.runtimeconfig.json G:\packages\microsoft.entityframeworkcore.tasks\9.0.0-preview.4.24205.3\tasks\net8.0\..\..\tools\netcoreapp2.0\ef.dll dbcontext optimize --output-dir D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model\obj\Release\net8.0\ 
  --namespace NewInEfCore9 
  --suffix .g 
  --assembly D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model\bin\Release\net8.0\Model.dll
  --project-dir D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model 
  --root-namespace NewInEfCore9 
  --language C# 
  --nullable 
  --working-dir D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App 
  --verbose 
  --no-color 
  --prefix-output 

Uygulamayı çalıştırmak, derlenen modelin algılandığını ve bu nedenle modelin yeniden derlenmediğini gösterir:

Starting application...
Model loaded with 2 entity types.

Artık model her değiştiğinde, derlenen model proje derlenir oluşturulmaz otomatik olarak yeniden oluşturulur.

Daha fazla bilgi için bkz. MSBuild tümleştirmesi.

Salt okunur ilkel koleksiyonlar

EF8, ilkel türlerin eşleme dizileri ve değiştirilebilir listeleri için destek sunar. Bu, EF9'da salt okunur koleksiyonları/listeleri içerecek şekilde genişletilmiştir. Özel olarak, EF9 , IReadOnlyListveya IReadOnlyCollectionolarak ReadOnlyCollectionyazılan koleksiyonları destekler. Örneğin, aşağıdaki kodda kurala DaysVisited göre ilkel bir tarih koleksiyonu olarak eşlenir:

public class DogWalk
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ReadOnlyCollection<DateOnly> DaysVisited { get; set; }
}

Salt okunur koleksiyon, isterseniz normal, değiştirilebilir bir koleksiyon tarafından yedeklenebilir. Örneğin, aşağıdaki kodda, DaysVisited sınıfındaki kodun temel listeyi işlemesine izin verirken, ilkel bir tarih koleksiyonu olarak eşlenebilir.

    public class Pub
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public IReadOnlyCollection<string> Beers { get; set; }

        private List<DateOnly> _daysVisited = new();
        public IReadOnlyList<DateOnly> DaysVisited => _daysVisited;
    }

Bu koleksiyonlar daha sonra sorgularda normal şekilde kullanılabilir. Örneğin, bu LINQ sorgusu:

var walksWithADrink = await context.Walks.Select(
    w => new
    {
        WalkName = w.Name,
        PubName = w.ClosestPub.Name,
        Count = w.DaysVisited.Count(v => w.ClosestPub.DaysVisited.Contains(v)),
        TotalCount = w.DaysVisited.Count
    }).ToListAsync();

Bu, SQLite'te aşağıdaki SQL'e çevrilir:

SELECT "w"."Name" AS "WalkName", "p"."Name" AS "PubName", (
    SELECT COUNT(*)
    FROM json_each("w"."DaysVisited") AS "d"
    WHERE "d"."value" IN (
        SELECT "d0"."value"
        FROM json_each("p"."DaysVisited") AS "d0"
    )) AS "Count", json_array_length("w"."DaysVisited") AS "TotalCount"
FROM "Walks" AS "w"
INNER JOIN "Pubs" AS "p" ON "w"."ClosestPubId" = "p"."Id"

Anahtarlar ve dizinler için dolgu faktörü belirtme

İpucu

Burada gösterilen kod ModelBuildingSample.cs gelir.

EF9, anahtarlar ve dizinler oluşturmak için EF Core Migrations kullanılırken SQL Server dolgu faktörünün belirtimini destekler. SQL Server belgelerinden ,"Dizin oluşturulduğunda veya yeniden oluşturulduğunda, doldurma faktörü değeri her yaprak düzeyindeki sayfada verilerle doldurulacak alan yüzdesini belirler ve her sayfada kalan alanı gelecekteki büyüme için boş alan olarak ayırır."

Doldurma faktörü tek veya bileşik birincil anahtarlar ile alternatif anahtarlar ve dizinler üzerinde ayarlanabilir. Örneğin:

modelBuilder.Entity<User>()
    .HasKey(e => e.Id)
    .HasFillFactor(80);

modelBuilder.Entity<User>()
    .HasAlternateKey(e => new { e.Region, e.Ssn })
    .HasFillFactor(80);

modelBuilder.Entity<User>()
    .HasIndex(e => new { e.Name })
    .HasFillFactor(80);

modelBuilder.Entity<User>()
    .HasIndex(e => new { e.Region, e.Tag })
    .HasFillFactor(80);

Mevcut tablolara uygulandığında, bu, tabloları dolgu faktörüne dönüştürerek kısıtlamayı değiştirir:

ALTER TABLE [User] DROP CONSTRAINT [AK_User_Region_Ssn];
ALTER TABLE [User] DROP CONSTRAINT [PK_User];
DROP INDEX [IX_User_Name] ON [User];
DROP INDEX [IX_User_Region_Tag] ON [User];

ALTER TABLE [User] ADD CONSTRAINT [AK_User_Region_Ssn] UNIQUE ([Region], [Ssn]) WITH (FILLFACTOR = 80);
ALTER TABLE [User] ADD CONSTRAINT [PK_User] PRIMARY KEY ([Id]) WITH (FILLFACTOR = 80);
CREATE INDEX [IX_User_Name] ON [User] ([Name]) WITH (FILLFACTOR = 80);
CREATE INDEX [IX_User_Region_Tag] ON [User] ([Region], [Tag]) WITH (FILLFACTOR = 80);

Bu geliştirme, @deano-hunter tarafından katkıda bulundu. Çok teşekkürler!

Mevcut model oluşturma kurallarını daha genişletilebilir hale getirme

İpucu

Burada gösterilen kod CustomConventionsSample.cs gelir.

Uygulamalar için genel model oluşturma kuralları EF7'de kullanıma sunulmuştur. EF9'da, mevcut kuralların bazılarını genişletmeyi kolaylaştırdık. Örneğin, EF7'de özniteliklere göre özellikleri eşleme kodu şu şekildedir:

public class AttributeBasedPropertyDiscoveryConvention : PropertyDiscoveryConvention
{
    public AttributeBasedPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
        : base(dependencies)
    {
    }

    public override void ProcessEntityTypeAdded(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionContext<IConventionEntityTypeBuilder> context)
        => Process(entityTypeBuilder);

    public override void ProcessEntityTypeBaseTypeChanged(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionEntityType? newBaseType,
        IConventionEntityType? oldBaseType,
        IConventionContext<IConventionEntityType> context)
    {
        if ((newBaseType == null
             || oldBaseType != null)
            && entityTypeBuilder.Metadata.BaseType == newBaseType)
        {
            Process(entityTypeBuilder);
        }
    }

    private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
    {
        foreach (var memberInfo in GetRuntimeMembers())
        {
            if (Attribute.IsDefined(memberInfo, typeof(PersistAttribute), inherit: true))
            {
                entityTypeBuilder.Property(memberInfo);
            }
            else if (memberInfo is PropertyInfo propertyInfo
                     && Dependencies.TypeMappingSource.FindMapping(propertyInfo) != null)
            {
                entityTypeBuilder.Ignore(propertyInfo.Name);
            }
        }

        IEnumerable<MemberInfo> GetRuntimeMembers()
        {
            var clrType = entityTypeBuilder.Metadata.ClrType;

            foreach (var property in clrType.GetRuntimeProperties()
                         .Where(p => p.GetMethod != null && !p.GetMethod.IsStatic))
            {
                yield return property;
            }

            foreach (var property in clrType.GetRuntimeFields())
            {
                yield return property;
            }
        }
    }
}

EF9'da bu, aşağıdakilere kadar basitleştirilebilir:

public class AttributeBasedPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
    : PropertyDiscoveryConvention(dependencies)
{
    protected override bool IsCandidatePrimitiveProperty(
        MemberInfo memberInfo, IConventionTypeBase structuralType, out CoreTypeMapping? mapping)
    {
        if (base.IsCandidatePrimitiveProperty(memberInfo, structuralType, out mapping))
        {
            if (Attribute.IsDefined(memberInfo, typeof(PersistAttribute), inherit: true))
            {
                return true;
            }

            structuralType.Builder.Ignore(memberInfo.Name);
        }

        mapping = null;
        return false;
    }
}

Genel olmayan oluşturucuları çağırmak için ApplyConfigurationsFromAssembly güncelleştirme

EF Core'un ApplyConfigurationsFromAssembly önceki sürümlerinde yöntemi yalnızca genel, parametresiz oluşturucularla yapılandırma türlerinin örneğini oluşturmuş. EF9'da , hem bu başarısız olduğunda oluşturulan hata iletilerini geliştirdik hem de genel olmayan oluşturucu tarafından örneklemeyi etkinleştirdik. Bu, yapılandırmayı hiçbir zaman uygulama koduyla örneklenmemesi gereken özel iç içe geçmiş bir sınıfta birlikte konumlandırırken kullanışlıdır. Örneğin:

public class Country
{
    public int Code { get; set; }
    public required string Name { get; set; }

    private class FooConfiguration : IEntityTypeConfiguration<Country>
    {
        private FooConfiguration()
        {
        }

        public void Configure(EntityTypeBuilder<Country> builder)
        {
            builder.HasKey(e => e.Code);
        }
    }
}

Bir yana, bazı kişiler bu düzenin bir iğrençlik olduğunu düşünür çünkü varlık türünü yapılandırmayla eşleştirir. Diğer kişiler bunun çok yararlı olduğunu düşünür çünkü varlık türüyle yapılandırmayı birlikte bulur. Bunu burada tartışmayalım. :-)

SQL Server HierarchyId

İpucu

Burada gösterilen kod HierarchyIdSample.cs gelir.

HierarchyId yol oluşturma için Şeker

EF8'de SQL Server HierarchyId türü için birinci sınıf destek eklendi. EF9'da, ağaç yapısında yeni alt düğümler oluşturmayı kolaylaştırmak için bir şeker yöntemi eklenmiştir. Örneğin, aşağıdaki kod özelliğine sahip HierarchyId mevcut bir varlık için sorgular:

var daisy = await context.Halflings.SingleAsync(e => e.Name == "Daisy");

Bu HierarchyId özellik daha sonra herhangi bir açık dize işlemesi olmadan alt düğümler oluşturmak için kullanılabilir. Örneğin:

var child1 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1), "Toast");
var child2 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 2), "Wills");

daisy bir HierarchyId varsa/4/1/3/1/, child1HierarchyId "/1/4/3/1/1/" child2 ve "/4/1/3/1/2/" alır HierarchyId .

Bu iki alt öğe arasında düğüm oluşturmak için ek bir alt düzey kullanılabilir. Örneğin:

var child1b = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1, 5), "Toast");

Bu, ve arasına HierarchyId/4/1/3/1/1.5/ yerleştirerek ile bir child1child2düğüm oluşturur.

Bu geliştirme, @Rezakazemi890 tarafından katkıda bulundu. Çok teşekkürler!

Araçlar

Daha az yeniden derleme

Komut dotnet ef satırı aracı , aracı yürütmeden önce projenizi varsayılan olarak oluşturur. Bunun nedeni, aracı çalıştırmadan önce yeniden derlenmemesi, işler çalışmadığında yaygın bir karışıklık kaynağıdır. Deneyimli geliştiriciler bu derlemeyi --no-build önlemek için bu seçeneği kullanabilir ve bu da yavaş olabilir. Ancak, bu seçenek bile --no-build projenin EF araçlarının dışında bir sonraki derlenmesinde yeniden derlenmesine neden olabilir.

@Suchiman topluluk katkılarının bunu düzeltmiş olduğunu düşünüyoruz. Ancak MSBuild davranışlarıyla ilgili ince ayarların istenmeyen sonuçlar doğurma eğilimine sahip olduğunu da biliyoruz. Bu nedenle sizin gibi insanlardan bunu denemelerini ve sahip olduğunuz olumsuz deneyimleri tekrar raporlamalarını istiyoruz.