Question

Ainsi, j'ai lu tous les Q & A; sont ici sur SO concernant la question d'exposer ou non IQueryable au reste de votre projet (voir ici , et ici ), et j’ai finalement décidé de ne pas exposer IQueryable à autre chose que mon modèle. IQueryable étant lié à certaines implémentations de persistance, je n'aime pas l'idée de m'enfermer dans cette situation. De même, je ne suis pas sûr de ce que je pense des classes plus loin dans la chaîne d'appels, modifiant la requête réelle qui ne se trouve pas dans le référentiel.

Alors, est-ce que quelqu'un a des suggestions sur la façon d'écrire un référentiel propre et concis sans le faire? Un problème que je vois, c’est que mon référentiel explose à partir d’une multitude de méthodes permettant de filtrer ma requête.

Avoir un groupe de:

IEnumerable GetProductsSinceDate(DateTime date);  
IEnumberable GetProductsByName(string name);  
IEnumberable GetProductsByID(int ID);

Si je permettais à IQueryable d'être diffusé, je pourrais facilement disposer d'un référentiel générique ressemblant à:

public interface IRepository<T> where T : class
{
    T GetById(int id);
    IQueryable<T> GetAll();
    void InsertOnSubmit(T entity);
    void DeleteOnSubmit(T entity);
    void SubmitChanges();
}

Toutefois, si vous n'utilisez pas IQueryable, les méthodes telles que GetAll () ne sont pas vraiment pratiques car une évaluation lazy ne se déroulera pas ultérieurement. Je ne veux pas renvoyer 10 000 enregistrements, mais en utiliser 10 plus tard.

Quelle est la réponse ici? Dans MVC Storefront de Conery , il a créé une autre couche appelée "Service". couche qui a reçu les résultats IQueryable du référentiel et était responsable de l’application de divers filtres.

Est-ce ce que je devrais faire ou quelque chose de similaire? Mon référentiel renvoie-t-il IQueryable mais en restreint l'accès en le masquant derrière un groupe de classes de filtres comme GetProductByName, qui renverra un type concret comme IList ou IEnumerable?

Était-ce utile?

La solution

Exposer un IQueryable est une solution très viable et c’est ce que font la plupart des implémentations de référentiel actuellement. (Y compris les contributions SharpArchitecture et FubuMVC.)

C’est là que vous vous trompez:

  

Cependant, si vous n'utilisez pas   IQueryable puis des méthodes comme GetAll ()   ne sont pas vraiment pratiques depuis paresseux   l'évaluation ne se déroulera pas   la ligne. Je ne veux pas revenir   10 000 enregistrements seulement pour en utiliser 10   plus tard.

Ce n'est pas vraiment vrai. Votre exemple est correct et vous devez renommer GetAll () en un nom plus informatif.

Il ne retourne pas tous les éléments si vous l'appelez. C’est à cela que sert IQueryable. Le concept s'appelle "chargement différé", car il charge uniquement les données (et effectue des requêtes de base de données) lorsque vous énumérez le IQueryable .

Alors, disons que j'ai une méthode comme celle-ci:

IQueryable<T> Retrieve() { ... }

Ensuite, je peux l'appeler comme suit:

Repository.Retrieve<Customer>().Single(c => c.ID == myID);

Ceci récupère UNIQUEMENT une ligne de la base de données.

Et ceci:

Repository.Retrieve<Customer>().Where(c => c.FirstName == "Joe").OrderBy(c => c.LastName);

