Megosztás:


ExecuteUpdate és ExecuteDelete

ExecuteUpdate és az EF hagyományos változáskövetési ExecuteDelete és SaveChanges() -módszere nélkül is menthetők az adatok az adatbázisba. A két módszer bevezető összehasonlítása az adatok mentésének Áttekintés lapján található.

Törlés végrehajtása

Tegyük fel, hogy törölnie kell az összes olyan blogot, amely egy bizonyos küszöbérték alatti minősítéssel rendelkezik. A hagyományos SaveChanges() megközelítéshez a következőket kell tennie:

await foreach (var blog in context.Blogs.Where(b => b.Rating < 3).AsAsyncEnumerable())
{
    context.Blogs.Remove(blog);
}

await context.SaveChangesAsync();

Ez meglehetősen nem hatékony módszer a feladat végrehajtására: lekérdezzük az adatbázist minden olyan bloghoz, amely megfelel a szűrőnknek, majd lekérdezzük, materializáljuk és nyomon követjük az összes ilyen példányt; az egyező entitások száma hatalmas lehet. Ezután jelezzük az EF változáskövetőnek, hogy minden egyes blogot el kell távolítani, és a változtatásokat a SaveChanges() hívással alkalmazzuk, amely mindegyikhez egy DELETE utasítást generál.

Az API-val ExecuteDelete végzett feladat a következő:

await context.Blogs.Where(b => b.Rating < 3).ExecuteDeleteAsync();

Ez a már jól ismert LINQ-operátorokkal határozza meg, hogy mely blogokat kell érinteni - ugyanúgy, mintha lekérdeznénk őket -, majd arra utasítja az EF-t, hogy hajtsa végre az SQL-t DELETE az adatbázison:

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Amellett, hogy egyszerűbb és rövidebb, ez nagyon hatékonyan fut az adatbázisban anélkül, hogy bármilyen adatot betöltenek az adatbázisból, vagy az EF változáskövetőjét is érintenék. Vegye figyelembe, hogy tetszőleges LINQ-operátorokkal kiválaszthatja, hogy mely blogokat szeretné törölni – ezeket a rendszer az SQL-nek fordítja le végrehajtás céljából az adatbázisban, mintha lekérdezné ezeket a blogokat.

FrissítésVégrehajtása

Ahelyett, hogy törölnénk ezeket a blogokat, mi lenne, ha módosítani szeretnénk egy tulajdonságot, hogy jelezzük, hogy inkább rejtve kellene lenniük? ExecuteUpdate hasonló módon fejezi ki az SQL-utasítást UPDATE :

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(setters => setters.SetProperty(b => b.IsVisible, false));

A ExecuteDelete-hoz hasonlóan először a LINQ segítségével meghatározzuk, hogy mely blogokat kell érinteni; de a ExecuteUpdate esetén a megfelelő blogokhoz alkalmazandó módosítást is ki kell fejeznünk. Ez SetProperty hívással úgy történik, hogy meghívja a ExecuteUpdate-t, és két argumentumot ad meg: a módosítani kívánt tulajdonságot (IsVisible) és az új értéket (false). Ez a következő SQL-t hajtja végre:

UPDATE [b]
SET [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Több tulajdonság frissítése

ExecuteUpdate lehetővé teszi több tulajdonság frissítését egyetlen meghívásban. Például, ha azt szeretné, hogy a IsVisible hamisra és a Rating nullára legyen állítva, egyszerűen láncolja össze a további SetProperty hívásokat:

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(b => b.IsVisible, false)
        .SetProperty(b => b.Rating, 0));

Ez a következő SQL-t hajtja végre:

UPDATE [b]
SET [b].[Rating] = 0,
    [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Hivatkozás a meglévő tulajdonságértékre

A fenti példák egy új állandó értékre frissítették a tulajdonságot. ExecuteUpdate lehetővé teszi a meglévő tulajdonságérték hivatkozását az új érték kiszámításakor; Ha például az összes megfelelő blog minősítését egyenként szeretné növelni, használja a következőt:

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));

