簡體   English   中英

EntityFramework 核心 - 在不選擇實體的情況下更新數據集合

[英]EntityFramework core - Update a collection of data without selecting the entities

如果沒有 select,你會如何更新? upsert 將是由包含 DTO 的方法接收的實體集合,這些 DTO 在數據庫中可能不可用,因此您不能使用附加范圍。 One way theoretically is to load the ExistingData partially with a select like dbContext.People.Where(x => x exists in requested collection).Select(x => new Person { Id = x.Id, State = x.State }).ToList()只加載實體的一部分而不是重的部分。 但是在這里,如果您從該集合中更新其中一個返回的 entityItems,它將不會更新,因為new Person沒有跟蹤它,而且您也不能說dbContext.Entry<Person>(person).State = Modified ,因為它會引發錯誤並且會告訴你 ef core 已經在“跟蹤”它了。 那么該怎么辦。 一種方法是將它們全部從ChangeTracker中分離出來,然后進行 state 更改,它會進行更新,但不僅僅是在一個字段上,即使你說dbContext.Entry<Person>(person).Property(x => x.State).Modified = true 它會將您尚未從數據庫中讀取的所有字段覆蓋為其默認值,並且會在數據庫中造成混亂。 另一種方法是讀取ChangeTracker條目並更新它們,但它也會覆蓋並且它會認為一切都已更改。 所以技術上我不知道 ef core 如何創建以下 SQL,

update People set state = 'Approved' where state != 'Approved'

不更新任何東西。 或先完全加載人。

不加載數據的原因是您可能想要更新14000條記錄,而這些記錄加載起來非常繁重,因為它們包含 byte[] 並存儲了圖像。

順便說一句,與 Laravel 相比,缺乏關於 EFCore 的友好文檔是一場災難。 最近,我們損失了大量數據。

順便說一句,像下面的代碼這樣的示例對我們不起作用,因為它們正在更新一個他們知道它存在於數據庫中的字段。 但是我們正在嘗試更新一個集合,其中一些 DTO 在數據庫中可能不可用。

try
{
    using (var db = new dbContext())
    {
        // Create new stub with correct id and attach to context.
        var entity = new myEntity { PageID = pageid };
        db.Pages.Attach(entity);

        // Now the entity is being tracked by EF, update required properties.
        entity.Title = "new title";
        entity.Url = "new-url";
        
        // EF knows only to update the propeties specified above.
        db.SaveChanges();
    }
}
catch (DataException)
{
    // process exception
}

編輯:使用的ef core版本是@3.1.9

太棒了,我找到了解決方案(您還需要注意您的單元測試)。 Entityframework 實際上運行良好,可能只是缺乏經驗,我在這里記錄一下,以防其他人遇到同樣的問題。

考慮到我們有一個 Person 的實體,它上面有一個保存為 Blob 的個人資料圖片,這會導致如果您對 20k 人執行以下操作,即使您嘗試在您的數據庫上有足夠的正確索引,查詢也會變慢桌子。 您希望執行此查詢以根據請求更新這些實體。

var entityIdsToUpdate = request.PeopleDtos.Select(p => p.Id);
var people = dbContext.People.Where(x => entityIdsToUpdate.Contains(x.Id)).ToList();

這很好,而且效果很好,您將獲得People集合,然后您可以根據給定的數據更新它們。 在這些類型的更新中,即使您更新圖像,您通常也不需要更新圖像,那么您需要增加客戶端上的 `TimeOut1 屬性,但對於我們的示例,我們不需要更新圖像。 所以上面的代碼會變成這個。

var entityIdsToUpdate = request.PeopleDtos.Select(p => p.Id);
var people = dbContext.People
.Select(p => new Person { 
    Id = p.Id,
    Firstname = p.Firstname,
    Lastname = p.Lastname,
    //But no images to load
 })
 .Where(p => entityIdsToUpdate.Contains(p.Id)).ToList();

但是使用這種方法, EntityFramework將失去對實體的跟蹤。 所以你需要像這樣附加它,我會告訴你如何附加它。

這是收藏的正確方法

dbContext.People.AttachRange(people); //These are the people you've already queried

現在要這樣做,你可能想要這樣做,因為你從EntityFramework的第一個錯誤中得到一個錯誤,它說實體已經被跟蹤,相信它,因為它已經被跟蹤了。 我會在代碼之后解釋。

//Do not do this
foreach(var entry in dbContext.ChangeTracker.Entries()) 
{
    entry.State = EntityState.Detached;
}
//and then on updating a record you may write the following to attach it back
dbContext.Entry(Person).State = EntityState.Modified;

上面的代碼將導致EntityFramework不再跟隨實體上的更改,最后一行你會告訴它所有已編輯或未編輯的內容都已更改,並將導致你丟失未編輯的屬性,如“圖像”。

注意:現在你可以做什么錯誤,甚至弄亂正確的方法。 好吧,既然您沒有加載整個實體,您可能會認為將值分配給卸載的實體仍然可以,即使該值與數據庫中的值沒有不同。 這會導致實體框架假定某些內容已更改,並且如果您在記錄上設置ModifiedOn ,它將無緣無故地更改它。

現在關於測試:當您測試時,您可能會從數據庫中獲取一些內容並從中創建一個 dto,並將具有相同dbContext的 dto 傳遞給您的SystemUnderTest ,attach 方法將在此處拋出一個錯誤,表示該實體已經被跟蹤,因為在你的測試方法中調用。 最好的方法是為每個進程創建一個新的 dbContext 並在完成后將它們處理掉。

順便說一句,在測試中,您可能會使用相同的 dbContext 更新實體,並且在測試之后您想要從數據庫中獲取。 請注意,返回給您的是EntityFramework的“緩存”,如果您首先獲取它並不完全像Select(x => )那樣,那么您將獲得一些字段為 null 或默認值價值。 在這種情況下,您應該執行DbContext.Entry(YOUR_ENTRY).Reload()

這是一個非常完整的答案,它可能與問題沒有直接關系,但是如果您沒有注意到上面提到的所有事情,它們可能會導致災難。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM