【问题标题】:Efficient way of updating list of entities更新实体列表的有效方法
【发布时间】:2012-07-10 09:43:15
【问题描述】:

我正在开发一个允许用户编辑实体列表的项目。我将这些实体映射到视图模型并使用编辑器字段显示它们。当用户按下提交按钮时,我会遍历每个模型并像这样更新它:

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 语句。有没有办法在循环结束后提交所有更新?

【问题讨论】:

标签: c# sql asp.net-mvc entity-framework entity


【解决方案1】:

我知道有两种方法可以在不先检索实体的情况下更新数据库中的实体:

//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() 不是先去数据库检索记录,然后再将更改与它合并吗?所以你最终还是要往返

没有。 We can test this

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 进行任何 DB 调用,我们将在 Start Attach Movie2End Attach Movie2 之间看到它们。我们还验证了以下说明的文档:

备注

附加用于使用已知数据库中已存在的实体重新填充上下文。

因此,SaveChanges 不会尝试将附加实体插入到数据库中,因为假定它已经存在。

附加movie2后,我们可以尝试从数据库中选择它。它不应该存在(因为 EF 只假定它存在)。

========= 开始添加:movie1 ==============

========= END 添加:movie1 ==============

========= 开始选择第一部电影 ==============

在 2020 年 1 月 15 日下午 5:29:23 +00:00 打开连接

选择顶部(1)

[c].[Id] AS [Id],

[c].[标题] AS [标题]

来自 [dbo].[电影] 作为 [c]

-- 执行于 1/15/2020 5:29:23 PM +00:00

-- 在 23 毫秒内完成,结果为:SqlDataReader

2020 年 1 月 15 日下午 5:29:23 +00:00 关闭连接

========= 结束选择第一部电影 ==============

========= 开始附加影片2 ==============

========= End Attach Movie2 ==============

========= 开始选择电影2 ==============

在 2020 年 1 月 15 日下午 5:29:23 +00:00 打开连接

选择顶部(1)

[Extent1].[Id] AS [Id],

[Extent1].[标题] AS [标题]

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 关闭连接

========= End SELECT Movie2 ==============

Movie2a.Id = null

所以在附加过程中没有调用 SQL,附加它没有错误消息,它不在数据库中。

【讨论】:

  • 我似乎找不到 Entry() 方法,它不是 DbSet 类的一部分。是否在扩展类中?
  • 我想强调一个事实,这是改变质量的最优雅的解决方案,我将保存更改调用移出方法,将其放入循环中以循环遍历对象。然后一旦循环并更改实体,就会调用保存更改。
  • .Attach() 不是先去数据库检索记录,然后再合并你的更改吗?所以你最终还是要往返
  • @IhorBodnarchuk 绝对不是。 .Attach()即实体以Unchanged状态放入上下文中,就好像它是从数据库中读取的一样。
【解决方案2】:

您可以尝试以下方法来最小化查询:

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 的列表很长,请注意that Contains can be potentially slow。但它只会运行一个查询。

【讨论】:

    【解决方案3】:

    HatSoft 已经提到过 EntityFramework.Extended。看看下面基于扩展框架的例子。

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

    【讨论】:

    • 厉害我去看看
    • 我认为 stackoverflow 反对链接答案,因为链接可能会死。
    【解决方案4】:

    我不确定实体框架的当前 beta 或 RC 版本是否支持批量更新之类的功能。但它们是 Nuget 上 EF 4.3.1 的扩展

    http://nuget.org/packages/EntityFramework.Extended

    希望这可以帮助您实现您的要求

    【讨论】:

    • 问题是没有在用户编辑的一篇文章中恢复多行。问题是如何有效地查询数据库来存储用户编辑的行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 1970-01-01
    • 1970-01-01
    • 2021-07-28
    • 1970-01-01
    • 1970-01-01
    • 2017-07-11
    相关资源
    最近更新 更多