Vegye figyelembe, hogy a második argumentum a SetProperty most lambda függvény, és nem állandó, mint korábban volt. A b paraméter a frissített blogot jelöli, ezen belül a lambdában, b.Rating így a módosítás előtt tartalmazza a minősítést. Ez a következő SQL-t hajtja végre:

UPDATE [b]
SET [b].[Rating] = [b].[Rating] + 1
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

ExecuteUpdate jelenleg nem támogatja a lambdán SetProperty belüli navigációkra való hivatkozásokat. Tegyük fel például, hogy frissíteni szeretnénk a blogok összes minősítését, hogy minden blog új minősítése az összes bejegyzés minősítésének átlaga legyen. Megpróbálhatjuk használni a ExecuteUpdate-t az alábbi módon:

await context.Blogs.ExecuteUpdateAsync(
    setters => setters.SetProperty(b => b.Rating, b => b.Posts.Average(p => p.Rating)));

Az EF azonban lehetővé teszi ennek a műveletnek a végrehajtását azáltal, hogy először Select segítségével kiszámítja az átlagos minősítést, projektálja egy névtelen típusra, majd ezt alkalmazza ExecuteUpdate.

await context.Blogs
    .Select(b => new { Blog = b, NewRating = b.Posts.Average(p => p.Rating) })
    .ExecuteUpdateAsync(setters => setters.SetProperty(b => b.Blog.Rating, b => b.NewRating));

Ez a következő SQL-t hajtja végre:

UPDATE [b]
SET [b].[Rating] = CAST((
    SELECT AVG(CAST([p].[Rating] AS float))
    FROM [Post] AS [p]
    WHERE [b].[Id] = [p].[BlogId]) AS int)
FROM [Blogs] AS [b]

Változások követése

Az ismerős SaveChanges felhasználók több módosítást is végrehajtanak, majd meghívják SaveChanges , hogy alkalmazzák ezeket a módosításokat az adatbázisra. Ezt az EF változáskövetője teszi lehetővé, amely összegyűjti - vagy nyomon követi - ezeket a módosításokat.

ExecuteUpdate és ExecuteDelete egészen másképp működnek: azonnal érvénybe lépnek, a meghívásuk időpontjában. Ez azt jelenti, hogy bár egyetlen ExecuteUpdate vagy ExecuteDelete művelet több sort is érinthet, nem lehet egyszerre több ilyen műveletet halmozni, például híváskor SaveChanges. Valójában a függvények egyáltalán nem ismerik az EF változáskövetőjét, és semmilyen módon nem működnek együtt vele. Ennek számos fontos következménye van.

Vegye figyelembe a következő kódot:

// 1. Query the blog with the name `SomeBlog`. Since EF queries are tracking by default, the Blog is now tracked by EF's change tracker.
var blog = await context.Blogs.SingleAsync(b => b.Name == "SomeBlog");

// 2. Increase the rating of all blogs in the database by one. This executes immediately.
await context.Blogs.ExecuteUpdateAsync(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));

// 3. Increase the rating of `SomeBlog` by two. This modifies the .NET `Rating` property and is not yet persisted to the database.
blog.Rating += 2;

// 4. Persist tracked changes to the database.
await context.SaveChangesAsync();

Döntő fontosságú, hogy amikor ExecuteUpdate meghívják, és az összes blog frissül az adatbázisban, az EF változáskövetője nem frissül, és a nyomon követett .NET-példány továbbra is az eredeti minősítési értékkel rendelkezik, attól a ponttól kezdve, amikor lekérdezték. Tegyük fel, hogy a blog értékelése eredetileg 5 volt; a harmadik sor végrehajtása után az adatbázisban a minősítés most 6 (a ExecuteUpdate), míg a nyomon követett .NET-példányban a minősítés 7. A meghíváskor SaveChanges az EF észleli, hogy az új 7 érték eltér az eredeti 5 értéktől, és megőrzi a változást. A(z) ExecuteUpdate által elvégzett módosítás felülíródik, és nem kerül figyelembe vételre.

