6

1 対多のマッピングを持つドメイン クラスは、通常、次の形式をとります (テストされていないコード)。

public Customer Customer
{
    // Public methods.

    public Order AddOrder(Order order)
    {
        _orders.Add(order);
    }

    public Order GetOrder(long id)
    {
        return _orders.Where(x => x.Id).Single();
    }

    // etc.

    // Private fields.

    private ICollection<Order> _orders = new List<Order>();
}

私が見たEF4 コードのみのサンプルでは、​​1 対多の関係を処理するときにパブリック ICollection が公開されています。

コレクションを公開して保持および復元する方法はありますか? そうでない場合、私のドメイン オブジェクトは ORM の要件を満たすように設計されているように見えますが、これは努力の精神に反しているようです。ICollection を (Add などのメソッドを使用して) 公開することは、特にきれいに見えず、私のデフォルトのアプローチではありません。

アップデート

5月にはできなかったことを示唆するこの投稿を見つけました。もちろん、Microsoft のポスターは、「実装を強く検討している」と述べており (そうであることを願っています)、半年が経過しているので、何か進展があったのでしょうか?

4

3 に答える 3

1

これを実現するもう 1 つの方法は、各 POCO に関連付けられたインターフェイスを作成して、永続化/ドメイン レイヤーの外部に必要なものだけを公開することです。DbContext クラスをインターフェースして、DbSet コレクションへのアクセスを隠したり制御したりすることもできます。結局のところ、DbSet プロパティは保護することができ、モデル ビルダーはテーブルの作成時にそれらを取得しますが、コレクションにアクセスしようとすると null になります。コンストラクターの代わりにファクトリ メソッド (この例では CreateNewContext) を使用して、インターフェース化された DbContext を取得し、DbSet コレクションを隠すことができます。

コーディングにはかなりの手間がかかりますが、実装の詳細を POCO 内に隠すことが重要な場合は、これでうまくいきます。

更新: DBSets が保護されている場合は、DBSets を設定できますが、DBContext に直接設定することはできません。集約ルートにすることはできません (つまり、エンティティへのアクセスは、パブリック DBSet エンティティの 1 つのコレクションを介する必要があります)。DBSet の実装を非表示にすることが重要である場合でも、説明したインターフェイス パターンは引き続き有効です。

public interface ICustomer
{
   void AddOrder(IOrder order);
   IOrder GetOrder(long id);
}

public Customer : ICustomer
{
   // Exposed methods:
   void ICustomer.AddOrder(IOrder order)
   {
      if (order is Order)
         orders.Add((Order)order);
      else
         throw new Exception("Hey! Not a mapped type!");
   }

   IOrder ICustomer.GetOrder(long id)
   {
      return orders.Where(x => x.Id).Single();
   }

   // public collection for EF
   // The Order class definition would follow the same interface pattern illustrated 
   // here for the Customer class.
   public ICollection<Order> orders = new List<Order>();
}

public interface IMyContext
{
   IEnumerable<ICustomer> GetCustomers();
   void AddCustomer(ICustomer customerObject);
   ICustomer CreateNewCustomer()
}


public class MyContext : DbContext, IMyContext
{
   public static IMyContext CreateNewContext() { return new MyContext(); }

   public DbSet<Customer> Customers {get;set;}
   public  DbSet<Order> Orders {get;set;}

   public IEnumerable<ICustomer> GetCustomers()
   {
      return Customers;
   }

   public void AddCustomer(ICustomer customerObject)
   {
      if (customerObject is Customer)
         Customers.Add((Customer)customerObject);
      else
         throw new Exception("Hey! Not a mapped type");
   }

   public ICustomer CreateNewCustomer()
   {
      return Customers.Create();
   }

   // wrap the Removes, Finds, etc as necessary. Remember to add these to the 
   // DbContext's interface

   // Follow this pattern also for Order/IOrder

}
于 2011-12-28T17:53:32.613 に答える
1

何を行っても、EF ではICollection<T>公開する必要があることがわかりました。Addこれは、オブジェクトがデータベースから読み込まれるときに、マッピングがコレクション プロパティを探し、コレクションを取得してから、コレクションのメソッドを呼び出して各子オブジェクトを追加するためだと思います。

追加が親オブジェクトのメソッドを介して行われたことを確認したかったので、コレクションをラップし、追加をキャッチして、好みの追加方法に向けるソリューションを作成しました。

メソッドが仮想ではないListため、a およびその他のコレクション型を拡張できませんでした。1 つのオプションは、クラスAddを拡張してメソッドをオーバーライドすることです。CollectionInsertItem

これらはコレクションを変更できるものであるため、インターフェイスのAddRemove、およびClear関数にのみ焦点を当てました。ICollection<T>

まず、ICollection<T>インターフェイスを実装するベース コレクション ラッパーです。デフォルトの動作は、通常のコレクションの動作です。ただし、呼び出し元は、呼び出される代替Addメソッドを指定できます。さらに、呼び出し元は、代替を に設定することAddによってRemove、、、Clear操作が許可されないようにすることができnullます。これはNotSupportedException、誰かがメソッドを使用しようとするとスローされます。

例外のスローは、そもそもアクセスを防止するほど効果的ではありません。ただし、コードはテスト (単体テスト) する必要があり、例外は非常に迅速に検出され、適切なコード変更が行われます。

public abstract class WrappedCollectionBase<T> : ICollection<T>
{

    private ICollection<T> InnerCollection { get { return GetWrappedCollection(); } }

    private Action<T> addItemFunction;
    private Func<T, bool> removeItemFunction;
    private Action clearFunction;


