0

データベースに更新または追加する必要があるさまざまなオブジェクトを Web から取得するように、Web アプリケーションを作成しています。さらに、所有者が変更されていないことを確認する必要があります。ハッカーがアカウントを取得し、更新を送信して外部キーをユーザー モデルに変更する可能性があるためです。これらすべてのメソッドを手動でコーディングする必要はなく、単純な汎用呼び出しを行いたいと考えています。

たぶん、これと同じくらい簡単なもの

ctx.OrderLines.AddOrUpdateSet(order.OrderLines, a => a.Order)

Order への外部キーを持つ古い永続化されたレコードと、新しい着信レコードに基づいています。

  1. 新しいレコード リストにない古いレコードを削除します。

  2. 古いレコード リストにない新しいレコードを追加します。

  3. 両方のリストに存在する新しいレコードを更新します。

    ctx.Entry(orderLine).State=EntityState.Deleted;
               ...
    ctx.Entry(orderLine).State=EntityState.Added;
               ...
    ctx.Entry(orderLine).State=EntityState.Modified;
    

所有権が変更されていないことを確認するために古いレコードが読み込まれると、これは少し複雑になります。しないとエラーになります。

   oldorder.OrderLines.remove(oldOrderLine); //for deletes
   oldorder.OrderLines.add(oldOrderLine); //for adds
   ctx.Entry(header).CurrentValues.SetValues(header); //for modifications

Entity Framework 5 には、AddOrUpdate という新しい拡張機能があります。また、このメソッドが追加される前に、このメソッドの作成方法に関する非常に興味深い (ぜひお読みください)ブログ エントリがありました。

これが StackOverflow で質問するには多すぎるかどうかはわかりませんが、問題へのアプローチ方法についての手がかりがあれば十分かもしれません。これまでの私の考えは次のとおりです。

a) 一部の機能には AddOrUpdate を利用します。

b) コンテキストへの順序のロードを回避し、余分な呼び出しを回避することを期待して、セカンダリ コンテキストを作成します。

c) 保存されたすべてのオブジェクトの状態を初期削除済みに設定します。

4

3 に答える 3

2

しばらくこれに取り組んだ後、GraphDiff と呼ばれるオープンソース プロジェクトを見つけました。これはブログ エントリであり、「最初にエンティティ フレームワーク コードに graphdiff を導入する – 切り離されたエンティティのグラフの自動更新を可能にする」です。使い始めたばかりですが、見た目が印象的です。また、多対 1 の関係で update/delete/insert を発行するという問題も解決します。実際には問題をグラフに一般化し、任意のネストを許可します。

于 2013-07-17T18:59:53.070 に答える
2

あなたは私自身の質問からこの質問にリンクしているので、Entity Frameworkで新たに獲得した経験を私のために投入すると思いました。

Entity Framework を使用して汎用リポジトリで共通の保存メソッドを実現するには、次のようにします。(Unit of Work パターンも実装しているため、Context は私のリポジトリのメンバーであることに注意してください)

public class EFRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
    internal readonly AwesomeContext Context;
    internal readonly DbSet<TEntity> DbSet;

    public EFRepository(AwesomeContext context)
    {
        if (context == null) throw new ArgumentNullException("context");

        Context = context;
        DbSet = context.Set<TEntity>();
    }

    // Rest of implementation removed for brevity 

    public void Save(TEntity entity)
    {
        var entry = Context.Entry(entity);
        if (entry.State == EntityState.Detached)
            DbSet.Add(entity);
        else entry.State = EntityState.Modified;
    }
}

正直なところ、状態条件を変更し続けただけなので、なぜこれが機能するのかはわかりませんが、機能することを証明するための単体 (統合) テストがあります。うまくいけば、私よりもEFに詳しい人がこれに光を当てることができます。

「カスケード更新」に関しては、ユニットオブワークパターンを使用して機能するかのように興味がありました(リンクした私の質問は、それが存在することを知らなかったときであり、リポジトリは基本的に必要なときにいつでも作業ユニットを作成します保存/取得/削除するのは悪いことです)、そのため、単純なリレーショナル DB にテスト ケースを投入しました。ここにアイデアを与える図があります。

モデル関係の図

重要テスト ケース 2 が機能するためには、EF が遅延読み込みを提供できるように、POCO 参照プロパティを仮想化する必要があります。