Ceci génère également une requête correspondante et n'est exécuté que lorsque vous l'énumérez. (Il génère une arborescence d'expression à partir de la requête. Le fournisseur de la requête doit ensuite traduire cette requête en requête appropriée sur la source de données.)

Pour en savoir plus à ce sujet, dans cet article MSDN .

Autres conseils

La méthode de Rob ne résout vraiment pas votre problème principal et elle ne veut pas écrire de méthodes individuelles pour chaque type de requête que vous souhaitez exécuter. Malheureusement, si vous n'utilisez pas IQueryable, c'est ce que vous êtes. laissé avec.

Bien sûr, les méthodes peuvent être dans le "service". couche, mais cela signifie toujours que vous devez écrire "GetProductsByName, GetProductsByDate" ...

L'autre méthode s'apparente à:

GetProducts(QueryObject);

Cela pourrait vous apporter un avantage par rapport à l'utilisation d'IQueryable dans la mesure où vous pouvez limiter ce qui est renvoyé.

hmm .. J'ai résolu ce problème de plusieurs manières en fonction du type d'ORM que j'utilise.
L'idée principale est de disposer d'une classe de base de référentiel et d'une méthode de requête qui prennent autant de paramètres indiquant toutes les options possible where / orderby / expand | include / paging / etc.

Voici un exemple rapide et incorrect qui utilise LINQ pour NHibernate (bien entendu, le référentiel entier doit contenir les détails de la mise en œuvre):

public class RepositoryBase
    {
        private ISession Session;

        public RepositoryBase()
        {
            Session = SessionPlaceHolder.Session;
        }



        public TEntity[] GetPaged<TEntity>(IEnumerable<Expression<Func<TEntity, bool>>> filters,
            IEnumerable<Expression<Func<TEntity, object>>> relatedObjects,
            IEnumerable<Expression<Func<TEntity, object>>> orderCriterias,
            IEnumerable<Expression<Func<TEntity, object>>> descOrderCriterias,
            int pageNumber, int pageSize, out int totalPages)
        {
            INHibernateQueryable<TEntity> nhQuery = Session.Linq<TEntity>();

            if (relatedObjects != null)
                foreach (var relatedObject in relatedObjects)
                {
                    if (relatedObject == null) continue;
                    nhQuery = nhQuery.Expand(relatedObject);
                }

            IQueryable<TEntity> query = nhQuery;

            if (filters != null)
                foreach (var filter in filters)
                {
                    if (filter == null) continue;
                    query = query.Where(filter);
                }

            bool pagingEnabled = pageSize > 0;

            if (pagingEnabled)
                totalPages = (int) Math.Ceiling((decimal) query.Count()/(decimal) pageSize);
            else
                totalPages = 1;

            if (orderCriterias != null)
                foreach (var orderCriteria in orderCriterias)
                {
                    if (orderCriteria == null) continue;
                    query = query.OrderBy(orderCriteria);
                }

            if (descOrderCriterias != null) 
                foreach (var descOrderCriteria in descOrderCriterias)
                {
                    if (descOrderCriteria == null) continue;
                    query = query.OrderByDescending(descOrderCriteria);
                }

            if (pagingEnabled)
                query = query.Skip(pageSize*(pageNumber - 1)).Take(pageSize);

            return query.ToArray();
        }
    }

Normalement, vous voudrez ajouter de nombreuses surcharges d'enchaînement sous forme de raccourcis lorsque vous n'avez pas besoin de pagination, par exemple, etc.

Voici un autre sale. Désolé, je ne suis pas sûr de pouvoir exposer les derniers. C'étaient des brouillons et vous pouvez montrer:

using Context = Project.Services.Repositories.EntityFrameworkContext;
using EntitiesContext = Project.Domain.DomainSpecificEntitiesContext;    
namespace Project.Services.Repositories
{
    public class EntityFrameworkRepository : IRepository
    {
        #region IRepository Members

        public bool TryFindOne<T>(Expression<Func<T, bool>> filter, out T result)
        {
            result = Find(filter, null).FirstOrDefault();

            return !Equals(result, default(T));
        }

        public T FindOne<T>(Expression<Func<T, bool>> filter)
        {
            T result;
            if (TryFindOne(filter, out result))
                return result;

            return default(T);
        }

        public IList<T> Find<T>() where T : class, IEntityWithKey
        {
            int count;
            return new List<T>(Find<T>(null, null, 0, 0, out count));
        }

        public IList<T> Find<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> sort)
        {
            int count;
            return new List<T>(Find(filter, sort, 0, 0, out count));
        }

        public IEnumerable<T> Find<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> sort, int pageSize,
                                      int pageNumber, out int count)
        {
            return ExecuteQuery(filter, sort, pageSize, pageNumber, out count) ?? new T[] {};
        }

        public bool Save<T>(T entity)
        {
            var contextSource = new EntityFrameworkContext();

            EntitiesContext context = contextSource.Context;

            EntityKey key = context.CreateEntityKey(GetEntitySetName(entity.GetType()), entity);

            object originalItem;
            if (context.TryGetObjectByKey(key, out originalItem))
            {
                context.ApplyPropertyChanges(key.EntitySetName, entity);
            }
            else
            {
                context.AddObject(GetEntitySetName(entity.GetType()), entity);
                //Attach(context, entity);
            }

            return context.SaveChanges() > 0;
        }

        public bool Delete<T>(Expression<Func<T, bool>> filter)
        {
            var contextSource = new EntityFrameworkContext();

            EntitiesContext context = contextSource.Context;

            int numberOfObjectsFound = 0;
            foreach (T entity in context.CreateQuery<T>(GetEntitySetName(typeof (T))).Where(filter))
            {
                context.DeleteObject(entity);
                ++numberOfObjectsFound;
            }

            return context.SaveChanges() >= numberOfObjectsFound;
        }

        #endregion

        protected IEnumerable<T> ExecuteQuery<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> sort,
                                                 int pageSize, int pageNumber,
                                                 out int count)
        {
            IEnumerable<T> result;

            var contextSource = new EntityFrameworkContext();

            EntitiesContext context = contextSource.Context;

            ObjectQuery<T> originalQuery = CreateQuery<T>(context);
            IQueryable<T> query = originalQuery;

            if (filter != null)
                query = query.Where(filter);

            if (sort != null)
                query = query.OrderBy(sort);

            if (pageSize > 0)
            {
                int pageIndex = pageNumber > 0 ? pageNumber - 1 : 0;
                query = query.Skip(pageIndex).Take(pageSize);

                count = query.Count();
            }
            else 
                count = -1;


            result = ExecuteQuery(context, query);

            //if no paging total count is count of the entire result set
            if (count == -1) count = result.Count();

            return result;
        }

        protected internal event Action<ObjectContext, IEnumerable> EntitiesFound;

        protected void OnEntitiesFound<T>(ObjectContext context, params T[] entities)
        {
            if (EntitiesFound != null && entities != null && entities.Length > 0)
            {
                EntitiesFound(context, entities);
            }
        }

        //Allowing room for system-specific-requirement extensibility
        protected Action<IEnumerable> ItemsFound;

        protected IEnumerable<T> ExecuteQuery<T>(ObjectContext context, IQueryable<T> query)
        {
            IEnumerable<T> result = null;

            if (query is ObjectQuery)
            {
                var objectQuery = (ObjectQuery<T>) query;

                objectQuery.EnablePlanCaching = false;
                objectQuery.MergeOption = MergeOption.PreserveChanges;

                result = new List<T>(objectQuery);

                if (ItemsFound != null)
                    ItemsFound(result);

                return result;
            }

            return result;
        }

        internal static RelationshipManager GetRelationshipManager(object entity)
        {
            var entityWithRelationships = entity as IEntityWithRelationships;
            if (entityWithRelationships != null)
            {
                return entityWithRelationships.RelationshipManager;
            }

            return null;
        }


        protected ObjectQuery<T> CreateQuery<T>(ObjectContext context)
        {
            ObjectQuery<T> query = context.CreateQuery<T>(GetEntitySetName(typeof (T)));
            query = this.AggregateEntities(query);
            return query;
        }

        protected virtual ObjectQuery<T> AggregateEntities<T>(ObjectQuery<T> query)
        {
            return query;
        }

        private static string GetEntitySetName(Type entityType)
        {
            return string.Format("{0}Set", entityType.Name);
        }
    }

    public class EntityFrameworkContext
    {
        private const string CtxKey = "ctx";

        private bool contextInitialized
        {
            get { return HttpContext.Current.Items[CtxKey] != null;  }
        }

        public EntitiesContext Context
        {
            get
            {
                if (contextInitialized == false)
                {
                    HttpContext.Current.Items[CtxKey] = new EntitiesContext(ConfigurationManager.ConnectionStrings["CoonectionStringName"].ToString());
                }

                return (EntitiesContext)HttpContext.Current.Items[CtxKey];
            }
        }

        public void TrulyDispose()
        {
            if (contextInitialized)
            {
                Context.Dispose();
                HttpContext.Current.Items[CtxKey] = null;
            }
        }
    }

    internal static class EntityFrameworkExtensions
    {
        internal static ObjectQuery<T> Include<T>(this ObjectQuery<T> query,
                                                  Expression<Func<T, object>> propertyToInclude)
        {
            string include = string.Join(".", propertyToInclude.Body.ToString().Split('.').Skip(1).ToArray());

            const string collectionsLinqProxy = ".First()";
            include = include.Replace(collectionsLinqProxy, "");

            return query.Include(include);
        }

        internal static string After(this string original, string search)
        {
            if (string.IsNullOrEmpty(original))
                return string.Empty;

            int index = original.IndexOf(search);
            return original.Substring(index + search.Length);
        }
    }
}
  

