Co nowego w programie EF Core 9
EF Core 9 (EF9) to kolejna wersja po programie EF Core 8 i zaplanowana na wydanie w listopadzie 2024 r. Aby uzyskać szczegółowe informacje, zobacz Plan dla programu Entity Framework Core 9 .
Program EF9 jest dostępny jako codzienne kompilacje , które zawierają wszystkie najnowsze funkcje ef9 i poprawki interfejsu API. Przykłady w tym miejscu korzystają z tych codziennych kompilacji.
Napiwek
Przykładowe przykłady można uruchamiać i debugować , pobierając przykładowy kod z usługi GitHub. Każda poniższa sekcja zawiera linki do kodu źródłowego specyficznego dla tej sekcji.
Program EF9 jest przeznaczony dla platformy .NET 8 i dlatego może być używany z platformą .NET 8 (LTS) lub platformą .NET 9 (wersja zapoznawcza).
Napiwek
Dokumentacja co nowego jest aktualizowana dla każdej wersji zapoznawczej. Wszystkie przykłady są skonfigurowane do korzystania z codziennych kompilacji EF9, które zwykle mają kilka dodatkowych tygodni ukończonych prac w porównaniu do najnowszej wersji zapoznawczej. Zdecydowanie zachęcamy do korzystania z codziennych kompilacji podczas testowania nowych funkcji, aby nie wykonywać testów względem nieaktualnych bitów.
Azure Cosmos DB for NoSQL
Pracujemy nad znaczącymi aktualizacjami w programie EF9 u dostawcy bazy danych EF Core dla usługi Azure Cosmos DB for NoSQL.
Dostęp oparty na rolach
Usługa Azure Cosmos DB for NoSQL zawiera wbudowany system kontroli dostępu opartej na rolach (RBAC). Jest to teraz obsługiwane przez program EF9 zarówno do zarządzania kontenerami, jak i korzystania z nich. Do kodu aplikacji nie są wymagane żadne zmiany. Aby uzyskać więcej informacji, zobacz Problem nr 32197 .
Dostęp synchroniczny domyślnie zablokowany
Napiwek
Pokazany tutaj kod pochodzi z CosmosSyncApisSample.cs.
Usługa Azure Cosmos DB for NoSQL nie obsługuje dostępu synchronicznego (blokowania) z kodu aplikacji. Wcześniej program EF maskował to domyślnie przez blokowanie w przypadku wywołań asynchronicznych. Jednak obie te metody zachęcają do używania synchronizacji, co jest złym rozwiązaniem i może spowodować zakleszczenia. W związku z tym, począwszy od ef9, podczas próby synchronicznego dostępu jest zgłaszany wyjątek. Na przykład:
System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Database.SyncNotSupported':
Azure Cosmos DB does not support synchronous I/O. Make sure to use and correctly await only async methods when using
Entity Framework Core to access Azure Cosmos DB. See https://aka.ms/ef-cosmos-nosync for more information.
This exception can be suppressed or logged by passing event ID 'CosmosEventId.SyncNotSupported' to the 'ConfigureWarnings'
method in 'DbContext.OnConfiguring' or 'AddDbContext'.
at Microsoft.EntityFrameworkCore.Diagnostics.EventDefinition.Log[TLoggerCategory](IDiagnosticsLogger`1 logger, Exception exception)
at Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal.CosmosLoggerExtensions.SyncNotSupported(IDiagnosticsLogger`1 diagnostics)
at Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal.CosmosClientWrapper.DeleteDatabase()
at Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal.CosmosDatabaseCreator.EnsureDeleted()
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureDeleted()
Jak mówi wyjątek, dostęp do synchronizacji może być nadal używany na razie przez odpowiednie skonfigurowanie poziomu ostrzeżenia. Na przykład w OnConfiguring
typie DbContext
:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ConfigureWarnings(b => b.Ignore(CosmosEventId.SyncNotSupported));
Należy jednak pamiętać, że planujemy w pełni usunąć obsługę synchronizacji w programie EF11, więc zacznij aktualizować się tak szybko, aby używać metod asynchronicznych, takich jak ToListAsync
i SaveChangesAsync
jak najszybciej!
Rozszerzone kolekcje pierwotne
Napiwek
Pokazany tutaj kod pochodzi z CosmosPrimitiveTypesSample.cs.
Dostawca usługi Cosmos DB obsługuje kolekcje pierwotne w ograniczonej formie od czasu platformy EF Core 6. Ta obsługa jest rozszerzana w programie EF9, począwszy od konsolidacji metadanych i powierzchni interfejsu API dla kolekcji pierwotnych w bazach danych dokumentów w celu dopasowania ich do kolekcji pierwotnych w relacyjnych bazach danych. Oznacza to, że kolekcje pierwotne można teraz jawnie mapować przy użyciu interfejsu API tworzenia modelu, co umożliwia skonfigurowanie aspektów typu elementu. Aby na przykład zamapować listę wymaganych ciągów (tj. ciągów innych niż null):
modelBuilder.Entity<Book>()
.PrimitiveCollection(e => e.Quotes)
.ElementType(b => b.IsRequired());
Aby uzyskać więcej informacji na temat interfejsu API tworzenia modelu, zobacz Co nowego w programie EF8: kolekcje pierwotne.
Zapytania AOT i wstępnie skompilowane
Jak wspomniano we wprowadzeniu, istnieje wiele prac w tle, aby umożliwić programowi EF Core uruchamianie bez kompilacji just in time (JIT). Zamiast tego program EF skompiluje wszystko, co jest potrzebne do uruchamiania zapytań w aplikacji. Ta kompilacja AOT i powiązane przetwarzanie będą wykonywane w ramach kompilowania i publikowania aplikacji. W tym momencie w wersji EF9 nie ma zbyt wiele dostępnych, które mogą być używane przez Ciebie, deweloper aplikacji. Jednak dla osób zainteresowanych ukończone problemy w programie EF9, które obsługują usługę AOT i wstępnie skompilowane zapytania, to:
- Skompilowany model: użyj powiązania statycznego zamiast odbicia dla właściwości i pól
- Skompilowany model: generowanie wyrażeń lambda używanych w śledzeniu zmian
- Wprowadzanie śledzenia zmian i potoku aktualizacji zgodnego z funkcją AOT/przycinaniem
- Przekierowywanie zapytania do wstępnie skompilowanego kodu za pomocą przechwytywania
- Ustaw wszystkie węzły wyrażeń SQL na cudzysłów
- Generowanie skompilowanego modelu podczas kompilacji
- Automatyczne odnajdywanie skompilowanego modelu
- Ustaw parametrExtractingExpressionVisitor w stanie wyodrębnić ścieżki do fragmentów ewaluowanych w drzewie
- Generowanie drzew wyrażeń w skompilowanych modelach (filtry zapytań, konwertery wartości)
- Zwiększenie odporności linqToCSharpSyntaxTranslator na wiele deklaracji tej samej zmiennej w zagnieżdżonych zakresach
- Optymalizowanie parametruExtractingExpressionVisitor
Zapoznaj się z tym artykułem, aby zapoznać się z przykładami użycia wstępnie skompilowanych zapytań podczas łączenia środowiska.
TŁUMACZENIE LINQ i SQL
Zespół pracuje nad istotnymi zmianami architektury potoku zapytań w programie EF Core 9 w ramach naszych ciągłych ulepszeń mapowania JSON i baz danych dokumentów. Oznacza to, że musimy zapewnić innym osobom, jak Ty , aby uruchamiali kod w tych nowych elementach wewnętrznych. (Jeśli czytasz dokument "Co nowego" w tym momencie w wydaniu, jesteś naprawdę zaangażowaną częścią społeczności; dziękuję!) Mamy ponad 120.000 testów, ale to nie wystarczy! Potrzebujemy Ciebie, ludzi, którzy uruchamiają prawdziwy kod na naszych bitach, aby znaleźć problemy i wysłać solidną wersję!
Grupuj według typów złożonych
Napiwek
Pokazany tutaj kod pochodzi z ComplexTypesSample.cs.
Program EF9 obsługuje grupowanie według wystąpienia typu złożonego. Na przykład:
var groupedAddresses = await context.Stores
.GroupBy(b => b.StoreAddress)
.Select(g => new { g.Key, Count = g.Count() })
.ToListAsync();
Ef tłumaczy to jako grupowanie według każdego elementu członkowskiego typu złożonego, który jest zgodny z semantyka typów złożonych jako obiekty wartości. Na przykład w usłudze Azure SQL:
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]
Przycinanie kolumn przekazanych do klauzuli WITH pliku OPENJSON
Napiwek
Pokazany tutaj kod pochodzi z JsonColumnsSample.cs.
Program EF9 usuwa niepotrzebne kolumny podczas wywoływania elementu OPENJSON WITH
. Rozważmy na przykład zapytanie, które uzyskuje liczbę z kolekcji JSON przy użyciu predykatu:
var postsUpdatedOn = await context.Posts
.Where(p => p.Metadata!.Updates.Count(e => e.UpdatedOn >= date) == 1)
.ToListAsync();
W programie EF8 to zapytanie generuje następujący kod SQL podczas korzystania z dostawcy usługi Azure SQL Database:
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
Zwróć uwagę, że element UpdatedBy
i Commits
nie są potrzebne w tym zapytaniu. Począwszy od ef9, te kolumny są teraz przycinane:
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
W niektórych scenariuszach powoduje to całkowite usunięcie klauzuli WITH
. Na przykład:
var tagsWithCount = await context.Tags.Where(p => p.Text.Length == 1).ToListAsync();
W programie EF8 to zapytanie przekłada się na następujący kod SQL:
SELECT [t].[Id], [t].[Text]
FROM [Tags] AS [t]
WHERE (
SELECT COUNT(*)
FROM OPENJSON([t].[Text]) WITH ([value] nvarchar(max) '$') AS [t0]) = 1
W programie EF9 zostało to ulepszone w następujący sposób:
SELECT [t].[Id], [t].[Text]
FROM [Tags] AS [t]
WHERE (
SELECT COUNT(*)
FROM OPENJSON([t].[Text]) AS [t0]) = 1
Tłumaczenia z udziałem GREATEST/LEAST
Napiwek
Pokazany tutaj kod pochodzi z LeastGreatestSample.cs.
Wprowadzono kilka nowych tłumaczeń korzystających z GREATEST
funkcji i LEAST
SQL.
Ważne
Funkcje GREATEST
i LEAST
wprowadzone w bazach danych SQL Server/Azure SQL Database w wersji 2022. Program Visual Studio 2022 domyślnie instaluje program SQL Server 2019. Zalecamy zainstalowanie programu SQL Server Developer Edition 2022 , aby wypróbować te nowe tłumaczenia w programie EF9.
Na przykład zapytania używające lub Math.Max
Math.Min
są teraz tłumaczone dla usługi Azure SQL przy użyciu i GREATEST
LEAST
odpowiednio. Na przykład:
var walksUsingMin = await context.Walks
.Where(e => Math.Min(e.DaysVisited.Count, e.ClosestPub.Beers.Length) > 4)
.ToListAsync();
To zapytanie jest tłumaczone na następujący kod SQL podczas korzystania z programu EF9 wykonującego względem programu SQL Server 2022:
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
można Math.Max
również używać wartości kolekcji pierwotnej. Na przykład:
var pubsInlineMax = await context.Pubs
.SelectMany(e => e.Counts)
.Where(e => Math.Max(e, threshold) > top)
.ToListAsync();
To zapytanie jest tłumaczone na następujący kod SQL podczas korzystania z programu EF9 wykonującego względem programu SQL Server 2022:
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
Na koniec i RelationalDbFunctionsExtensions.Greatest
może służyć do bezpośredniego wywoływania Least
funkcji or Greatest
w języku SQL. Na przykład:
var leastCount = await context.Pubs
.Select(e => EF.Functions.Least(e.Counts.Length, e.DaysVisited.Count, e.Beers.Length))
.ToListAsync();
To zapytanie jest tłumaczone na następujący kod SQL podczas korzystania z programu EF9 wykonującego względem programu SQL Server 2022:
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]
Wymuszanie lub zapobieganie parametryzacji zapytań
Napiwek
Pokazany tutaj kod pochodzi z QuerySample.cs.
Z wyjątkiem niektórych specjalnych przypadków, EF Core parametryzuje zmienne używane w zapytaniu LINQ, ale zawiera stałe w wygenerowanym języku SQL. Rozważmy na przykład następującą metodę zapytania:
async Task<List<Post>> GetPosts(int id)
=> await context.Posts
.Where(
e => e.Title == ".NET Blog" && e.Id == id)
.ToListAsync();
Przekłada się to na następujące parametry SQL i podczas korzystania z usługi Azure SQL:
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
Zwróć uwagę, że program EF utworzył stałą w języku SQL dla bloga platformy ".NET", ponieważ ta wartość nie zmieni się z zapytania na kwerendę. Użycie stałej umożliwia badanie tej wartości przez aparat bazy danych podczas tworzenia planu zapytania, co może spowodować zwiększenie wydajności zapytania.
Z drugiej strony wartość parametru id
jest sparametryzowana, ponieważ to samo zapytanie może być wykonywane z wieloma różnymi wartościami dla elementu id
. Utworzenie stałej w tym przypadku powoduje zanieczyszczenie pamięci podręcznej zapytań z dużą częścią zapytań, które różnią się tylko wartościami parametrów. Jest to bardzo złe w przypadku ogólnej wydajności bazy danych.
Mówiąc ogólnie, te wartości domyślne nie powinny być zmieniane. Jednak program EF Core 8.0.2 wprowadza metodę EF.Constant
, która wymusza użycie stałej przez program EF, nawet jeśli parametr będzie używany domyślnie. Na przykład:
async Task<List<Post>> GetPostsForceConstant(int id)
=> await context.Posts
.Where(
e => e.Title == ".NET Blog" && e.Id == EF.Constant(id))
.ToListAsync();
Tłumaczenie zawiera teraz stałą dla id
wartości:
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
Program EF9 wprowadza metodę EF.Parameter
do wykonania odwrotnego. Oznacza to, że wymuś użycie parametru przez program EF, nawet jeśli wartość jest stałą w kodzie. Na przykład:
async Task<List<Post>> GetPostsForceParameter(int id)
=> await context.Posts
.Where(
e => e.Title == EF.Parameter(".NET Blog") && e.Id == id)
.ToListAsync();
Tłumaczenie zawiera teraz parametr ciągu bloga ".NET":
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
Niezrelowane podzapytania
Napiwek
Pokazany tutaj kod pochodzi z QuerySample.cs.
W programie EF8 zapytanie IQueryable, do których odwołuje się inne zapytanie, może być wykonywane jako oddzielna dwukierunkowa baza danych. Rozważmy na przykład następujące zapytanie LINQ:
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();
W programie EF8 zapytanie jest dotnetPosts
wykonywane jako jedna runda, a następnie końcowe wyniki są wykonywane jako drugie zapytanie. Na przykład w programie SQL Server:
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
W programie EF9 IQueryable
element w elemecie dotnetPosts
jest podkreślony, co powoduje pojedynczą rundę:
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
Nowe ToHashSetAsync<T>
metody
Napiwek
Pokazany tutaj kod pochodzi z QuerySample.cs.
Metody Enumerable.ToHashSet istniały od platformy .NET Core 2.0. W programie EF9 dodano równoważne metody asynchroniczne. Na przykład:
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);
To ulepszenie zostało wprowadzone przez @wertzui. Dziękujemy!
ExecuteUpdate i ExecuteDelete
Zezwalaj na przekazywanie wystąpień typu złożonego do polecenia ExecuteUpdate
Napiwek
Pokazany tutaj kod pochodzi z ExecuteUpdateSample.cs.
Interfejs ExecuteUpdate
API został wprowadzony w programie EF7 w celu natychmiastowego, bezpośredniego aktualizowania bazy danych bez śledzenia lub SaveChanges
. Na przykład:
await context.Stores
.Where(e => e.Region == "Germany")
.ExecuteUpdateAsync(s => s.SetProperty(b => b.Region, "Deutschland"));
Uruchomienie tego kodu powoduje wykonanie następującego zapytania w celu zaktualizowania elementu Region
do elementu "Deutschland":
UPDATE [s]
SET [s].[Region] = N'Deutschland'
FROM [Stores] AS [s]
WHERE [s].[Region] = N'Germany'
W programie EF8 ExecuteUpdate
można również użyć do aktualizowania wartości właściwości typu złożonego. Jednak każdy element członkowski typu złożonego musi być określony jawnie. Na przykład:
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));
Uruchomienie tego kodu powoduje wykonanie następującego zapytania:
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'
W programie EF9 tę samą aktualizację można wykonać przez przekazanie samego wystąpienia typu złożonego. Oznacza to, że każdy element członkowski nie musi być jawnie określony. Na przykład:
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));
Uruchomienie tego kodu powoduje wykonanie tego samego zapytania co w poprzednim przykładzie:
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'
Wiele aktualizacji zarówno właściwości typu złożonego, jak i prostych właściwości można połączyć w jednym wywołaniu metody ExecuteUpdate
. Na przykład:
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"));
Uruchomienie tego kodu powoduje wykonanie tego samego zapytania co w poprzednim przykładzie:
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
Migracje
Ulepszone migracje tabel czasowych
Migracja utworzona podczas zmiany istniejącej tabeli na tabelę czasową została zmniejszona dla programu EF9. Na przykład w programie EF8 utworzenie pojedynczej istniejącej tabeli powoduje wykonanie następującej migracji:
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");
}
W programie EF9 ta sama operacja powoduje teraz znacznie mniejszą migrację:
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);
}
Kompilowanie modelu
Modele kompilowane automatycznie
Napiwek
Pokazany tutaj kod pochodzi z przykładu NewInEFCore9.CompiledModels .
Skompilowane modele mogą poprawić czas uruchamiania aplikacji z dużymi modelami — jest to liczba typów jednostek w 100 lub 1000. W poprzednich wersjach programu EF Core skompilowany model musiał zostać wygenerowany ręcznie przy użyciu wiersza polecenia. Na przykład:
dotnet ef dbcontext optimize
Po uruchomieniu polecenia należy dodać wiersz podobny do polecenia , .UseModel(MyCompiledModels.BlogsContextModel.Instance)
aby OnConfiguring
poinformować platformę EF Core o użyciu skompilowanego modelu.
Począwszy od ef9, ten .UseModel
wiersz nie jest już potrzebny, gdy typ aplikacji DbContext
znajduje się w tym samym projekcie/zestawie co skompilowany model. Zamiast tego skompilowany model zostanie wykryty i użyty automatycznie. Można to zobaczyć, logując program EF za każdym razem, gdy kompiluje model. Uruchomienie prostej aplikacji powoduje wyświetlenie kompilowania modelu przez platformę EF po uruchomieniu aplikacji:
Starting application...
>> EF is building the model...
Model loaded with 2 entity types.
Dane wyjściowe z uruchamiania dotnet ef dbcontext optimize
w projekcie modelu to:
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>
Zwróć uwagę, że dane wyjściowe dziennika wskazują, że model został skompilowany podczas uruchamiania polecenia. Jeśli teraz ponownie uruchomimy aplikację, po ponownym skompilowaniu, ale bez wprowadzania żadnych zmian w kodzie, dane wyjściowe to:
Starting application...
Model loaded with 2 entity types.
Zwróć uwagę, że model nie został skompilowany podczas uruchamiania aplikacji, ponieważ skompilowany model został wykryty i użyty automatycznie.
Integracja z programem MSBuild
W przypadku powyższego podejścia skompilowany model nadal musi być ponownie wygenerowany ręcznie po zmianie typów jednostek lub DbContext
konfiguracji. Jednak program EF9 jest dostarczany z programem MSBuild i pakietem docelowym, który może automatycznie aktualizować skompilowany model po skompilowaniu projektu modelu. Aby rozpocząć, zainstaluj pakiet NuGet Microsoft.EntityFrameworkCore.Tasks . Na przykład:
dotnet add package Microsoft.EntityFrameworkCore.Tasks --version 9.0.0-preview.4.24205.3
Napiwek
Użyj wersji pakietu w poleceniu powyżej, który pasuje do używanej wersji programu EF Core.
Następnie włącz integrację, ustawiając EFOptimizeContext
właściwość na plik .csproj
. Na przykład:
<PropertyGroup>
<EFOptimizeContext>true</EFOptimizeContext>
</PropertyGroup>
Istnieją dodatkowe, opcjonalne właściwości programu MSBuild do kontrolowania sposobu kompilowania modelu, co odpowiada opcjom przekazywanym w wierszu polecenia do dotnet ef dbcontext optimize
. Są to:
Właściwość MSBuild | opis |
---|---|
EFOptimizeContext | Ustaw wartość na , aby true włączyć modele kompilowane automatycznie. |
DbContextName | Klasa DbContext do użycia. Tylko nazwa klasy lub w pełni kwalifikowana z przestrzeniami nazw. Jeśli ta opcja zostanie pominięta, program EF Core znajdzie klasę kontekstu. Jeśli istnieje wiele klas kontekstowych, ta opcja jest wymagana. |
EFStartupProject | Względna ścieżka do projektu startowego. Wartość domyślna to bieżący folder. |
EFTargetNamespace | Przestrzeń nazw do użycia dla wszystkich wygenerowanych klas. Domyślnie są generowane z głównej przestrzeni nazw i katalogu wyjściowego oraz CompiledModels. |
W naszym przykładzie musimy określić projekt startowy:
<PropertyGroup>
<EFOptimizeContext>true</EFOptimizeContext>
<EFStartupProject>..\App\App.csproj</EFStartupProject>
</PropertyGroup>
Teraz, jeśli skompilujemy projekt, zobaczymy rejestrowanie w czasie kompilacji wskazujące, że kompilowany model jest kompilowany:
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 --startup-assembly D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App\bin\Release\net8.0\App.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
Uruchomiona aplikacja pokazuje, że skompilowany model został wykryty, dlatego model nie został skompilowany ponownie:
Starting application...
Model loaded with 2 entity types.
Teraz, gdy tylko zmieni się model, skompilowany model zostanie automatycznie skompilowany ponownie utworzony.
[UWAGA!] Pracujemy nad niektórymi problemami z wydajnością dotyczącymi zmian wprowadzonych w skompilowanym modelu w programach EF8 i EF9. Aby uzyskać więcej informacji, zobacz Problem 33483# .
Kolekcje pierwotne tylko do odczytu
Napiwek
Pokazany tutaj kod pochodzi z PrimitiveCollectionsSample.cs.
Program EF8 wprowadził obsługę mapowania tablic i modyfikowalnych list typów pierwotnych. Ta funkcja została rozszerzona w programie EF9 w celu uwzględnienia kolekcji/list tylko do odczytu. W szczególności program EF9 obsługuje kolekcje wpisane jako IReadOnlyList
, IReadOnlyCollection
lub ReadOnlyCollection
. Na przykład w poniższym kodzie DaysVisited
zostanie zamapowany zgodnie z konwencją jako pierwotna kolekcja dat:
public class DogWalk
{
public int Id { get; set; }
public string Name { get; set; }
public ReadOnlyCollection<DateOnly> DaysVisited { get; set; }
}
Kolekcja tylko do odczytu może być wspierana przez normalną, modyfikowaną kolekcję w razie potrzeby. Na przykład w poniższym kodzie DaysVisited
można mapować jako pierwotną kolekcję dat, pozwalając jednocześnie kodowi w klasie manipulować bazową listą.
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;
}
Te kolekcje mogą być następnie używane w zapytaniach w normalny sposób. Na przykład to zapytanie LINQ:
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();
Co przekłada się na następujący kod SQL w języku SQLite:
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"
Określanie buforowania dla sekwencji
Napiwek
Pokazany tutaj kod pochodzi z ModelBuildingSample.cs.
Program EF9 umożliwia ustawienie opcji buforowania dla sekwencji baz danych dla dowolnego dostawcy relacyjnej bazy danych, który to obsługuje. Na przykład UseCache
można użyć do jawnego włączenia buforowania i ustawienia rozmiaru pamięci podręcznej:
modelBuilder.HasSequence<int>("MyCachedSequence")
.HasMin(10).HasMax(255000)
.IsCyclic()
.StartsAt(11).IncrementsBy(2)
.UseCache(3);
Powoduje to następującą definicję sekwencji podczas korzystania z programu SQL Server:
CREATE SEQUENCE [MyCachedSequence] AS int START WITH 11 INCREMENT BY 2 MINVALUE 10 MAXVALUE 255000 CYCLE CACHE 3;
Podobnie jawnie UseNoCache
wyłącza buforowanie:
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;
Jeśli ani nie UseCache
UseNoCache
zostanie wywołana, buforowanie nie zostanie określone, a baza danych będzie używać dowolnej wartości domyślnej. Może to być inna wartość domyślna dla różnych baz danych.
To ulepszenie zostało wprowadzone przez @bikbov. Dziękujemy!
Określanie współczynnika wypełnienia dla kluczy i indeksów
Napiwek
Pokazany tutaj kod pochodzi z ModelBuildingSample.cs.
Program EF9 obsługuje specyfikację współczynnika wypełnienia programu SQL Server podczas używania migracji platformy EF Core do tworzenia kluczy i indeksów. W dokumentacji programu SQL Server "Podczas tworzenia lub odbudowy indeksu wartość współczynnika wypełnienia określa procent miejsca na każdej stronie na poziomie liścia, rezerwując resztę na każdej stronie jako wolne miejsce na przyszły wzrost".
Współczynnik wypełnienia można ustawić na jednym lub złożonym kluczu podstawowym i alternatywnym oraz indeksach. Na przykład:
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);
Po zastosowaniu do istniejących tabel spowoduje to zmianę tabel na współczynnik wypełnienia na ograniczenie:
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);
To ulepszenie zostało wprowadzone przez @deano-hunter. Dziękujemy!
Zwiększenie rozszerzalności istniejących konwencji tworzenia modelu
Napiwek
Pokazany tutaj kod pochodzi z CustomConventionsSample.cs.
Konwencje tworzenia modeli publicznych dla aplikacji zostały wprowadzone w programie EF7. W programie EF9 ułatwiliśmy rozszerzenie niektórych istniejących konwencji. Na przykład kod mapowania właściwości według atrybutu w programie EF7 to:
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;
}
}
}
}
W programie EF9 można to uprościć do następujących elementów:
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;
}
}
Zaktualizuj metodę ApplyConfigurationsFromAssembly, aby wywołać konstruktory niepublicznie
W poprzednich wersjach programu EF Core ApplyConfigurationsFromAssembly
metoda tworzyła tylko wystąpienia typów konfiguracji z publicznymi konstruktorami bez parametrów. W programie EF9 ulepszyliśmy komunikaty o błędach generowane w przypadku niepowodzenia, a także włączono tworzenie wystąpień przez konstruktora niepublizowanego. Jest to przydatne w przypadku współlokowania konfiguracji w prywatnej zagnieżdżonej klasie, która nigdy nie powinna być tworzone przez kod aplikacji. Na przykład:
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);
}
}
}
Na bok niektórzy uważają, że ten wzorzec jest obrzydliwieniem, ponieważ łączy typ jednostki z konfiguracją. Inne osoby uważają, że jest to bardzo przydatne, ponieważ współlokuje konfigurację z typem jednostki. Nie dyskutujmy tego tutaj. :-)
Identyfikator hierarchii programu SQL Server
Napiwek
Pokazany tutaj kod pochodzi z HierarchyIdSample.cs.
Cukier dla generowania ścieżki HierarchyId
Obsługa pierwszej klasy dla typu programu SQL Server HierarchyId
została dodana w programie EF8. W programie EF9 dodano metodę cukru, aby ułatwić tworzenie nowych węzłów podrzędnych w strukturze drzewa. Na przykład następujące zapytania dotyczące kodu dla istniejącej jednostki z właściwością HierarchyId
:
var daisy = await context.Halflings.SingleAsync(e => e.Name == "Daisy");
Tej HierarchyId
właściwości można następnie użyć do tworzenia węzłów podrzędnych bez jawnego manipulowania ciągami. Na przykład:
var child1 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1), "Toast");
var child2 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 2), "Wills");
Jeśli daisy
element ma HierarchyId
wartość /4/1/3/1/
z wartością , child1
otrzyma HierarchyId
wartość "/4/1/3/1/1/" i child2
uzyska HierarchyId
wartość "/4/1/3/1/2/".
Aby utworzyć węzeł między tymi dwoma elementami podrzędnym, można użyć dodatkowego poziomu podrzędnego. Na przykład:
var child1b = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1, 5), "Toast");
Spowoduje to utworzenie węzła z elementem HierarchyId
/4/1/3/1/1.5/
, umieszczając go w pliku bteween child1
i child2
.
To ulepszenie zostało wprowadzone przez @Rezakazemi890. Dziękujemy!
Narzędzia
Mniej ponownych kompilacji
Narzędzie dotnet ef
wiersza polecenia domyślnie kompiluje projekt przed wykonaniem narzędzia. Jest to spowodowane tym, że nie jest to ponowne kompilowanie przed uruchomieniem narzędzia jest typowym źródłem pomyłek, gdy rzeczy nie działają. Doświadczeni deweloperzy mogą użyć --no-build
opcji, aby uniknąć tej kompilacji, co może być powolne. Jednak nawet --no-build
opcja może spowodować ponowne skompilowanie projektu przy następnym skompilowaniu poza narzędziami EF.
Uważamy, że wkład społeczności z @Suchiman rozwiązał ten problem. Jednak jesteśmy również świadomi, że poprawki dotyczące zachowań MSBuild mają tendencję do niezamierzonych konsekwencji, więc prosimy ludzi, którzy lubią cię wypróbować i zgłosić z powrotem na wszelkie negatywne doświadczenia, które masz.
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla