Question

Mes classes de domaine qui ont un à-plusieurs prennent généralement la forme suivante (code non testé):

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

exemples de code seulement EF4 que j'ai vu exposer un ICollection public lorsqu'il traite avec un à plusieurs.

Est-il possible de persister et de restaurer mes collections avec les exposant? Dans le cas contraire, il semblerait que mes objets de domaine seront conçus pour répondre aux exigences de l'ORM, qui semble aller à l'encontre de l'esprit de l'entreprise. L'exposition ICollection (avec sa Ajouter, etc.) méthodes ne semble pas particulièrement propre, et ne serait pas mon approche par défaut.

Mise à jour

ce message qui suggère qu'il n'a pas été possible en mai. Bien sûr, l'affiche Microsoft a bien dit qu'ils étaient « envisagent fortement la mise en œuvre » (je serais l'espère) et nous sommes une demi-année, alors peut-être il y a eu des progrès?

Était-ce utile?

La solution

Je trouve que tout ce qui a été fait, EF exige le ICollection<T> être public. Je pense que cela est parce que quand les objets sont chargés à partir de la base de données, les regards de cartographie pour une propriété de collection, obtient la collection, puis appelle la méthode Add de la collection d'ajouter chacun des objets de l'enfant.

Je voulais faire en sorte que l'addition a été fait par une méthode sur l'objet parent ainsi créé une solution d'emballage la collecte, la capture et l'ajouter à diriger ma méthode préférée d'addition.

L'extension d'un List et d'autres types de collecte n'a pas été possible car la méthode Add n'est pas virtuelle. Une option consiste à étendre la classe de Collection et remplacer la méthode InsertItem.

J'ai porté que sur les fonctions Add, Remove et Clear de l'interface ICollection<T> que ce sont ceux qui peuvent modifier la collection.

D'abord, mon enveloppe de collection de base qui implémente l'interface ICollection<T> Le comportement par défaut est celui d'une collection normale. Toutefois, l'appelant peut spécifier une méthode Add alternative à appeler. De plus, l'appelant peut appliquer que les Add, Remove, les opérations de Clear ne sont pas autorisés en définissant les alternatives à null. Cela se traduit par NotSupportedException jetés si tente de quiconque d'utiliser la méthode.

Le lancement d'une exception est pas aussi bon que d'empêcher l'accès en premier lieu. Cependant, le code doit être testé (unité testée) et une exception se trouve très rapidement et un changement de code approprié fait.

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

}

Étant donné que la classe de base, nous pouvons l'utiliser de deux façons. Les exemples utilisent les objets post d'origine.

1) Créer un type spécifique de collection enveloppé (Par exemple, List)     public class WrappedListCollection: WrappedCollectionBase, IList     {         Liste privée 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
    }

Cela peut ensuite être utilisé:

 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) Donnez une collection à l'aide enveloppa

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

qui peut être utilisé comme suit:

{      Les commandes ICollection publics {get {_wrappedOrders de retour; }}      // Méthodes publiques.

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

Il y a d'autres façons d'appeler les constructeurs de WrappedCollection Par exemple, pour remplacer ajouter mais gardez supprimer et effacer normalement

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

Je suis d'accord que ce serait mieux si EF ne nécessiterait pas la collection d'être public, mais cette solution me permet de contrôler la modification de ma collection.

Pour le problème d'empêcher l'accès à la collecte pour effectuer des requêtes, vous pouvez utiliser l'approche 2) ci-dessus et définir la méthode de GetEnumerator de WrappedCollection jeter un NotSupportedException. Ensuite, votre méthode de GetOrder peut rester tel qu'il est. Une méthode plus propre peut cependant être d'exposer la collection enveloppée. Par exemple:

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

Ensuite, l'appel dans la méthode GetOrder deviendrait

_orders.InnerCollection.Where(x => x.Id == id).Single();

Autres conseils

Une autre façon d'y parvenir serait de créer une interface associée pour chacun de vos Poços pour exposer seulement ce que vous voulez en dehors des couches persistance / domaine. Vous pouvez également interfacer votre classe DbContext d'accéder également cacher et de contrôle aux collections DbSet. Comme il se trouve, les propriétés DbSet peuvent être protégées, et le constructeur de modèle les ramasser quand il est la création de tables, mais quand vous essayez d'accéder aux collections qu'ils seront nuls. Une méthode de fabrication (dans mon exemple, CreateNewContext) peut être utilisé à la place du constructeur pour obtenir le interfacé DbContext pour cacher les collections DbSet.

Il y a un peu d'effort supplémentaire dans le codage, mais si les détails de mise en œuvre de se cacher dans les Poços est important, cela fonctionne.

Mise à jour: Il se trouve que vous pouvez remplir DBSets si elles sont protégées, mais pas directement dans le DBContext. Ils ne peuvent pas être des racines globales (à savoir l'accessibilité de l'entité doit être à travers une collection dans l'une des entités publiques DBSet). Si la mise en œuvre de cacher DBSet est important, le modèle d'interface que je viens de décrire est toujours d'actualité.

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

}

Si vous modifiez le nom de votre collection de _orders au nom de la table des commandes dans votre base de données, cela devrait fonctionner. EF cartes noms de table / champ aux collections / propriétés par convention. Si vous souhaitez utiliser un autre nom, vous pouvez modifier les mises en correspondance dans le fichier edmx.

vous pouvez AFAIK il suffit de laisser le modificateur privé tel qu'il est. Collections ne doivent pas nécessairement être public.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top