    /// <summary>
    /// Default behaviour is to be like a normal collection
    /// </summary>
    public WrappedCollectionBase()
    {
        this.addItemFunction = this.AddToInnerCollection;
        this.removeItemFunction = this.RemoveFromInnerCollection;
        this.clearFunction = this.ClearInnerCollection;
    }

    public WrappedCollectionBase(Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction) : this()
    {
        this.addItemFunction = addItemFunction;
        this.removeItemFunction = removeItemFunction;
        this.clearFunction = clearFunction;
    }

    protected abstract ICollection<T> GetWrappedCollection();

    public void Add(T item)
    {
        if (this.addItemFunction != null)
        {
            this.addItemFunction(item);
        }
        else
        {
            throw new NotSupportedException("Direct addition to this collection is not permitted");
        }
    }

    public void AddToInnerCollection(T item)
    {
        this.InnerCollection.Add(item);
    }

    public bool Remove(T item)
    {
        if (removeItemFunction != null)
        {
            return removeItemFunction(item);
        }
        else
        {
            throw new NotSupportedException("Direct removal from this collection is not permitted");
        }
    }

    public bool RemoveFromInnerCollection(T item)
    {
        return this.InnerCollection.Remove(item);
    }

    public void Clear()
    {
        if (this.clearFunction != null)
        {
            this.clearFunction();
        }
        else
        {
            throw new NotSupportedException("Clearing of this collection is not permitted");
        }
    }

    public void ClearInnerCollection()
    {
        this.InnerCollection.Clear();
    }

    public bool Contains(T item)
    {
        return InnerCollection.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        InnerCollection.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return InnerCollection.Count; }
    }

    public bool IsReadOnly
    {
        get { return ((ICollection<T>)this.InnerCollection).IsReadOnly; }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return InnerCollection.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return InnerCollection.GetEnumerator();
    }

}

その基本クラスを考えると、それを 2 つの方法で使用できます。例では、元の投稿オブジェクトを使用しています。

1) 特定のタイプのラップされたコレクションを作成します (例List: ) public class WrappedListCollection : WrappedCollectionBase, IList { private List innerList;

    public WrappedListCollection(Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction)
        : base(addItemFunction, removeItemFunction, clearFunction)
    { 
    this.innerList = new List<T>();
    }

    protected override ICollection<T> GetWrappedCollection()
    {
        return this.innerList;
    }
 <...snip....> // fill in implementation of IList if important or don't implement IList
    }

これは、次のように使用できます。

 public Customer Customer
 {
 public ICollection<Order> Orders {get { return _orders; } }
 // Public methods.

 public void AddOrder(Order order)
 {
    _orders.AddToInnerCollection(order);
 }

// Private fields.

private WrappedListCollection<Order> _orders = new WrappedListCollection<Order>(this.AddOrder, null, null);
}

2) を使用してラップされるコレクションを指定します

 public class WrappedCollection<T> : WrappedCollectionBase<T>
{
    private ICollection<T> wrappedCollection;

    public WrappedCollection(ICollection<T> collectionToWrap, Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction)
        : base(addItemFunction, removeItemFunction, clearFunction)
    {
        this.wrappedCollection = collectionToWrap;
    }

    protected override ICollection<T> GetWrappedCollection()
    {
        return this.wrappedCollection;
    }
}

次のように使用できます。

{ public ICollection オーダー {get { return _wrappedOrders; } } // パブリック メソッド。

 public void AddOrder(Order order)
 {
    _orders.Add(order);
 }

// Private fields.
private ICollection<Order> _orders = new List<Order>();
private WrappedCollection<Order> _wrappedOrders = new WrappedCollection<Order>(_orders, this.AddOrder, null, null);
}

WrappedCollectionコンストラクターを呼び出す方法は他にもいくつかあります。

private WrappedListCollection<Order> _orders = new WrappedListCollection(this.AddOrder,  (Order o) => _orders.RemoveFromInnerCollection(o), () => _orders.ClearInnerCollection());

EF がコレクションを公開する必要がないことが最善であることに同意しますが、このソリューションにより、コレクションの変更を制御できます。

クエリのためにコレクションにアクセスできないという問題については、上記のアプローチ 2) を使用して、WrappedCollectionGetEnumeratorメソッドをNotSupportedException. その後、GetOrderメソッドはそのままにすることができます。ただし、より適切な方法は、ラップされたコレクションを公開することです。例えば:

 public class WrappedCollection<T> : WrappedCollectionBase<T>
 {
    public ICollection<T> InnerCollection { get; private set; }

    public WrappedCollection(ICollection<T> collectionToWrap, Action<T> addItemFunction, Func<T, bool> removeItemFunction, Action clearFunction)
        : base(addItemFunction, removeItemFunction, clearFunction)
    {
        this.InnerCollection = collectionToWrap;
    }


    protected  override ICollection<T> GetWrappedCollection()
    {
        return this.InnerCollection;
    }
}

次に、GetOrderメソッドの呼び出しは次のようになります

_orders.InnerCollection.Where(x => x.Id == id).Single();
于 2011-06-04T13:40:38.023 に答える
0

_ordersコレクションの名前をデータベースの注文テーブルの名前に変更すると、これが機能するはずです。EF は、慣例により、テーブル/フィールド名をコレクション/プロパティにマップします。別の名前を使用する場合は、edmx ファイルでマッピングを編集できます。

私の知る限り、プライベート修飾子はそのままにしておくことができます。コレクションは公開する必要はありません。

于 2010-11-03T13:10:06.757 に答える