1

EFとDDDを使用して単純なアプリケーションをセットアップするために数日を費やした後、私は非常にイライラしていると言わなければなりません。Linq-to-SQLを使用したほうがいいと思い、DDDとEFのことをすべて忘れてしまいます。

EF付き

a)適切な読み取り専用コレクションを作成することはできません

b)子アイテムのコレクションから何かを削除すると、1つ以上の外部キープロパティがnull不可のメッセージであるため、関係を変更できなかったことがよくあります。

c)親のすべての子アイテムを削除して再挿入する簡単な方法はありません。

私が見つけた回避策がかなり厄介に見えることを考えると、これらはすべて私にとってほとんどショーストッパーです。誰かがこれらの問題に対処する簡単なリポジトリをまとめることができましたか?

はいの場合、コードを共有していただけませんか?!?

また、これが大きなトピックであることを私は知っていますが、大規模なWebアプリケーションで実際のDDDの利点を実際に体験した人はいますか?私たちは皆理論を知っていますが、それが実際に面倒な価値があるかどうかについてのアイデアを持っているといいでしょう!


さて、これまでのところ、あらゆる種類の厄介な回避策を実行することなく実行できる最善の方法は、何かをクエリするときにAsNoTracking()を使用することです。そうすれば、私は自分の情報を入手し、EFは私の背後で何もしなくても一人で去ります。コレクションから削除できるようになり、削除もできるようになりました(idはこれからsqlに戻らなければならないと誰が思うでしょう!)AsNoTrackingを使用する際の落とし穴を知っている人はいますか?オブジェクトに基づいてSQLを生成し、それらにデータを入力したり、更新/削除したりできる限り、問題はありません。とにかく追跡全体が行き過ぎですか?


namespace EShop.Models.Repositories
{
public class CustomerRepository : BaseRepository, IRepository<Customer, Int32>
{
    public CustomerRepository() : base(new EShopData()) { }

    #region CoreMethods

    public void InsertOrUpdate(Customer customer)
    {
        if (customer.CustomerId > 0)
        {
            // you cannot use remove, if you do you ll attach and then you ll have issues with the address/cards below
            // dbContext.Entry<CustomerAddress>(address).State = EntityState.Added; will fail
            dbContext.Database.ExecuteSqlCommand("DELETE FROM CustomerAddress WHERE CustomerId = @CustomerId", new SqlParameter("CustomerId", customer.CustomerId));
            dbContext.Database.ExecuteSqlCommand("DELETE FROM CreditCard WHERE CustomerId = @CustomerId", new SqlParameter("CustomerId", customer.CustomerId));

            foreach (var address in customer.Addresses)
                dbContext.Entry<CustomerAddress>(address).State = EntityState.Added;
            foreach (var card in customer.CreditCards)
                dbContext.Entry<CreditCard>(card).State = EntityState.Added;

            dbContext.Entry<Customer>(customer).State = EntityState.Modified;
        }
        else
        {
            dbContext.Entry<Customer>(customer).State = EntityState.Added;
            foreach (var card in customer.CreditCards)
                dbContext.Entry<CreditCard>(card).State = EntityState.Added;
            foreach (var address in customer.Addresses)
                dbContext.Entry<CustomerAddress>(address).State = EntityState.Added;
        }
    }

    public void Delete(int customerId)
    {
        var existingCustomer = dbContext.Customers.Find(customerId);

        if (existingCustomer != null)
        {
            //delete cards
            var creditCards = dbContext.CreditCards.Where(c => c.CustomerId == customerId);
            foreach (var card in creditCards)
                dbContext.Entry<CreditCard>(card).State = EntityState.Deleted;

            //delete addresses
            var addresses = dbContext.CustomerAddresses.Where(c => c.CustomerId == customerId);
            foreach (var address in addresses)
                dbContext.Entry<CustomerAddress>(address).State = EntityState.Deleted;

            //delete basket
            dbContext.Entry<Customer>(existingCustomer).State = EntityState.Deleted;
        }
    }

    public Customer GetById(int customerId)
    {
        return dbContext.Customers.Include("Addresses").AsNoTracking().SingleOrDefault(c => c.CustomerId == customerId);
    }

