[英]Efficient way of updating list of entities
我正在開發一個允許用戶編輯實體列表的項目。 我映射這些實體以查看模型並使用編輯器字段顯示它們。 當用戶按下提交按鈕時,我會瀏覽每個模型並像這樣更新它:
foreach (var viewModel in viewModels)
{
//Find the database model and set the value and update
var entity = unit.EntityRepository.GetByID(fieldModel.ID);
entity.Value = viewModel.Value;
unit.EntityRepository.Update(entity);
}
上面的代碼有效,但是正如您所看到的,我們需要為每個實體訪問數據庫兩次(一次用於檢索,另一次用於更新)。 使用實體框架有沒有更有效的方法來做到這一點? 我注意到每次更新都會生成一個單獨的 SQL 語句。 有沒有辦法在循環完成后提交所有更新?
這是我知道的更新數據庫中實體而不先檢索實體的兩種方法:
//Assuming person is detached from the context
//for both examples
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime BornOn { get; set; }
}
public void UpdatePerson(Person person)
{
this.Context.Persons.Attach(person)
DbEntityEntry<Person> entry = Context.Entry(person);
entry.State = System.Data.EntityState.Modified;
Context.SaveChanges();
}
應該產生:
Update [schema].[table]
Set Name = @p__linq__0, BornOn = @p__linq__1
Where id = @p__linq__2
或者,您可以根據需要指定字段(可能適用於具有大量列的表,或者出於安全目的,只允許更新特定列:
public void UpdatePersonNameOnly(Person person)
{
this.Context.Persons.Attach(person)
DbEntityEntry<Person> entry = Context.Entry(person);
entry.Property(e => e.Name).IsModified = true;
Context.SaveChanges();
}
應該產生:
Update [schema].[table]
Set Name = @p__linq__0
Where id = @p__linq__1
.Attach() 不是先去數據庫檢索記錄,然后將您的更改與它合並嗎? 所以無論如何你最終都會往返
不,我們可以測試這個
using System;
using System.Data.Entity;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
public class Program
{
public static void Main()
{
var movie1 = new Movie { Id = 1, Title = "Godzilla" };
var movie2 = new Movie { Id = 2, Title = "Iron Man" };
using (var context = new MovieDb())
{
/*
context.Database.Log = (s) => {
Console.WriteLine(s);
};
*/
Console.WriteLine("========= Start Add: movie1 ==============");
context.Movies.Add(movie1);
context.SaveChanges();
Console.WriteLine("========= END Add: movie1 ==============");
// LET EF CREATE ALL THE SCHEMAS AND STUFF THEN WE CAN TEST
context.Database.Log = (s) => {
Console.WriteLine(s);
};
Console.WriteLine("========= Start SELECT FIRST movie ==============");
var movie1a = context.Movies.First();
Console.WriteLine("========= End SELECT FIRST movie ==============");
Console.WriteLine("========= Start Attach Movie2 ==============");
context.Movies.Attach(movie2);
Console.WriteLine("========= End Attach Movie2 ==============");
Console.WriteLine("========= Start SELECT Movie2 ==============");
var movie2a = context.Movies.FirstOrDefault(m => m.Id == 2);
Console.WriteLine("========= End SELECT Movie2 ==============");
Console.Write("Movie2a.Id = ");
Console.WriteLine(movie2a == null ? "null" : movie2a.Id.ToString());
}
}
public class MovieDb : DbContext
{
public MovieDb() : base(FiddleHelper.GetConnectionStringSqlServer()) {}
public DbSet<Movie> Movies { get; set; }
}
public class Movie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Title { get; set; }
}
}
如果 attach 進行任何數據庫調用,我們將在Start Attach Movie2和End Attach Movie2之間看到它們。 我們還驗證了說明以下內容的文檔:
評論
附加用於使用已知已存在於數據庫中的實體重新填充上下文。
SaveChanges 因此不會嘗試將附加實體插入到數據庫中,因為它假定已經存在。
附加movie2后,我們可以嘗試從數據庫中選擇它。 它不應該在那里(因為 EF 只假設它在那里)。
======== 開始添加:movie1 ==============
========== 結束添加:movie1 ============
========== 開始選擇第一部電影 ==============
在 1/15/2020 5:29:23 PM +00:00 打開連接
選擇頂部 (1)
[c].[Id] AS [Id],
[c].[標題] AS [標題]
從 [dbo].[電影] AS [c]
-- 於 1/15/2020 5:29:23 PM +00:00 執行
-- 在 23 毫秒內完成,結果:SqlDataReader
2020 年 1 月 15 日下午 5:29:23 +00:00 關閉連接
========== 結束選擇第一部電影 ============
========== 開始附加電影 2 ==============
======== 結束附加電影2 ==============
========== 開始選擇電影2 ==============
在 1/15/2020 5:29:23 PM +00:00 打開連接
選擇頂部 (1)
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title]
FROM [dbo].[電影] AS [Extent1]
WHERE 2 = [Extent1].[Id]
-- 於 1/15/2020 5:29:23 PM +00:00 執行
-- 在 2 毫秒內完成,結果:SqlDataReader
2020 年 1 月 15 日下午 5:29:23 +00:00 關閉連接
========== 結束選擇電影2 ==============
Movie2a.Id = null
所以在附加過程中沒有調用 SQL,沒有附加它的錯誤消息,它不在數據庫中。
您可以嘗試以下操作以盡量減少查詢:
using (var ctx = new MyContext())
{
var entityDict = ctx.Entities
.Where(e => viewModels.Select(v => v.ID).Contains(e.ID))
.ToDictionary(e => e.ID); // one DB query
foreach (var viewModel in viewModels)
{
Entity entity;
if (entityDict.TryGetValue(viewModel.ID, out entity))
entity.Value = viewModel.Value;
}
ctx.SaveChanges(); //single transaction with multiple UPDATE statements
}
請注意,如果viewModels
列表很長, Contains
可能會很慢。 但它只會運行一個查詢。
我不確定 Entity Framework 的 beta 或 RC 中的當前版本是否支持批量更新之類的東西。 但它們是 Nuget 上 EF 4.3.1 的擴展
http://nuget.org/packages/EntityFramework.Extended
希望這可以幫助您實現您的要求
HatSoft 已經提到了 EntityFramework.Extended。 只需查看以下基於擴展框架的示例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.