31

私は、ユーザーがエンティティのリストを編集できるようにするプロジェクトに取り組んでいます。これらのエンティティをマップしてモデルを表示し、エディターフィールドで表示します。ユーザーが送信ボタンを押すと、各モデルを調べて、次のように更新します。

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);
}

上記のコードは機能しますが、ご覧のとおり、エンティティごとにデータベースを2回ヒットする必要があります(1回は取得し、もう1回は更新します)。Entity Frameworkを使用してこれを行うためのより効率的な方法はありますか?更新のたびに個別のSQLステートメントが生成されることに気づきました。ループが終了した後にすべての更新をコミットする方法はありますか?

4

4 に答える 4

26

最初にエンティティの取得を行わずにデータベース内のエンティティを更新するために私が知っている2つの方法を次に示します。

//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がDB呼び出しを行うと、StartAttachMovie2EndAttachMovie2の間にそれらが表示されます。また、次のように記載されているドキュメントを確認します。

備考

アタッチは、データベースにすでに存在することがわかっているエンティティをコンテキストに再設定するために使用されます。

したがって、SaveChangesは、アタッチされたエンティティがすでに存在していると想定されるため、データベースにアタッチされたエンティティを挿入しようとはしません。

movie2を添付した後、DBから選択を試みることができます。そこにあるべきではありません(EFはそこにあると想定しているだけだからです)。

=========追加を開始:movie1 ==============

========= END追加:movie1 ==============

=========SELECTFIRSTムービーを開始==============

2020年1月15日午後5時29分23秒+00:00に接続を開きました

トップを選択(1)

[c]。[Id]AS[Id]、

[c]。[タイトル]AS[タイトル]

FROM[dbo]。[Movies]AS[c]

-2020年1月15日午後5時29分23秒+00:00に実行

--23ミリ秒で完了し、結果はSqlDataReaderです。

2020年1月15日午後5時29分23秒+00:00に接続を閉じました

=========SELECTFIRSTムービーを終了==============

=========AttachMovie2の開始==============

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

=========SELECTMovie2を開始==============

2020年1月15日午後5時29分23秒+00:00に接続を開きました

トップを選択(1)

[Extent1]。[Id]AS[Id]、

[Extent1]。[タイトル]AS[タイトル]

FROM[dbo]。[Movies]AS[Extent1]

WHERE 2=[Extent1]。[Id]

-2020年1月15日午後5時29分23秒+00:00に実行

--2ミリ秒で完了し、結果は:SqlDataReader

2020年1月15日午後5時29分23秒+00:00に接続を閉じました

=========SELECTMovie2を終了==============

Movie2a.Id = null

したがって、アタッチ中にSQLが呼び出されず、アタッチ中にエラーメッセージが表示されず、データベースにありません。

于 2012-07-10T20:50:11.183 に答える
8

次のことを試して、クエリを最小限に抑えることができます。

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
}

のリストが非常に長い場合、速度が低下する可能性があることに注意してくださいContainsviewModelsただし、実行されるクエリは1つだけです。

于 2012-07-10T20:51:19.497 に答える
0

HatSoftはすでにEntityFramework.Extendedについて言及しています。拡張フレームワークに基づく次の例を見てください。

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

于 2012-07-10T20:52:08.070 に答える
-1

EntityFrameworkのベータ版またはRCの現在のバージョンがバッチ更新のようなものをサポートしているかどうかはわかりません。ただし、これらはNugetのEF4.3.1の拡張機能です。

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

これがあなたの要件を達成するのに役立つかもしれないことを願っています

于 2012-07-10T20:33:11.790 に答える