    public IList<Customer> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
    {
        return null;
    }

    public void Save()
    {
        dbContext.SaveChanges();
    }

    #endregion CoreMethods


    #region AdditionalMethods

    #endregion AdditionalMethods

}

}

4

5 に答える 5

1

わかりました、今のところこれで十分だと思うので、かなり否定的な経験を要約します

a) ある程度は可能ですが、これはバージョン 5 であるため、より良いものを期待していました。おそらく最も簡単で簡単な回避策は、 http://edo-van-asseldonk.blogspot.co.uk/2012/03/readonly-collections-with-entity.html にあります。バスケットとその製品のコレクションがある場合は、BasketProductsReadOnlyCollection など、目前の問題に固有の読み取り専用コレクション。

b) とにかく a について心配する必要はないでしょう。「天才のストローク」で、マイクロソフトは、ここでの問題を考えると、適切な DDD コードを書くことをほとんど不可能にしました。Products テーブルに BasketId を持つ Basket と Products がある場合、Basket.RemoveProduct(product) を実行すると問題が発生します。このようなものを削除すると、レコードではなく「関係」が削除されることを意味します。したがって、EF は BasketId を null に設定しようとし、それができない場合は例外をスローします (いいえ、EF に合わせるためだけに null 可能にしたくありません。そうしない DBA と一緒に作業した場合はどうでしょうか?)行う必要があるのは、dbContext.Products.Remove(product) を呼び出して、削除されたことを確認することです。これは基本的に、ビジネス ロジック コードが dbContext を認識する必要があることを意味します。

c) もう悩まない!繰り返しになりますが、StackOverflow でこれに関する回答があり、何かを起動して実行できる可能性がありますが、それほど難しくなく、直感に反するものではありません。

全体像については、「切り離された」エンティティで機能する N 層の推奨事項を確認しました。私はジュリア・ラーマンの本を読んだが、彼はこのテーマの権威であると思われるが、私は感銘を受けなかった. オブジェクト グラフをアタッチする全体のしくみと、これを処理するための推奨される方法は、非常に直感的ではありません。物事を「シンプル」にするために彼女が推奨するアプローチは、各オブジェクトの状態をビジネス コードに記録することでした。私のお茶ではありません。

私は自分自身を建築の天才だとは考えておらず、おそらく何か (または多く) を見逃していますが、EF の努力は見当違いのように思えます。彼らは、あなたのためにすべてを行うことになっているこの追跡システム全体を実装するために多くの時間とお金を費やしました (典型的な MS、彼らは私たちがあまりにも愚かであるか、私たち自身のものの世話をする何かであると考えています)。はるかに使いやすい。

私が ORM に求めているのは、オブジェクトでデータを提供し、LEAVE ME ALONE を使用してそれらを処理し、オブジェクトまたはオブジェクト グラフを ORM に戻し、自由に伝えることです。オブジェクトグラフに追加/削除/更新したいものと、現在のEFの悪ふざけなしでどうするか。

結論:私はこれについてMSにさらに数年を与えると思います.彼らはおそらく最終的にそれを正しく理解するでしょうが、これはまだ私のためではありません. そして、MS は最終的に適切なドキュメント/チュートリアルをサイトに掲載するのでしょうか? 何年も前に NHibernate に関する 300 百ページの PDF チュートリアルを読んだことを覚えています。

于 2012-12-04T21:32:44.360 に答える
1

b への対応: データベースを作成するときは、削除をカスケードする (つまり、データベースは関連するすべての子レコードも削除する) か、外部キーを null 可能にする必要があります。その後、そのエラーは発生しません。これは EF のせいではありません。リレーショナル データベースが制約を処理する方法です。これは、EDMX、最初にコード、またはデータベース側で DDL を使用して構成できます。プロジェクトをどのようにセットアップしたかの決定に応じて。

c への応答: より一般的な感じですが、すべての子を削除して再挿入すると、エラーが発生しやすく、「におい」がします。少なくとも、絶対に必要な場合にのみそうします。パフォーマンスの観点からは、更新の方がおそらく高速です。削除して再挿入することを選択した理由をもう一度考え直してみてはいかがでしょうか?

于 2012-12-03T19:32:37.190 に答える
0