EFRepository<TEntity>リポジトリの実装は、上記のようにジェネリックから派生しただけなので、その実装は省略します。

これらは私のテストケースで、どちらも合格です。

public class EFResourceGroupFacts
{
    [Fact]
    public void Saving_new_resource_will_cascade_properly()
    {
        // Recreate a fresh database and add some dummy data.
        SetupTestCase();

        using (var ctx = new LocalizationContext("Localization.CascadeTest"))
        {
            var cultureRepo = new EFCultureRepository(ctx);
            var resourceRepo = new EFResourceRepository(cultureRepo, ctx);

            var existingCulture = cultureRepo.Get(1); // First and only culture.
            var groupToAdd = new ResourceGroup("Added Group");
            var resourceToAdd = new Resource(existingCulture,"New Resource", "Resource to add to existing group.",groupToAdd);

            // Verify we got a single resource group.
            Assert.Equal(1,ctx.ResourceGroups.Count());

            // Saving the resource should also add the group.
            resourceRepo.Save(resourceToAdd);
            ctx.SaveChanges();

            // Verify the group was added without explicitly saving it.
            Assert.Equal(2, ctx.ResourceGroups.Count());
        }

        // try creating a new Unit of Work to really verify it has been persisted..
        using (var ctx = new LocalizationContext("Localization.CascadeTest"))
        {
            Assert.DoesNotThrow(() => ctx.ResourceGroups.First(rg => rg.Name == "Added Group"));
        }
    }

    [Fact]
    public void Changing_existing_resources_group_saves_properly()
    {
        SetupTestCase();

        using (var ctx = new LocalizationContext("Localization.CascadeTest"))
        {
            ctx.Configuration.LazyLoadingEnabled = true;
            var cultureRepo = new EFCultureRepository(ctx);
            var resourceRepo = new EFResourceRepository(cultureRepo, ctx);



            // This resource already has a group.
            var existingResource = resourceRepo.Get(2);
            Assert.NotNull(existingResource.ResourceGroup); // IMPORTANT: Property must be virtual!
            // Verify there is only one resource group in the datastore.
            Assert.Equal(1,ctx.ResourceGroups.Count());

            existingResource.ResourceGroup = new ResourceGroup("I am implicitly added to the database. How cool is that?");

            // Make sure there are 2 resources in the datastore before saving.
            Assert.Equal(2, ctx.Resources.Count());

            resourceRepo.Save(existingResource);
            ctx.SaveChanges();

            // Make sure there are STILL only 2 resources in the datastore AFTER saving.
            Assert.Equal(2, ctx.Resources.Count());

            // Make sure the new group was added.
            Assert.Equal(2,ctx.ResourceGroups.Count());

            // Refetch from store, verify relationship.
            existingResource = resourceRepo.Get(2);
            Assert.Equal(2,existingResource.ResourceGroup.Id);

             // let's change the group to an existing group
            existingResource.ResourceGroup = ctx.ResourceGroups.First();
            resourceRepo.Save(existingResource);
            ctx.SaveChanges();

            // Assert no change in groups.
            Assert.Equal(2, ctx.ResourceGroups.Count());

            // Refetch from store, verify relationship.
            existingResource = resourceRepo.Get(2);
            Assert.Equal(1, existingResource.ResourceGroup.Id);
        }
    }

    private void SetupTestCase()
    {
        // Delete everything first. Database.SetInitializer does not work very well for me.
        using (var ctx = new LocalizationContext("Localization.CascadeTest"))
        {
            ctx.Database.Delete();
            ctx.Database.Create();

            var culture = new Culture("en-US", "English");
            var resourceGroup = new ResourceGroup("Existing Group");
            var resource = new Resource(culture, "Existing Resource 1",
                                        "This resource will already exist when starting the test. Initially it has no group.");
            var resourceWithGroup = new Resource(culture, "Exising Resource 2",
                                                 "Same for this resource, except it has a group.",resourceGroup);

            ctx.Cultures.Add(culture);
            ctx.ResourceGroups.Add(resourceGroup);
            ctx.Resources.Add(resource);
            ctx.Resources.Add(resourceWithGroup);

            ctx.SaveChanges();
        }
    }
}

うまくいくかどうかわからなかったので、これを学ぶのは面白かったです。

于 2013-07-12T09:07:32.330 に答える