簡體   English   中英

更新實體列表的有效方法

[英]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 Movie2End 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。 只需查看以下基於擴展框架的示例。

http://weblogs.asp.net/pwelter34/archive/2011/11/29/entity-framework-batch-update-and-future-queries.aspx

暫無
暫無

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

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