a) そもそも何をしようとしているのか? コレクションをプライベートにして、そのスナップショットを取得するパブリック プロパティのみを公開することはできませんか?

b) データベースから子エンティティを削除するにはdbcontext.ThatEntitySet.Remove(child)、 ではなくを使用しますparent.Children.Remove(child)

または、子の外部キーを主キーの一部にすることで、識別関係を作成できます。次にparent.Children.Remove(child)、DB から行を削除します。

c) ばかげたことをしているようだ。詳細を提供していただければ、別の解決策を提案します。

大きな話題: あなたのドメインは十分に複雑ですか? それとも、単純な CRUD アプリケーションで DDD パターンを強制するために適用しようとしているだけですか? どのようなビジネス ルールがありますか? 不変条件?エンティティにはどのような方法がありますか? ポリシーはありますか?

なぜ InsertOrUpdate メソッドが必要になるのでしょうか? エンティティの作成と更新に同じフォームを使用するため、あなたがそれを発明したと思います。これは、CRUD アプリを実行していることを示す強力なシグナルです。

于 2012-12-04T13:18:44.937 に答える
0

他の誰かがこれに苦労している場合、これは私が思いつくことができる最良の実装です。RemoveFromBasket、AddToBasket メソッドを見てください。理想的ではありませんが、少なくとも何かを起動して実行することができます

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
 using System.Web.Helpers;
 using EShop.Models.DomainModel;
 using System.Data;
 using EShop.Models.DataAccess;
 using System.Data.Objects;
 using System.Data.Entity.Infrastructure;

namespace EShop.Models.Repositories
{
public class BasketRepository : BaseRepository, IRepository<Basket, Int32>
{
    public BasketRepository() : base(new EShopData()) { }

    #region CoreMethods

    public void InsertOrUpdate(Basket basket)
    {
        var basketInDB = dbContext.Baskets.SingleOrDefault(b => b.BasketId == basket.BasketId);
        if (basketInDB == null)
            dbContext.Baskets.Add(basket);
    }

    public void Delete(int basketId)
    {
        var basket = this.GetById(basketId);
        if (basket != null)
        {
            foreach (var product in basket.BasketProducts.ToList())
            {
                basket.BasketProducts.Remove(product); //delete relationship
                dbContext.BasketProducts.Remove(product); //delete from DB
            }
            dbContext.Baskets.Remove(basket);
        }
    }

    public Basket GetById(int basketId)
    {
        // eager-load product info
        var basket = dbContext.Baskets.Include("BasketProducts")
                                      .Include("BasketProducts.Product.Brand").SingleOrDefault(b => b.BasketId == basketId);
        return basket;
    }

    public IList<Basket> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
    {
        throw new NotImplementedException();
    }

    public void Save()
    {
        dbContext.SaveChanges();
    }

    #endregion CoreMethods


    #region AdditionalMethods
    public void AddToBasket(Basket basket, Product product, int quantity)
    {
        var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId);
        if (existingProductInBasket == null)
        {
            var basketProduct = new BasketProduct()
            {
                BasketId = basket.BasketId,
                ProductId = product.ProductId,
                Quantity = quantity
            };
            basket.BasketProducts.Add(basketProduct);   
        }
        else
        {
            existingProductInBasket.Quantity = quantity;
        }
    }

    public void RemoveFromBasket(Basket basket, Product product)
    {
        var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId);
        if (existingProductInBasket != null)
        {
            basket.BasketProducts.Remove(existingProductInBasket); //delete relationship
            dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB
        }
    }

    public void RemoveFromBasket(BasketProduct basketProduct)
    {
        var basket = dbContext.Baskets.Find(basketProduct.BasketId);
        var existingProductInBasket = dbContext.BasketProducts.Find(basketProduct.BasketId, basketProduct.ProductId);
        if (basket != null && existingProductInBasket != null)
        {
            basket.BasketProducts.Remove(existingProductInBasket); //delete relationship
            dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB
        }
    }

    public void ClearBasket(Basket basket)
    {
        foreach (var product in basket.BasketProducts.ToList())
            basket.BasketProducts.Remove(product);
    }

    #endregion AdditionalMethods

}

}

于 2012-12-06T09:48:39.903 に答える