Dans MVC Storefront de Conery, il a créé   une autre couche appelée "Service".   couche qui a reçu IQueryable   les résultats du référentiel et était   responsable de l'application de divers   filtres.

Dans tous les cas, personne ne doit interagir directement avec le référentiel, à l'exception de la couche services.

La solution la plus souple consiste à laisser les services interagir avec le référentiel comme bon leur semble, comme dans le code ci-dessus (à l'aide d'un seul point, comme dans l'exemple ci-dessus, pour écrire du code DRY et trouver une place pour l'optimisation).
Cependant, la méthode la plus appropriée en termes de modèles DDD courants consiste à utiliser le paramètre "Spécification". pattern, où vous encapsulez tous vos filtres, etc. dans des variables (membres de classe, dans LINQ généralement des types délégués). LINQ peut en tirer un avantage considérable en le combinant avec des "requêtes compilées". Si vous recherchez les {modèles de spécification} et les {requêtes compilées} de LINQ} sur Google, vous vous rapprocherez de ce que je veux dire ici.

J'ai fini par créer deux ensembles de méthodes, l'une renvoyant IEnumerable (dans votre cas, IQueryable) et l'autre renvoyant Collection (extraire le contenu avant de l'envoyer hors du référentiel.)

Cela me permet à la fois de créer des requêtes ad hoc dans les services extérieurs au référentiel et d’utiliser des méthodes de référentiel renvoyant directement des collections résistantes aux effets secondaires. En d'autres termes, la jonction de deux entités de référentiel ensemble donne une seule requête de sélection au lieu d'une requête de sélection pour chaque entité trouvée.

J'imagine que vous pouvez définir votre niveau de protection pour éviter que de graves problèmes ne se produisent.

Ayant moi-même eu du mal à trouver une solution viable à ce problème, il semble y avoir ce qui semble être une bonne solution dans le Implémentation des modèles de référentiel et d'unité de travail dans un Application ASP.NET MVC (9 sur 10) article.

public virtual IEnumerable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = "") 
    {
        IQueryable<TEntity> query = dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

L'article ne parle pas de ce problème mais des méthodes génériques de référentiel réutilisable.

Jusqu'à présent, c'est tout ce que j'ai pu proposer comme solution.

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