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. Ayrıntılar için bkz . Entity Framework Core 9 planı.
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 önizlemesi 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.
LINQ ve SQL çevirisi
Ekip, JSON eşleme ve belge veritabanlarında sürekli iyileştirmelerimizin bir parçası olarak EF Core 9'daki sorgu işlem hattında bazı önemli mimari değişiklikleri üzerinde çalışmaktadır. Bu, sizin gibi kişilerin kodunuzu bu yeni iç bileşenlerde çalıştırmasını istediğimiz anlamına gelir. (Yayının bu noktasında "Yenilikler" belgesini okuyorsanız topluluğun gerçekten meşgul bir parçasısınızdır; teşekkür ederiz!) 120.000'den fazla testimiz var ama yeterli değil! Sorunları bulmak ve sağlam bir sürüm göndermek için bitlerimizde gerçek kod çalıştıran kişilere ihtiyacımız var!
OPENJSON'un WITH yan tümcesine geçirilen sütunları ayıklama
İpucu
Burada gösterilen kod JsonColumnsSample.cs gelir.
EF9 çağrılırken OPENJSON WITH
gereksiz sütunları kaldırır. Örneğin, bir koşul kullanarak bir JSON koleksiyonundan sayı elde eden bir sorgu düşünün:
var postsUpdatedOn = await context.Posts
.Where(p => p.Metadata!.Updates.Count(e => e.UpdatedOn >= date) == 1)
.ToListAsync();
EF8'de bu sorgu, Azure SQL veritabanı sağlayıcısını kullanırken aşağıdaki SQL'i oluşturur:
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 (
SELECT COUNT(*)
FROM OPENJSON([p].[Metadata], '$.Updates') WITH (
[PostedFrom] nvarchar(45) '$.PostedFrom',
[UpdatedBy] nvarchar(max) '$.UpdatedBy',
[UpdatedOn] date '$.UpdatedOn',
[Commits] nvarchar(max) '$.Commits' AS JSON
) AS [u]
WHERE [u].[UpdatedOn] >= @__date_0) = 1
ve değerinin UpdatedBy
Commits
bu sorguda gerekli olmadığını fark edin. EF9'dan başlayarak, bu sütunlar artık budanır:
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 (
SELECT COUNT(*)
FROM OPENJSON([p].[Metadata], '$.Updates') WITH (
[PostedFrom] nvarchar(45) '$.PostedFrom',
[UpdatedOn] date '$.UpdatedOn'
) AS [u]
WHERE [u].[UpdatedOn] >= @__date_0) = 1
Bazı senaryolarda bu durum yan tümcesinin tamamen kaldırılmasına WITH
neden olur. Örneğin:
var tagsWithCount = await context.Tags.Where(p => p.Text.Length == 1).ToListAsync();
EF8'de bu sorgu aşağıdaki SQL'e çevrilir:
SELECT [t].[Id], [t].[Text]
FROM [Tags] AS [t]
WHERE (
SELECT COUNT(*)
FROM OPENJSON([t].[Text]) WITH ([value] nvarchar(max) '$') AS [t0]) = 1
EF9'da bu, şu şekilde geliştirilmiştir:
SELECT [t].[Id], [t].[Text]
FROM [Tags] AS [t]
WHERE (
SELECT COUNT(*)
FROM OPENJSON([t].[Text]) AS [t0]) = 1
GREATEST/LEAST içeren çeviriler
İpucu
Burada gösterilen kod LeastGreatestSample.cs gelir.
ve LEAST
SQL işlevlerini kullanan GREATEST
birkaç yeni çeviri kullanıma sunulmuştur.
Önemli
GREATEST
2022 sürümünde SQL Server/Azure SQL veritabanlarına sunulan ve LEAST
işlevleri. 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.Min
kullanan Math.Max
sorgular artık sırasıyla ve LEAST
kullanılarak GREATEST
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 Greatest
işlevini çağırmak Least
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:
info: 2/5/2024 15:43:13.789 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
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 id
birçok farklı değerle id
yürütülebileceğinden değeri parametresizleştirilir. Bu durumda bir sabit oluşturmak, yalnızca parametre değerlerinde farklılık gösteren çok sayıda sorguyla sorgu önbelleğinin kirlenmesine neden olur. 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:
info: 2/5/2024 15:43:13.812 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
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
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:
info: 2/5/2024 15:43:13.803 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
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
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 = dotnetPosts
.Where(p => p.Id > 2)
.Select(p => new { Post = p, TotalCount = dotnetPosts.Count() })
.Skip(2).Take(10)
.ToArray();
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'dadotnetPosts
, IQueryable
içindekiler tek bir gidiş dönüşle 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
Yeni ToHashSetAsync<T>
yöntemler
İpucu
Burada gösterilen kod QuerySample.cs gelir.
Enumerable.ToHashSet.NET Core 2.0'dan bu yana yöntemler mevcut. EF9'da eşdeğer zaman uyumsuz yöntemler eklenmiştir. Örneğin:
var set1 = await context.Posts
.Where(p => p.Tags.Count > 3)
.ToHashSetAsync();
var set2 = await context.Posts
.Where(p => p.Tags.Count > 3)
.ToHashSetAsync(ReferenceEqualityComparer.Instance);
Bu geliştirme, @wertzui tarafından katkıda bulundu. Çok teşekkürler!
ExecuteUpdate ve ExecuteDelete
Karmaşık tür örneklerinin ExecuteUpdate'e geçirilmesine izin ver
İpucu
Burada gösterilen kod ExecuteUpdateSample.cs gelir.
ExecuteUpdate
API, EF7'de, veya SaveChanges
izlemeden veritabanında anında, doğrudan güncelleştirmeler gerçekleştirmek için kullanıma sunulmuştur. Örneğin:
await context.Stores
.Where(e => e.Region == "Germany")
.ExecuteUpdateAsync(s => s.SetProperty(b => b.Region, "Deutschland"));
Bu kodu çalıştırmak aşağıdaki sorguyu yürüterek "Deutschland" olarak güncelleştirir Region
:
UPDATE [s]
SET [s].[Region] = N'Deutschland'
FROM [Stores] AS [s]
WHERE [s].[Region] = N'Germany'
EF8'de ExecuteUpdate
karmaşık tür özelliklerinin değerlerini güncelleştirmek için de kullanılabilir. 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 == "Deutschland")
.ExecuteUpdateAsync(
s => s.SetProperty(b => b.StoreAddress.Line1, newAddress.Line1)
.SetProperty(b => b.StoreAddress.Line2, newAddress.Line2)
.SetProperty(b => b.StoreAddress.City, newAddress.City)
.SetProperty(b => b.StoreAddress.Country, newAddress.Country)
.SetProperty(b => b.StoreAddress.PostCode, newAddress.PostCode));
Bu kodun çalıştırılması aşağıdaki sorgu yürütmesine neden olur:
UPDATE [s]
SET [s].[StoreAddress_PostCode] = @__newAddress_PostCode_4,
[s].[StoreAddress_Country] = @__newAddress_Country_3,
[s].[StoreAddress_City] = @__newAddress_City_2,
[s].[StoreAddress_Line2] = NULL,
[s].[StoreAddress_Line1] = @__newAddress_Line1_0
FROM [Stores] AS [s]
WHERE [s].[Region] = N'Deutschland'
EF9'da, karmaşık tür örneğinin kendisini geçirerek aynı güncelleştirme gerçekleştirilebilir. Yani, her üyenin açıkça belirtilmesi gerekmez. Ö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 kodun çalıştırılması, önceki örnekle aynı sorgu yürütmesine neden olur:
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'
Hem karmaşık tür özelliklerinde hem de basit özelliklerde birden çok güncelleştirme tek bir çağrısında ExecuteUpdate
birleştirilebilir. Örneğin:
await context.Customers
.Where(e => e.Name == name)
.ExecuteUpdateAsync(
s => s.SetProperty(
b => b.CustomerInfo.WorkAddress, new Address("Gressenhall Workhouse", null, "Beetley", "Norfolk", "NR20 4DR"))
.SetProperty(b => b.CustomerInfo.HomeAddress, new Address("Gressenhall Farm", null, "Beetley", "Norfolk", "NR20 4DR"))
.SetProperty(b => b.CustomerInfo.Tag, "Tog"));
Bu kodun çalıştırılması, önceki örnekle aynı sorgu yürütmesine neden olur:
UPDATE [c]
SET [c].[CustomerInfo_Tag] = N'Tog',
[c].[CustomerInfo_HomeAddress_City] = N'Beetley',
[c].[CustomerInfo_HomeAddress_Country] = N'Norfolk',
[c].[CustomerInfo_HomeAddress_Line1] = N'Gressenhall Farm',
[c].[CustomerInfo_HomeAddress_Line2] = NULL,
[c].[CustomerInfo_HomeAddress_PostCode] = N'NR20 4DR',
[c].[CustomerInfo_WorkAddress_City] = N'Beetley',
[c].[CustomerInfo_WorkAddress_Country] = N'Norfolk',
[c].[CustomerInfo_WorkAddress_Line1] = N'Gressenhall Workhouse',
[c].[CustomerInfo_WorkAddress_Line2] = NULL,
[c].[CustomerInfo_WorkAddress_PostCode] = N'NR20 4DR'
FROM [Customers] AS [c]
WHERE [c].[Name] = @__name_0
Geçişler
Geliştirilmiş zamana bağlı tablo geçişleri
Mevcut bir tablo zamansal tabloya değiştirilirken oluşturulan geçiş EF9 için boyut olarak küçültüldü. Örneğin, EF8'de tek bir mevcut tabloyu zamana bağlı bir tablo haline getirmek aşağıdaki geçişle sonuçlandığında:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterTable(
name: "Blogs")
.Annotation("SqlServer:IsTemporal", true)
.Annotation("SqlServer:TemporalHistoryTableName", "BlogsHistory")
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
migrationBuilder.AlterColumn<string>(
name: "SiteUri",
table: "Blogs",
type: "nvarchar(max)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)")
.Annotation("SqlServer:IsTemporal", true)
.Annotation("SqlServer:TemporalHistoryTableName", "BlogsHistory")
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Blogs",
type: "nvarchar(max)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)")
.Annotation("SqlServer:IsTemporal", true)
.Annotation("SqlServer:TemporalHistoryTableName", "BlogsHistory")
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Blogs",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("SqlServer:Identity", "1, 1")
.Annotation("SqlServer:IsTemporal", true)
.Annotation("SqlServer:TemporalHistoryTableName", "BlogsHistory")
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart")
.OldAnnotation("SqlServer:Identity", "1, 1");
migrationBuilder.AddColumn<DateTime>(
name: "PeriodEnd",
table: "Blogs",
type: "datetime2",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
.Annotation("SqlServer:IsTemporal", true)
.Annotation("SqlServer:TemporalHistoryTableName", "BlogsHistory")
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
migrationBuilder.AddColumn<DateTime>(
name: "PeriodStart",
table: "Blogs",
type: "datetime2",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
.Annotation("SqlServer:IsTemporal", true)
.Annotation("SqlServer:TemporalHistoryTableName", "BlogsHistory")
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
}
EF9'da aynı işlem artık çok daha küçük bir geçişe neden olur:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterTable(
name: "Blogs")
.Annotation("SqlServer:IsTemporal", true)
.Annotation("SqlServer:TemporalHistoryTableName", "BlogsHistory")
.Annotation("SqlServer:TemporalHistoryTableSchema", null)
.Annotation("SqlServer:TemporalPeriodEndColumnName", "PeriodEnd")
.Annotation("SqlServer:TemporalPeriodStartColumnName", "PeriodStart");
migrationBuilder.AddColumn<DateTime>(
name: "PeriodEnd",
table: "Blogs",
type: "datetime2",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
.Annotation("SqlServer:TemporalIsPeriodEndColumn", true);
migrationBuilder.AddColumn<DateTime>(
name: "PeriodStart",
table: "Blogs",
type: "datetime2",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
.Annotation("SqlServer:TemporalIsPeriodStartColumn", true);
}
Model oluşturma
Diziler için önbelleğe almayı belirtme
İpucu
Burada gösterilen kod ModelBuildingSample.cs gelir.
EF9, bunu destekleyen herhangi bir ilişkisel veritabanı sağlayıcısı için veritabanı dizileri için önbelleğe alma seçeneklerinin ayarlanmasına olanak tanır. Örneğin, UseCache
önbelleğe almayı açıkça açmak ve önbellek boyutunu ayarlamak için kullanılabilir:
modelBuilder.HasSequence<int>("MyCachedSequence")
.HasMin(10).HasMax(255000)
.IsCyclic()
.StartsAt(11).IncrementsBy(2)
.UseCache(3);
Bu, SQL Server kullanılırken aşağıdaki sıra tanımıyla sonuçlandığında:
CREATE SEQUENCE [MyCachedSequence] AS int START WITH 11 INCREMENT BY 2 MINVALUE 10 MAXVALUE 255000 CYCLE CACHE 3;
Benzer şekilde, UseNoCache
önbelleğe almayı açıkça kapatır:
modelBuilder.HasSequence<int>("MyUncachedSequence")
.HasMin(10).HasMax(255000)
.IsCyclic()
.StartsAt(11).IncrementsBy(2)
.UseNoCache();
CREATE SEQUENCE [MyUncachedSequence] AS int START WITH 11 INCREMENT BY 2 MINVALUE 10 MAXVALUE 255000 CYCLE NO CACHE;
UseCache
UseNoCache
Veya çağrılmazsa, önbelleğe alma belirtilmez ve veritabanı varsayılan değeri ne olursa olsun kullanır. Bu, farklı veritabanları için farklı bir varsayılan değer olabilir.
Bu geliştirme, @bikbov tarafından katkıda bulundu. Çok teşekkürler!
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);
Not
Şu anda önizleme 2'de, tablo ilk kez oluşturulduğunda doldurma faktörlerinin dahil edilmediği bir hata vardır. Bu, Sorun #33269 tarafından izlenir
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/
, child1
HierarchyId
"/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, ile bteween child1
ve child2
koyan bir HierarchyId
/4/1/3/1/1.5/
düğü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.
Geri Bildirim
https://aka.ms/ContentUserFeedback.
Çok yakında: 2024 boyunca, içerik için geri bildirim mekanizması olarak GitHub Sorunları’nı kullanımdan kaldıracak ve yeni bir geri bildirim sistemiyle değiştireceğiz. Daha fazla bilgi için bkz.Gönderin ve geri bildirimi görüntüleyin