Ennek eredményeképpen általában jó ötlet elkerülni a nyomon követett SaveChanges módosítások és a nyomon nem követett módosítások keverését.ExecuteUpdate/ExecuteDelete

Tranzakciók

A fentiek folytatásaként fontos tudni, hogy amikor a ExecuteUpdate és ExecuteDelete meghívásra kerülnek, nem indul el implicit tranzakció. Vegye figyelembe a következő kódot:

await context.Blogs.ExecuteUpdateAsync(/* some update */);
await context.Blogs.ExecuteUpdateAsync(/* another update */);

var blog = await context.Blogs.SingleAsync(b => b.Name == "SomeBlog");
blog.Rating += 2;
await context.SaveChangesAsync();

Minden ExecuteUpdate hívás egyetlen SQL-t UPDATE küld az adatbázisba. Mivel nem jön létre tranzakció, ha valamilyen hiba megakadályozza a második ExecuteUpdate sikeres végrehajtását, az első hatása továbbra is megmarad az adatbázisban. Valójában a fenti négy művelet – két meghívás ExecuteUpdate, egy lekérdezés és SaveChanges – mindegyik saját tranzakcióján belül hajtódik végre. Ha több műveletet szeretne egyetlen tranzakcióba burkolni, explicit módon indítson el egy tranzakciót a következővel DatabaseFacade:

using (var transaction = context.Database.BeginTransaction())
{
    context.Blogs.ExecuteUpdate(/* some update */);
    context.Blogs.ExecuteUpdate(/* another update */);

    ...
}

A tranzakciókezeléssel kapcsolatos további információkért lásd a Tranzakciók használata című témakört.

Egyidejűség-vezérlés és érintett adatsorok

SaveChanges Automatikus egyidejűségi vezérlést biztosít egy egyidejűségi token használatával, amely garantálja, hogy a sor ne változzon meg a betöltés és a módosítások mentése között. Mivel ExecuteUpdate és ExecuteDelete nem használják a változáskövetőt, nem alkalmazhatják automatikusan az egyidejűség-vezérlést.

Mindkét módszer azonban a művelet által érintett sorok számát adja vissza; ez különösen hasznos lehet az egyidejűség-vezérlés implementálásához:

// (load the ID and concurrency token for a Blog in the database)

var numUpdated = await context.Blogs
    .Where(b => b.Id == id && b.ConcurrencyToken == concurrencyToken)
    .ExecuteUpdateAsync(/* ... */);
if (numUpdated == 0)
{
    throw new Exception("Update failed!");
}

Ebben a kódban linq Where operátort használunk egy adott blog frissítésének alkalmazására, és csak akkor, ha az egyidejűségi jogkivonatnak van egy adott értéke (például az, amelyet a blog adatbázisból való lekérdezésekor láttunk). Ezután ellenőrizzük, hogy hány sor lett ténylegesen frissítve ExecuteUpdate; ha az eredmény nulla, nem frissültek sorok, és az egyidejűségi jogkivonat valószínűleg egy egyidejű frissítés eredményeként módosult.

Korlátozások

  • Jelenleg csak a frissítés és a törlés támogatott; a beszúrást a következőn keresztül DbSet<TEntity>.AddSaveChanges()kell elvégezni:
  • Bár az SQL UPDATE és DELETE utasítások lehetővé teszik az érintett sorok eredeti oszlopértékeinek lekérését, ezt jelenleg nem támogatja ExecuteUpdate és ExecuteDelete.
  • Ezen metódusok több meghívása nem kötegelhető. Minden egyes meghívás saját fordulót hajt végre, az adatbázisba.
  • Az adatbázisok általában csak egyetlen táblát engedélyeznek az UPDATE vagy a DELETE használatával.
  • Ezek a módszerek jelenleg csak relációsadatbázis-szolgáltatókkal működnek.

További erőforrások