Az EF Core 9 újdonságai
Az EF Core 9 (EF9) az EF Core 8 után a következő kiadás, amely 2024 novemberében várható.
Az EF9 napi buildek elérhetők, amelyek tartalmazzák az EF9 legújabb funkcióit és az API-finomításokat. Az itt található minták ezeket a napi buildeket használják.
Borravaló
Futtathatja és hibakeresést végezhet a mintákban, ha letölti a mintakódot a GitHubról. Az alábbi szakaszok az adott szakaszhoz tartozó forráskódra mutatnak.
Az EF9 a .NET 8-at célozza, ezért használható .NET 8 (LTS) vagy .NET 9.
Borravaló
Az What's New dokumentumok frissülnek minden előzetes verzióhoz. Az összes minta a EF9 napi buildekhasználatára van beállítva, amelyek általában több hétnyi befejezett munkával rendelkeznek a legújabb előzetes verzióhoz képest. Határozottan javasoljuk a napi buildek használatát az új funkciók tesztelése során, hogy ne az elavult bitek ellen tesztelje a teszteket.
Azure Cosmos DB NoSQL-hoz
Az EF 9.0 jelentős fejlesztéseket hoz az Azure Cosmos DB-hez készült EF Core-szolgáltatón; A szolgáltató jelentős részét átírták, hogy új funkciókat biztosítsanak, új lekérdezési formákat tegyenek lehetővé, és jobban igazodjanak a szolgáltatóhoz az Azure Cosmos DB ajánlott eljárásaihoz. A fő, magas szintű fejlesztéseket alább találja; a teljes listát, lásd ezt az epikus problémát.
Figyelmeztetés
A szolgáltatót érintő fejlesztések részeként számos jelentős, jelentős törést eredményező módosítást kellett végrehajtani; ha egy meglévő alkalmazást frissít, olvassa el a kompatibilitástörő változások szakaszt gondosan.
Fejlesztések a partíciókulcsokkal és a dokumentumazonosítókkal való lekérdezéshez
Az Azure Cosmos DB-adatbázisban tárolt minden dokumentum egyedi erőforrás-azonosítóval rendelkezik. Emellett minden dokumentum tartalmazhat egy "partíciókulcsot", amely meghatározza az adatok logikai particionálását, hogy az adatbázis hatékonyan méretezhető legyen. A partíciókulcsok kiválasztásáról további információ található a Particionálás és vízszintes skálázás az Azure Cosmos DB.
Az EF 9.0-ban az Azure Cosmos DB-szolgáltató jelentősen jobban azonosítja a partíciókulcs-összehasonlításokat a LINQ-lekérdezésekben, és kinyeri őket, hogy a lekérdezések csak a megfelelő partícióra legyenek elküldve; ez jelentősen javíthatja a lekérdezések teljesítményét, és csökkentheti az ru-díjakat. Például:
var sessions = await context.Sessions
.Where(b => b.PartitionKey == "someValue" && b.Username.StartsWith("x"))
.ToListAsync();
Ebben a lekérdezésben a szolgáltató automatikusan felismeri az összehasonlítást a PartitionKey
; ha megvizsgáljuk a naplókat, a következőket fogjuk látni:
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")
Vegye figyelembe, hogy a WHERE
záradék nem tartalmaz PartitionKey
: az összehasonlítás "feloldva" lett, és a lekérdezés csak a megfelelő partíción való végrehajtására szolgál. A korábbi verziókban az összehasonlítás sok esetben a WHERE
záradékban maradt, ami miatt a lekérdezést az összes partíción végrehajtották, ami megnövelte a költségeket és csökkentette a teljesítményt.
Emellett ha a lekérdezés a dokumentum azonosító tulajdonságának is értéket ad, és nem tartalmaz semmilyen más lekérdezési műveletet, a szolgáltató további optimalizálást alkalmazhat:
var somePartitionKey = "someValue";
var someId = 8;
var sessions = await context.Sessions
.Where(b => b.PartitionKey == somePartitionKey && b.Id == someId)
.SingleAsync();
A naplók a következőt mutatják a lekérdezéshez:
Executed ReadItem (73 ms, 1 RU) ActivityId='13f0f8b8-d481-47f0-bf41-67f7deb008b2', Container='test', Id='8', Partition='["someValue"]'
Itt a rendszer egyáltalán nem küld SQL-lekérdezést. Ehelyett a szolgáltató egy rendkívül hatékony pontolvasást hajt végre (ReadItem
API), amely közvetlenül lekéri a dokumentumot a partíciókulcs és az azonosító megadásával. Ez a leghatékonyabb és legköltséghatékonyabb olvasási típus az Azure Cosmos DB-ben; az Azure Cosmos DB dokumentációjában további információt a pontolvasásokról.
Ha többet szeretne megtudni a partíciókulcsokkal és pontolvasásokkal való lekérdezésről, tekintse meg a lekérdezés dokumentációjánaklapját.
Hierarchikus partíciókulcsok
Borravaló
Az itt látható kód HierarchicalPartitionKeysSample.csszármazik.
Az Azure Cosmos DB eredetileg egyetlen partíciókulcsot támogatott, de azóta bővítette a particionálási képességeket, hogy alrészezést is támogassa a partíciókulcslegfeljebb három hierarchiaszint specifikációja révén. Az EF Core 9 teljes körű támogatást nyújt a hierarchikus partíciókulcsokhoz, így kihasználhatja a funkcióhoz kapcsolódó jobb teljesítményt és költségmegtakarítást.
A partíciókulcsok a modellkészítő API használatával vannak megadva, általában DbContext.OnModelCreating. A partíciókulcs minden szintjén rendelkeznie kell egy hozzárendelt tulajdonságnak az entitástípusban. Vegyük például UserSession
entitástípust:
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!;
}
Az alábbi kód egy háromszintű partíciókulcsot határoz meg a TenantId
, UserId
és SessionId
tulajdonságok használatával:
modelBuilder
.Entity<UserSession>()
.HasPartitionKey(e => new { e.TenantId, e.UserId, e.SessionId });
Borravaló
Ez a partíciókulcs-definíció a Hierarchikus partíciókulcsok kiválasztása az Azure Cosmos DB dokumentációjában ismertetett példát követi.
Figyelje meg, hogy az EF Core 9-től kezdve bármilyen leképezett típus tulajdonságai használhatók a partíciókulcsban. A bool
és numerikus típusok, például a int SessionId
tulajdonság esetében a rendszer közvetlenül a partíciókulcsban használja az értéket. Más típusok, például a Guid UserId
tulajdonság, automatikusan sztringekké alakulnak.
Lekérdezéskor az EF automatikusan kinyeri a partíciókulcs értékeit a lekérdezésekből, és alkalmazza őket az Azure Cosmos DB lekérdezési API-ra, hogy a lekérdezések a lehető legkevesebb partícióra legyenek korlátozva. Vegyük például a következő LINQ-lekérdezést, amely a hierarchia mindhárom partíciókulcs-értékét tartalmazza:
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();
A lekérdezés végrehajtásakor az EF Core kinyeri a tenantId
, userId
és sessionId
paraméterek értékeit, és átadja őket az Azure Cosmos DB lekérdezési API-nak partíciókulcs-értékként. Lásd például a fenti lekérdezés végrehajtásának naplóit:
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"))
Figyelje meg, hogy a partíciókulcs-összehasonlításokat eltávolították a WHERE
záradékból, és ehelyett partíciókulcsként használják a hatékony végrehajtáshoz: ["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",7.0]
.
További információért lásd a partíciókulcsokkal való lekérdezés dokumentációját .
Jelentősen továbbfejlesztett LINQ-lekérdezési képességek
Az EF 9.0-ban az Azure Cosmos DB-szolgáltató LINQ-fordítási képességei jelentősen kibővültek, és a szolgáltató mostantól jelentősen több lekérdezéstípust képes végrehajtani. A lekérdezésfejlesztések teljes listája túl hosszú a listához, de a legfontosabbak a következők:
- Az EF primitív gyűjteményeinek teljes támogatása, lehetővé téve például ints vagy sztringek gyűjteményeinek LINQ-lekérdezését. Tekintse meg a Mi újdonság az EF8-ban: a primitív gyűjtemények további információkért.
- Nem primitív gyűjtemények tetszőleges lekérdezésének támogatása.
- Most már számos további LINQ-operátor támogatott: gyűjteményekbe,
Length
/Count
,ElementAt
,Contains
és sok másba való indexelés. - Összesítő operátorok, például
Count
ésSum
támogatása. - További függvényfordítások (a támogatott fordítások teljes listájához tekintse meg a függvényleképezések dokumentációját):
- Fordítások a
DateTime
ésDateTimeOffset
összetevő-komponensek tagjainak (DateTime.Year
,DateTimeOffset.Month
...). -
EF.Functions.IsDefined
ésEF.Functions.CoalesceUndefined
mostantól lehetővé teszikundefined
értékek kezelését. -
string.Contains
,StartsWith
ésEndsWith
mostantól támogatjaStringComparison.OrdinalIgnoreCase
.
- Fordítások a
A lekérdezési fejlesztések teljes listáját lásd a probléma-ben.
Továbbfejlesztett modellezés az Azure Cosmos DB- és JSON-szabványoknak megfelelően
Az EF 9.0 egy JSON-alapú dokumentumadatbázis természetesebb módjaival képez le Azure Cosmos DB-dokumentumokat, és segít együttműködni a dokumentumokhoz hozzáférő más rendszerekkel. Bár ez kompatibilitástörő módosításokkal jár, léteznek olyan API-k, amelyek minden esetben lehetővé teszik a 9.0 előtti viselkedés visszaállítását.
Egyszerűsített id
tulajdonságok megkülönböztetés nélkül
Először az EF korábbi verziói szúrták be a megkülönböztető értéket a JSON id
tulajdonságba, és olyan dokumentumokat gyártottak, mint a következők:
{
"id": "Blog|1099",
...
}
Ez azért történt, hogy a különböző típusú dokumentumok (pl. Blog és Post) és ugyanaz a kulcsérték (1099) létezhessenek ugyanazon a tárolópartíción belül. Az EF 9.0-tól kezdődően a id
tulajdonság csak a kulcsértéket tartalmazza:
{
"id": 1099,
...
}
Ez egy természetesebb módszer a JSON-ra való leképezésre, és megkönnyíti a külső eszközök és rendszerek számára az EF által létrehozott JSON-dokumentumokkal való interakciót; az ilyen külső rendszerek általában nem ismerik az EF diszkriminatív értékeit, amelyek alapértelmezés szerint .NET-típusokból származnak.
Vegye figyelembe, hogy ez egy kompatibilitástörő változás, mivel az EF már nem fogja tudni lekérdezni a meglévő dokumentumokat a régi id
formátumban. Egy API-t vezetünk be, amely visszaállítja az előző viselkedést. További részletekért tekintse meg a
Diszkriminatív tulajdonság átnevezve $type
Az alapértelmezett diszkriminatív tulajdonság neve korábban Discriminator
volt. Az EF 9.0 alapértelmezett értékét $type
-ra változtatja.
{
"id": 1099,
"$type": "Blog",
...
}
Ez a JSON-polimorfizmus új szabványát követi, amely jobb interoperabilitást tesz lehetővé más eszközökkel. Például, a .NET System.Text.Json szintén támogatja a polimorfizmust, és a $type
-t alapértelmezett megkülönböztető tulajdonságnévként (dokumentáció) használja.
Vegye figyelembe, hogy ez egy kompatibilitástörő változás, mivel az EF már nem fogja tudni lekérdezni a meglévő dokumentumokat a régi diszkriminatív tulajdonságnévvel. Az előző elnevezésre való visszaállítással kapcsolatos részletekért tekintse meg a kompatibilitástörő változás megjegyzését.
Vektoros hasonlóság keresése (előzetes verzió)
Az Azure Cosmos DB mostantól előzetes verziójú támogatást nyújt a vektoros hasonlóság kereséséhez. A vektoros keresés néhány alkalmazástípus alapvető része, beleértve az AI-t, a szemantikai keresést és másokat. Az Azure Cosmos DB lehetővé teszi a vektorok közvetlen tárolását a dokumentumokban a többi adat mellett, ami azt jelenti, hogy az összes lekérdezést egyetlen adatbázison hajthatja végre. Ez jelentősen leegyszerűsítheti az architektúrát, és szükségtelenné teheti egy további, dedikált vektoradatbázis-megoldást a rendszerben. Az Azure Cosmos DB vektorkeresésével kapcsolatos további információkért tekintse meg a dokumentációt.
Miután az Azure Cosmos DB-tároló megfelelően be van állítva, a vektorkeresés ef-en keresztüli használatával egyszerűen hozzáadhat egy vektortulajdonságot, és konfigurálhatja azt:
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);
}
}
Ha ez megtörtént, használja a linq-lekérdezések EF.Functions.VectorDistance()
függvényét a vektoros hasonlóság kereséséhez:
var blogs = await context.Blogs
.OrderBy(s => EF.Functions.VectorDistance(s.Vector, vector))
.Take(5)
.ToListAsync();
További információt a vektorkeresési
Lapozási funkció támogatása
Az Azure Cosmos DB-szolgáltató mostantól lehetővé teszi a lekérdezési eredmények lapozását folytatási jogkivonatokkal, ami sokkal hatékonyabb és költséghatékonyabb, mint a Skip
és Take
hagyományos használata:
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
}
Az új ToPageAsync
operátor egy CosmosPage
-et ad vissza, amely egy folytatási tokent tesz elérhetővé. Ezzel a tokennel hatékonyan folytathatja a lekérdezést egy későbbi időpontban, és lekérheti a következő 10 elemet.
var nextPage = await context.Sessions.OrderBy(s => s.Id).ToPageAsync(10, continuationToken);
További információ: a lapozásidokumentációs szakaszában.
FromSql a biztonságosabb SQL-lekérdezéshez
Az Azure Cosmos DB-szolgáltató engedélyezte az SQL-lekérdezést FromSqlRawkeresztül. Ez az API azonban érzékeny lehet az SQL-injektálási támadásokra, ha a felhasználó által megadott adatokat interpolálják vagy összefűzik az SQL-be. Az EF 9.0-ban most már használhatja az új FromSql
metódust, amely mindig paraméteres adatokat integrál az SQL-n kívüli paraméterként:
var maxAngle = 8;
_ = await context.Blogs
.FromSql($"SELECT VALUE c FROM root c WHERE c.Angle1 <= {maxAngle}")
.ToListAsync();
További információ: a lapozásidokumentációs szakaszában.
Szerepköralapú hozzáférés
Az Azure Cosmos DB for NoSQL tartalmaz egy beépített szerepköralapú hozzáférés-vezérlési (RBAC) rendszert. Ezt az EF9 mostantól minden adatsík-művelet esetében támogatja. Az Azure Cosmos DB SDK azonban nem támogatja az RBAC-t az Azure Cosmos DB felügyeletisík-műveleteihez. Az RBAC-vel EnsureCreatedAsync
helyett használja az Azure Management API-t.
A szinkron I/O alapértelmezés szerint le van tiltva
Az Azure Cosmos DB for NoSQL nem támogatja a szinkron (blokkoló) API-kat az alkalmazáskódból. Korábban az EF ezt az aszinkron hívások blokkolásával maszkolta el. Ez azonban mind a szinkron I/O-használatot ösztönzi, ami rossz gyakorlat, és holtpontot okozhat. Ezért az EF 9-től kezdve kivétel történik a szinkron hozzáférés megkísérlésekor. Például:
A szinkron I/O egyelőre továbbra is használható a figyelmeztetési szint megfelelő konfigurálásával. Például a OnConfiguring
mezőbe írja be a DbContext
típuson:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ConfigureWarnings(b => b.Ignore(CosmosEventId.SyncNotSupported));
Vegye figyelembe azonban, hogy az EF 11-ben teljes mértékben el szeretnénk távolítani a szinkronizálási támogatást, ezért a lehető leghamarabb frissítsen az olyan aszinkron módszerek használatára, mint ToListAsync
és SaveChangesAsync
!
AOT és előre lefordított lekérdezések
Figyelmeztetés
A NativeAOT és a lekérdezés-előfordítás rendkívül kísérleti funkciók, és még nem alkalmasak éles használatra. Az alábbiakban ismertetett támogatást infrastruktúraként kell tekinteni a végső funkció felé, amely valószínűleg az EF 10-zel fog megjelenni. Javasoljuk, hogy kísérletezzen az aktuális támogatással, és számoljon be a tapasztalatairól, de nem javasoljuk, hogy az EF NativeAOT-alkalmazásokat éles környezetben helyezze üzembe.
Az EF 9.0 kezdeti, kísérleti támogatást nyújt a .NET NativeAOT
Egy program például a következő EF-lekérdezéssel rendelkezik:
var blogs = await context.Blogs.Where(b => b.Name == "foo").ToListAsync();
Az EF létrehoz egy C#-elfogót a projektben, amely átveszi a lekérdezés végrehajtását. Ahelyett, hogy feldolgozta volna a lekérdezést, és lefordította volna SQL-re a program minden indításakor, az elfogó közvetlenül bele van ágyazva az SQL-be (ebben az esetben az SQL Server esetében), így a program sokkal gyorsabban indulhat el:
var relationalCommandTemplate = ((IRelationalCommandTemplate)(new RelationalCommand(materializerLiftableConstantContext.CommandBuilderDependencies, "SELECT [b].[Id], [b].[Name]\nFROM [Blogs] AS [b]\nWHERE [b].[Name] = N'foo'", new IRelationalParameter[] { })));
Emellett ugyanaz a interceptor tartalmaz kódot a .NET-objektum adatbázis-eredményekből való materializálásához:
var instance = new Blog();
UnsafeAccessor_Blog_Id_Set(instance) = dataReader.GetInt32(0);
UnsafeAccessor_Blog_Name_Set(instance) = dataReader.GetString(1);
Ez egy másik új .NET-funkciót használ – nem biztonságos hozzáférőket–, hogy az adatokat az adatbázisból az objektum privát mezőibe illessze be.
Ha érdekli a NativeAOT, és szeretne kísérletezni a legkorszerűbb funkciókkal, próbálja ki! Csak vegye figyelembe, hogy a funkciót instabilnak kell tekinteni, és jelenleg számos korlátozással rendelkezik; várhatóan stabilizálni fogjuk, és alkalmasabbá tesszük az EF 10-es éles használatra.
További részletekért tekintse meg az NativeAOT dokumentációs oldalát.
LINQ és SQL-fordítás
Mint minden kiadásnál, az EF9 is számos fejlesztést tartalmaz a LINQ lekérdezési képességeiben. Új lekérdezések fordíthatók le, és számos SQL-fordítás is javult a támogatott forgatókönyvek esetében a jobb teljesítmény és az olvashatóság érdekében.
A fejlesztések száma túl nagy ahhoz, hogy az összeset felsorolja itt. Az alábbiakban kiemelünk néhány fontosabb fejlesztést; lásd az számú kiadást a 9.0-s verzióban elvégzett munka teljesebb felsorolásáért.
Szeretnénk kiemelni Andrea Cancianit (@ranma42) a számos, kiváló minőségű hozzájárulásáért, amely az EF Core által generált SQL optimalizálására irányult!
Összetett típusok: A GroupBy és az ExecuteUpdate támogatása
GroupBy
Borravaló
Az itt látható kód ComplexTypesSample.csszármazik.
Az EF9 támogatja az összetett típusú példányok szerinti csoportosítást. Például:
var groupedAddresses = await context.Stores
.GroupBy(b => b.StoreAddress)
.Select(g => new { g.Key, Count = g.Count() })
.ToListAsync();
Az EF ezt az összetett típus egyes tagjai szerint csoportosítja, amely értékobjektumként igazodik az összetett típusok szemantikájának megfelelően. Például az Azure SQL-ben:
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]
FrissítésVégrehajtása
Borravaló
Az itt látható kód ExecuteUpdateSample.csszármazik.
Hasonlóképpen, az EF9 ExecuteUpdate
is javult az összetett típustulajdonságok elfogadásához. Az összetett típus minden tagját azonban explicit módon kell megadni. Például:
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));
Ez olyan SQL-t hoz létre, amely az egyes oszlopokat az összetett típusra frissíti:
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'
Korábban manuálisan kellett felsorolnia az összetett típus különböző tulajdonságait a ExecuteUpdate
hívásban.
Felesleges elemek eltávolítása az SQL-ből
Korábban az EF néha olyan SQL-t hozott létre, amely olyan elemeket tartalmazott, amelyekre valójában nincs szükség; a legtöbb esetben ezekre az SQL-feldolgozás egy korábbi szakaszában volt szükség, és lemaradtak. Az EF9 most már a legtöbb ilyen elemet metszi, ami kompaktabb és bizonyos esetekben hatékonyabb SQL-t eredményez.
Táblametszet
Első példaként az EF által létrehozott SQL néha tartalmazott JOIN-eket olyan táblákhoz, amelyekre valójában nincs szükség a lekérdezésben. Fontolja meg a következő modellt, amely tábla/típus (TPT) öröklés-leképezést használ:
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();
}
}
Ha ezután a következő lekérdezést hajtjuk végre, hogy az összes ügyfél megkapja legalább egy rendelést:
var customers = await context.Customers.Where(o => o.Orders.Any()).ToListAsync();
Az EF8 a következő SQL-t generálta:
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])
Vegye figyelembe, hogy a lekérdezés tartalmazott egy kapcsolódást a DiscountedOrders
táblához, annak ellenére, hogy nem történt hivatkozás oszlopokra. Az EF9 metszett SQL-t hoz létre illesztés nélkül:
SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE EXISTS (
SELECT 1
FROM [Orders] AS [o]
WHERE [c].[Id] = [o].[CustomerId])
Vetületmetsző metszés
Hasonlóképpen vizsgáljuk meg a következő lekérdezést:
var orders = await context.Orders
.Where(o => o.Amount > 10)
.Take(5)
.CountAsync();
Az EF8-on ez a lekérdezés a következő SQL-t generálta:
SELECT COUNT(*)
FROM (
SELECT TOP(@__p_0) [o].[Id]
FROM [Orders] AS [o]
WHERE [o].[Amount] > 10
) AS [t]
Vegye figyelembe, hogy a [o].[Id]
vetítésre nincs szükség az al lekérdezésben, mivel a külső SELECT kifejezés egyszerűen megszámolja a sorokat. Az EF9 ehelyett a következőket hozza létre:
SELECT COUNT(*)
FROM (
SELECT TOP(@__p_0) 1 AS empty
FROM [Orders] AS [o]
WHERE [o].[Amount] > 10
) AS [s]
... és a vetület üres. Ez talán nem tűnik soknak, de bizonyos esetekben nagymértékben leegyszerűsítheti az SQL-t. Javasoljuk, hogy nézze meg néhány SQL változtatást a tesztekben, hogy lássa a hatást.
A GREATEST/LEAST-t tartalmazó fordítások
Tipp
Az itt látható kód LeastGreatestSample.csszármazik.
Számos új fordítás jelent meg, amelyek a GREATEST
és LEAST
SQL-függvényeket használják.
Fontos
A GREATEST
és LEAST
függvények 2022-es verzióban jelentek meg az SQL Server/Azure SQL-adatbázisokban. A Visual Studio 2022 alapértelmezés szerint telepíti az SQL Server 2019-et. Javasoljuk, hogy telepítse SQL Server Developer Edition 2022, hogy kipróbálhassa ezeket az új fordításokat az EF9-ben.
A Math.Max
vagy Math.Min
használó lekérdezések például most már lefordítva vannak az Azure SQL-hez GREATEST
és LEAST
használatával. Például:
var walksUsingMin = await context.Walks
.Where(e => Math.Min(e.DaysVisited.Count, e.ClosestPub.Beers.Length) > 4)
.ToListAsync();
Ez a lekérdezés a következő SQL-re lesz átalakítva, amikor az SQL Server 2022-n EF9-et hajt végre.
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
és Math.Max
egy primitív adathalmaz értékein is használható. Például:
var pubsInlineMax = await context.Pubs
.SelectMany(e => e.Counts)
.Where(e => Math.Max(e, threshold) > top)
.ToListAsync();
Ez a lekérdezés az alábbi SQL-re fordul, amikor az EF9-et az SQL Server 2022-vel együtt hajtják végre.
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
Végül RelationalDbFunctionsExtensions.Least
és RelationalDbFunctionsExtensions.Greatest
használható a Least
vagy Greatest
függvény közvetlen meghívására az SQL-ben. Például:
var leastCount = await context.Pubs
.Select(e => EF.Functions.Least(e.Counts.Length, e.DaysVisited.Count, e.Beers.Length))
.ToListAsync();
Ez a lekérdezés a következő SQL-re kerül lefordításra, amikor az EF9-et az SQL Server 2022-n hajtják végre.
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]
Lekérdezésparaméterezés kényszerítése vagy megakadályozása
Borravaló
Az alábbi kód a QuerySample.csfájlból származik.
Néhány speciális eset kivételével az EF Core paraméterezi a LINQ-lekérdezésekben használt változókat, de a generált SQL-ben állandókat is tartalmaz. Vegyük például a következő lekérdezési módszert:
async Task<List<Post>> GetPosts(int id)
=> await context.Posts
.Where(e => e.Title == ".NET Blog" && e.Id == id)
.ToListAsync();
Ez a következő SQL-ekre és paraméterekre fordítható le az Azure SQL használatakor:
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
Figyelje meg, hogy az EF létrehozott egy állandót a ".NET Blog" SQL-ben, mert ez az érték nem változik lekérdezésről lekérdezésre. Az állandó használata lehetővé teszi, hogy az adatbázismotor megvizsgálja ezt az értéket egy lekérdezési terv létrehozásakor, ami hatékonyabb lekérdezést eredményezhet.
Másrészt a id
értéke paraméteres, mivel ugyanaz a lekérdezés számos különböző értékkel végrehajtható a id
. Ha ebben az esetben konstanst hoz létre, az a lekérdezési gyorsítótár szennyezését eredményezi, és sok olyan lekérdezést tartalmaz, amelyek csak id
értékekben különböznek. Ez nagyon rossz az adatbázis általános teljesítményére.
Általánosságban elmondható, hogy ezeket az alapértelmezett értékeket nem szabad módosítani. Az EF Core 8.0.2 azonban egy EF.Constant
metódust vezet be, amely arra kényszeríti az EF-t, hogy állandót használjon akkor is, ha alapértelmezés szerint paramétert használna. Például:
async Task<List<Post>> GetPostsForceConstant(int id)
=> await context.Posts
.Where(e => e.Title == ".NET Blog" && e.Id == EF.Constant(id))
.ToListAsync();
A fordítás mostantól a id
érték konstansát tartalmazza.
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
A EF.Parameter
metódus
Az EF9 az ellenkezőjére vezeti be a EF.Parameter
metódust. Vagyis kényszerítse az EF-t, hogy akkor is használjon paramétert, ha az érték állandó a kódban. Például:
async Task<List<Post>> GetPostsForceParameter(int id)
=> await context.Posts
.Where(e => e.Title == EF.Parameter(".NET Blog") && e.Id == id)
.ToListAsync();
A fordítás most már tartalmaz egy paramétert a ".NET Blog" sztringhez:
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
Paraméterezett primitív kollekciók
Az EF8 megváltoztatta a primitív gyűjteményeket használó lekérdezések fordítását. Ha egy LINQ-lekérdezés paraméteres primitív gyűjteményt tartalmaz, az EF átalakítja annak tartalmát JSON-ra, és egyetlen paraméterértékként adja át a lekérdezést:
async Task<List<Post>> GetPostsPrimitiveCollection(int[] ids)
=> await context.Posts
.Where(e => e.Title == ".NET Blog" && ids.Contains(e.Id))
.ToListAsync();
Ez a következő fordítást eredményezi az SQL Serveren:
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]
)
Ez lehetővé teszi ugyanazt az SQL-lekérdezést a különböző paraméteres gyűjteményekhez (csak a paraméterérték változik), de bizonyos esetekben teljesítményproblémákhoz vezethet, mivel az adatbázis nem tudja optimálisan megtervezni a lekérdezést. A EF.Constant
metódussal visszaállítható az előző fordítás.
A következő lekérdezés ehhez EF.Constant
használ:
async Task<List<Post>> GetPostsForceConstantCollection(int[] ids)
=> await context.Posts
.Where(
e => e.Title == ".NET Blog" && EF.Constant(ids).Contains(e.Id))
.ToListAsync();
Az eredményként kapott SQL a következő:
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)
Az EF9 emellett TranslateParameterizedCollectionsToConstants
környezeti lehetőséget is bevezet, amely az összes lekérdezés primitív gyűjteményparaméterezésének megakadályozására használható. Egy kiegészítő TranslateParameterizedCollectionsToParameters
is hozzáadtunk, amely explicit módon kényszeríti a primitív gyűjtemények paraméterezését (ez az alapértelmezett viselkedés).
Borravaló
A EF.Parameter
metódus felülírja a környezeti beállítást. Ha meg szeretné akadályozni a primitív gyűjtemények paraméterezését a legtöbb lekérdezéshez (de nem az összeshez), beállíthatja a környezeti beállítást TranslateParameterizedCollectionsToConstants
, és EF.Parameter
használhat a paraméterezni kívánt lekérdezésekhez vagy egyéni változókhoz.
Beágyazott, korrelálatlan allekérdezések
Borravaló
Az itt látható kód QuerySample.cs.
Az EF8-ban egy másik lekérdezésben hivatkozott IQueryable külön adatbázis-kerekítésként végrehajtható. Vegyük például a következő LINQ-lekérdezést:
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();
Az EF8-ban a dotnetPosts
lekérdezése egy körútként lesz végrehajtva, majd a végső eredmények második lekérdezésként lesznek végrehajtva. Például az SQL Serveren:
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
Az EF9-ben a IQueryable
a dotnetPosts
-ben inline van, ami egyetlen adatbázis lekérdezést eredményez.
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
Szublekérdezések és összesítések fölötti összegző függvények az SQL Serveren
Az EF9 javítja egyes összetett lekérdezések fordítását alkérések vagy egyéb összesítő függvények alapján összeállított összesítő függvények használatával. Az alábbi példa az ilyen lekérdezésre:
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();
Először is Select
kiszámítja LatestPostRating
minden egyes Post
esetében, ami az SQL-re való fordítás során al-lekérdezést igényel. A lekérdezés későbbi részében ezek az eredmények Average
művelettel lesznek összesítve. Az eredményként kapott SQL a következőképpen néz ki az SQL Serveren való futtatáskor:
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]
A korábbi verziókban az EF Core érvénytelen SQL-t hozna létre hasonló lekérdezésekhez, és az összesítő műveletet közvetlenül az al lekérdezésre próbálná alkalmazni. Ez az SQL Serveren nem engedélyezett, és kivételt eredményez. Ugyanez az elv vonatkozik az aggregátumot használó lekérdezésekre egy másik aggregátumon keresztül:
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();
Jegyzet
Ez a változás nem érinti az Sqlite-t, amely támogatja az aggregátumokat az alregátumokon (vagy egyéb összesítéseken) keresztül, és nem támogatja a LATERAL JOIN
(APPLY
). Az alábbiakban látható az SQLite-on futó első lekérdezés SQL-címe:
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"
A Count != 0 függvényt használó lekérdezések optimalizálva vannak
Borravaló
Az itt látható kód a QuerySample.cs-ből származik.
Az EF8-ban a következő LINQ-lekérdezés lett lefordítva az SQL COUNT
függvény használatára:
var blogsWithPost = await context.Blogs
.Where(b => b.Posts.Count > 0)
.ToListAsync();
Az EF9 mostantól hatékonyabb fordítást hoz létre EXISTS
használatával:
SELECT "b"."Id", "b"."Name", "b"."SiteUri"
FROM "Blogs" AS "b"
WHERE EXISTS (
SELECT 1
FROM "Posts" AS "p"
WHERE "b"."Id" = "p"."BlogId")
C# szemantikája null értékű összehasonlító műveletekhez
Az EF8-ban egyes forgatókönyvekben a null értékű elemek összehasonlítása nem volt megfelelően végrehajtva. C#-ban, ha az egyik vagy mindkét operandus null értékű, az összehasonlító művelet eredménye hamis; ellenkező esetben az operandusok tartalmazott értékeit hasonlítjuk össze. Az EF8-ban az összehasonlításokat nulladatbázis-szemantikával fordítottuk le. Ez a LINQ-ból objektumokhoz használt hasonló lekérdezésnél eltérő eredményeket eredményezne. Ezenkívül eltérő eredményeket adnánk, ha a szűrés és a vetítés összehasonlítását végeznénk. Egyes lekérdezések az SQL Server és az Sqlite/Postgres között is eltérő eredményeket eredményeznének.
Például a lekérdezés:
var negatedNullableComparisonFilter = await context.Entities
.Where(x => !(x.NullableIntOne > x.NullableIntTwo))
.Select(x => new { x.NullableIntOne, x.NullableIntTwo }).ToListAsync();
a következő SQL-t hozza létre:
SELECT [e].[NullableIntOne], [e].[NullableIntTwo]
FROM [Entities] AS [e]
WHERE NOT ([e].[NullableIntOne] > [e].[NullableIntTwo])
amely kiszűri azokat az entitásokat, amelyek NullableIntOne
vagy NullableIntTwo
null értékre vannak beállítva.
Az EF9-ben a következőt állítjuk elő:
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)
Hasonló összehasonlítás egy vetületben:
var negatedNullableComparisonProjection = await context.Entities.Select(x => new
{
x.NullableIntOne,
x.NullableIntTwo,
Operation = !(x.NullableIntOne > x.NullableIntTwo)
}).ToListAsync();
a következő SQL-t eredményezte:
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]
amely az olyan entitások számára adja vissza a false
-t, amelyeknél a NullableIntOne
vagy NullableIntTwo
null értékre vannak állítva (a C#-ban elvárt true
helyett). Ugyanazt a forgatókönyvet futtatja a létrehozott Sqlite-on:
SELECT "e"."NullableIntOne", "e"."NullableIntTwo", NOT ("e"."NullableIntOne" > "e"."NullableIntTwo") AS "Operation"
FROM "Entities" AS "e"
amely Nullable object must have a value
kivételt eredményez, mivel a fordítás null
értéket eredményez olyan esetekben, amikor a NullableIntOne
vagy NullableIntTwo
null értékűek.
Az EF9 mostantól megfelelően kezeli ezeket a forgatókönyveket, és a LINQ-val összhangban álló eredményeket hoz létre az objektumokhoz és a különböző szolgáltatókhoz.
Ezt a fejlesztést @ranma42segítette. Nagyon köszönöm!
Order
és OrderDescending
LINQ operátorok fordítása
Az EF9 lehetővé teszi a LINQ egyszerűsített rendezési műveleteinek fordítását (Order
és OrderDescending
). Ezek a OrderBy
/OrderByDescending
hasonlóan működnek, de nem igényelnek argumentumot. Ehelyett az alapértelmezett sorrendet alkalmazzák – az entitások esetében ez azt jelenti, hogy az elsődleges kulcsértékek és más típusok alapján rendezik az értékeket.
Az alábbiakban egy példalekérdezés látható, amely az egyszerűsített rendezési operátorokat használja ki:
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();
Ez a lekérdezés egyenértékű a következővel:
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();
és a következő SQL-t hozza létre:
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]
Jegyzet
Order
és OrderDescending
metódusok csak entitások, összetett típusok vagy skalárok gyűjteményéhez támogatottak – nem működnek összetettebb vetületeken, például több tulajdonságot tartalmazó névtelen típusok gyűjteményén.
Ezt a fejlesztést az EF Team alumnus @bricelamsegítette. Nagyon köszönöm!
A logikai negation operátor (!) továbbfejlesztett fordítása
Az EF9 számos optimalizálást biztosít az SQL CASE/WHEN
, COALESCE
, a tagadás és különböző más szerkezetek körül; ezek közül a legtöbbet Andrea Canciani (@ranma42) járult hozzá - sok köszönet mindezekért! Az alábbiakban csak néhány ilyen optimalizálást ismertetünk a logikai tagadással kapcsolatban.
Vizsgáljuk meg a következő lekérdezést:
var negatedContainsSimplification = await context.Posts
.Where(p => !p.Content.Contains("Announcing"))
.Select(p => new { p.Content }).ToListAsync();
Az EF8-ban a következő SQL-t állítjuk elő:
SELECT "p"."Content"
FROM "Posts" AS "p"
WHERE NOT (instr("p"."Content", 'Announcing') > 0)
Az EF9-ben "leküldjük" NOT
műveletet az összehasonlításba:
SELECT "p"."Content"
FROM "Posts" AS "p"
WHERE instr("p"."Content", 'Announcing') <= 0
Egy másik, az SQL Serverre alkalmazható példa egy tagadott feltételes művelet.
var caseSimplification = await context.Blogs
.Select(b => !(b.Id > 5 ? false : true))
.ToListAsync();
Az EF8-ban beágyazott CASE
blokkokat eredményezett.
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]
Az EF9-ben eltávolítottuk a beágyazást:
SELECT CASE
WHEN [b].[Id] > 5 THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
FROM [Blogs] AS [b]
SQL Serveren egy tagadott bool tulajdonság kivetítésekor:
var negatedBoolProjection = await context.Posts.Select(x => new { x.Title, Active = !x.Archived }).ToListAsync();
Az EF8 CASE
blokkot hozna létre, mert az összehasonlítások nem jelennek meg közvetlenül az SQL Server-lekérdezésekben a kivetítésben:
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]
Az EF9-ben ez a fordítás egyszerűsödött, és most a bitenkénti NOT műveletet használja (~
):
SELECT [p].[Title], ~[p].[Archived] AS [Active]
FROM [Posts] AS [p]
Az Azure SQL és az Azure Synapse jobb támogatása
Az EF9 nagyobb rugalmasságot tesz lehetővé a megcélzott SQL Server típusának megadásakor. Ahelyett, hogy az EF-t UseSqlServer
konfigurálja, megadhatja UseAzureSql
vagy UseAzureSynapse
.
Így az EF jobb SQL-t hozhat létre az Azure SQL vagy az Azure Synapse használatakor. Az EF kihasználhatja az adatbázisspecifikus funkciókat (például dedikált JSON-típust az Azure SQL-), vagy megkerülheti annak korlátait (például ESCAPE
záradék nem érhető el az Azure SynapseLIKE
használatakor).
Egyéb lekérdezési fejlesztések
- Az EF8-ban bevezetett lekérdezési primitív gyűjtemények az összes
ICollection<T>
típusok támogatásához ki lettek terjesztve. Vegye figyelembe, hogy ez csak a paraméter- és beágyazott gyűjteményekre vonatkozik – az entitásokhoz tartozó primitív gyűjtemények még mindig csak tömbök, listák, és az EF9-ben , valamint csak olvasható tömbök/listákformájában érhetők el. - Új
ToHashSetAsync
függvények a lekérdezések eredményeinek visszaadásáhozHashSet
(#30033, @wertzuiközreműködésével). - A
TimeOnly.FromDateTime
és aFromTimeSpan
most már az SQL Serveren (#33678) fordítják le. -
ToString
már lefordítottuk a számokat (#33706, @Danevandy99). -
string.Join
most a CONCAT_WS jelenik meg nem összesített környezetben az SQL Serveren (#28899). -
EF.Functions.PatIndex
most az SQL ServerPATINDEX
függvényre fordítja, amely egy minta első előfordulásának kezdő pozícióját adja vissza (#33702, @smnsht). -
Sum
ésAverage
mostantól működnek a decimális számokkal az SQLite-ban (#33721, @ranma42közreműködésével). - Javítások és optimalizálások
string.StartsWith
ésEndsWith
(#31482). -
Convert.To*
metódusok mostantól elfogadhatják aobject
típusú argumentumot (#33891, @imangd). - Exclusive-Or (XOR) művelet most már lefordítva az SQL Serverre (#34071, @ranma42).
- Optimalizálások a
COLLATE
ésAT TIME ZONE
műveletek nullabilitásával kapcsolatban (#34263, hozzájáruló: @ranma42). - A
DISTINCT
optimalizálásaIN
,EXISTS
és halmazműveletekhez (#34381, @ranma42).
A fentiek csak néhányat jelentenek a fontosabb lekérdezési fejlesztések közül az EF9-ben; a teljesebb lista érdekében lásd a . kiadást.
Áttelepítések
Védelem az egyidejű migrálásokkal szemben
Az EF9 egy zárolási mechanizmust vezet be, amely védelmet nyújt az egyidejűleg végrehajtott több migrálási végrehajtás ellen, mivel az sérült állapotban hagyhatja az adatbázist. Ez nem fordul elő, ha a migrációk éles környezetben a javasolt módszerekhasználatával vannak üzembe helyezve, előfordulhat azonban, ha a migráció futásidőben történik a DbContext.Database.MigrateAsync()
módszer használatával. Javasoljuk, hogy az áttelepítéseket az üzembe helyezéskor alkalmazza az alkalmazás indítása helyett, de ez bonyolultabb alkalmazásarchitektúrákat eredményezhet (például a .NET Aspire-projektekhasználatakor
Jegyzet
Ha Sqlite-adatbázist használ, tekintse meg funkcióval kapcsolatos lehetséges problémákat.
Figyelmeztetés, ha több áttelepítési művelet nem futtatható tranzakción belül
A migrálás során végrehajtott műveletek többségét tranzakció védi. Ez biztosítja, hogy ha a migrálás valamilyen okból meghiúsul, az adatbázis nem sérült állapotba kerül. Egyes műveletek azonban nincsenek tranzakcióba burkolva (például sql server memóriaoptimalizált táblákonműveletek, vagy adatbázis-módosító műveletek, például az adatbázis-rendezés módosítása). A migrálási hiba esetén az adatbázis sérülésének elkerülése érdekében javasoljuk, hogy ezeket a műveleteket külön migrálással végezze el. Az EF9 mostantól észlel egy forgatókönyvet, amikor egy migrálás több műveletet tartalmaz, amelyek közül az egyik nem csomagolható be egy tranzakcióba, és figyelmeztetést ad ki.
Továbbfejlesztett adatbevetés
Az EF9 egy kényelmes módot vezetett be az adatbevetés végrehajtására, amely az adatbázis kezdeti adatokkal való feltöltését teszi lehetővé.
DbContextOptionsBuilder
mostantól UseSeeding
és UseAsyncSeeding
metódusokat tartalmaz, amelyek a DbContext inicializálásakor (a EnsureCreatedAsync
részeként) lesznek végrehajtva.
jegyzet
Ha az alkalmazás korábban futott, az adatbázis már tartalmazhatja a mintaadatokat (amelyeket a környezet első inicializálásakor adtak volna hozzá). Ezért UseSeeding
UseAsyncSeeding
ellenőriznie kell, hogy léteznek-e adatok az adatbázis feltöltésének megkísérlése előtt. Ez egy egyszerű EF-lekérdezés kiadásával érhető el.
Íme egy példa ezekre a módszerekre:
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);
}
});
További információt itt talál.
Egyéb migrálási fejlesztések
- Ha egy meglévő táblát sql serveres temporális táblává módosít, az áttelepítési kód mérete jelentősen csökkent.
Modellépítés
Automatikusan összeállított modellek
A lefordított modellek javíthatják a nagy modellekkel rendelkező alkalmazások indítási idejét – ez az entitástípusok száma a 100-es vagy az 1000-esekben. Az EF Core korábbi verzióiban manuálisan kellett létrehozni egy lefordított modellt a parancssor használatával. Például:
dotnet ef dbcontext optimize
A parancs futtatása után hozzá kell adni egy sort, például .UseModel(MyCompiledModels.BlogsContextModel.Instance)
a OnConfiguring
-hez, hogy jelezzük az EF Core számára a lefordított modell használatát.
Az EF9-től kezdődően erre a .UseModel
sorra már nincs szükség, ha az alkalmazás DbContext
típusa ugyanabban a projektben/szerelvényben van, mint a lefordított modell. Ehelyett a rendszer automatikusan észleli és használja a lefordított modellt. Ez megfigyelhető, valahányszor az EF naplózza a modell készítését. Egy egyszerű alkalmazás futtatása után az EF megjeleníti a modell összeállítását az alkalmazás indításakor:
Starting application...
>> EF is building the model...
Model loaded with 2 entity types.
A modellprojekten futó dotnet ef dbcontext optimize
kimenete a következő:
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>
Figyelje meg, hogy a naplókimenet azt jelzi, hogy a modell aparancs futtatásakor készült. Ha most újra futtatjuk az alkalmazást az újraépítés után, de kódmódosítás nélkül, akkor a kimenet a következő:
Starting application...
Model loaded with 2 entity types.
Figyelje meg, hogy a modell nem készült el az alkalmazás indításakor, mert a lefordított modellt a rendszer észlelte és automatikusan használta.
MSBuild-integráció
A fenti megközelítéssel a lefordított modellt továbbra is manuálisan kell létrehozni az entitástípusok vagy DbContext
konfiguráció módosításakor. Az EF9 azonban egy MSBuild feladatcsomaggal rendelkezik, amely automatikusan frissítheti a lefordított modellt a modellprojekt létrehozásakor! Első lépésként telepítse a Microsoft.EntityFrameworkCore.Tasks NuGet-csomagot. Például:
dotnet add package Microsoft.EntityFrameworkCore.Tasks --version 9.0.0
Borravaló
Használja a fenti parancs csomagverzióját, amely megfelel a használt EF Core verziójának.
Ezután engedélyezze az integrációt a .csproj
fájl EFOptimizeContext
és EFScaffoldModelStage
tulajdonságainak beállításával. Például:
<PropertyGroup>
<EFOptimizeContext>true</EFOptimizeContext>
<EFScaffoldModelStage>build</EFScaffoldModelStage>
</PropertyGroup>
Most, ha elkészítjük a projektet, láthatjuk a naplózást a fordítási folyamat során, amely azt jelzi, hogy a lefordított modell készül.
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
Az alkalmazás futtatása azt mutatja, hogy a lefordított modell észlelhető, ezért a modell nem lesz újra felépítve:
Starting application...
Model loaded with 2 entity types.
Most, amikor a modell megváltozik, a lefordított modell automatikusan újraépül, amint a projekt létrejön.
További információ: MSBuild integráció.
Csak olvasható primitív gyűjtemények
Borravaló
Az itt látható kód PrimitiveCollectionsSample.csszármazik.
Az EF8 bevezette a leképezési tömbök és a primitív típusúmutable-listák támogatását. Ez az EF9-ben kibővült írásvédett gyűjteményekkel/listákkal. Az EF9 támogatja a IReadOnlyList
, IReadOnlyCollection
vagy ReadOnlyCollection
típusú gyűjteményeket. Az alábbi kódban például a DaysVisited
konvenció szerint lesz leképezve, mint egy kezdetleges dátumgyűjtemény:
public class DogWalk
{
public int Id { get; set; }
public string Name { get; set; }
public ReadOnlyCollection<DateOnly> DaysVisited { get; set; }
}
Az írásvédett gyűjteményt igény szerint egy normál, mutable gyűjtemény is alátámaszthatja. Az alábbi kódban például a DaysVisited
leképezhetők kezdetleges dátumgyűjteményként, miközben továbbra is lehetővé teszik az osztály kódjainak a mögöttes lista manipulálását.
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;
}
Ezek a gyűjtemények ezután a szokásos módon használhatók a lekérdezésekben. Ez a LINQ-lekérdezés például:
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();
Ez a következő SQL-hez fordítható le az SQLite-en:
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"
Kulcsok és indexek kitöltési tényezőinek megadása
Borravaló
Az itt látható kód ModelBuildingSample.csszármazik.
Az EF9 támogatja az SQL Server kitöltőtényezős specifikációját, amikor kulcsokat és indexeket hoz létre az EF Core Migrations használatával. Az SQL Server-dokumentumokban a "Index létrehozásakor vagy újraépítésekor a kitöltési tényező értéke határozza meg, hogy az egyes levélszintű oldalakon hány százalékban legyenek kitöltve adatok, és a fennmaradó részeket az egyes oldalakon szabad helyként kell tárolni a jövőbeli növekedéshez."
A kitöltési tényező beállítható egyetlen vagy összetett elsődleges és alternatív kulcsra és indexre. Például:
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);
Ha meglévő táblákra alkalmazzák, ez módosítja a táblákat a kitöltési tényező kényszerének megfelelően.
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);
Ezt a fejlesztést @deano-huntersegítette. Nagyon köszönöm!
Meglévő modellépítési konvenciók bővíthetőbbé tétele
Borravaló
Az itt látható kód CustomConventionsSample.csszármazik.
Az alkalmazások nyilvános modellépítési konvencióit az EF7
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;
}
}
}
}
Az EF9-ben ez a következőképpen egyszerűsíthető le:
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;
}
}
Az ApplyConfigurationsFromAssembly frissítése nem nyilvános konstruktorok meghívásához
Az EF Core korábbi verzióiban a ApplyConfigurationsFromAssembly
metódus csak nyilvános, paraméter nélküli konstruktorokkal példányosított konfigurációtípusokat. Az EF9-ben mind a javítottuk a sikertelenesetén generált hibaüzeneteket, és engedélyeztük a nem nyilvános konstruktorok általi példányosítást is. Ez akkor hasznos, ha konfigurációt úgy helyez el egy privát beágyazott osztályban, hogy azt soha ne lehessen alkalmazáskóddal példányosítani. Például:
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);
}
}
}
Egyesek szerint ez a minta egy szörnyűség, mert összekapcsolja az entitástípust a konfigurációval. Mások úgy vélik, hogy ez nagyon hasznos, mert a konfigurációt az entitástípussal együtt helyezi el. Ezt itt ne vitatjuk meg. :-)
SQL Server HierarchyId
Borravaló
Az itt látható kód HierarchyIdSample.csszármazik.
Sugar a HierarchyId-elérési út létrehozásához
Az SQL Server HierarchyId
típusának első osztályú támogatása lett hozzáadva az EF8. Az EF9-ben hozzáadtunk egy cukormetódust, amely megkönnyíti az új gyermekcsomópontok létrehozását a faszerkezetben. Például a következő kód lekérdez egy HierarchyId
tulajdonsággal rendelkező meglévő entitást:
var daisy = await context.Halflings.SingleAsync(e => e.Name == "Daisy");
Ez a HierarchyId
tulajdonság ezután a gyermekcsomópontok explicit sztringmanipuláció nélkül történő létrehozására használható. Például:
var child1 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1), "Toast");
var child2 = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 2), "Wills");
Ha daisy
/4/1/3/1/
HierarchyId
-rel rendelkezik, akkor child1
megkapja a "/4/1/3/1/1/" HierarchyId
-t, és child2
megkapja a "/4/1/3/1/2/" HierarchyId
-t.
A két gyermek közötti csomópont létrehozásához további alszint használható. Például:
var child1b = new Halfling(HierarchyId.Parse(daisy.PathFromPatriarch, 1, 5), "Toast");
Ez létrehoz egy csomópontot HierarchyId
/4/1/3/1/1.5/
, amely child1
és child2
közé helyezi.
Ezt a fejlesztést @Rezakazemi890segítette. Nagyon köszönöm!
Szerszámozás
Kevesebb újraépítés
A dotnet ef
parancssori eszköz alapértelmezés szerint az eszköz végrehajtása előtt létrehozza a projektet. Ennek az az oka, hogy az eszköz futtatása előtt nem végzett újraépítés gyakran zavart okoz, amikor a dolgok nem működnek. A tapasztalt fejlesztők a --no-build
lehetőséggel elkerülhetik ezt a buildet, ami lassú is lehet. Azonban még a --no-build
lehetőség is okozhatja, hogy a projektet legközelebb újraépítik, amikor az EF eszközkészleten kívül történik az építés.
Úgy gondoljuk, hogy a