代码之家  ›  专栏  ›  技术社区  ›  mmcdole

如何编写一个干净的存储库,而不将IQuerying暴露给应用程序的其他部分?

  •  19
  • mmcdole  · 技术社区  · 16 年前

    here here

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

    如果我允许IQuerying被传递,我可以很容易地得到一个通用的存储库,看起来像:

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

    然而,如果你不使用IQuerying,那么GetAll()等方法就不太实用,因为以后不会进行懒惰求值。我不想返回10000条记录,只是为了以后使用其中的10条。

    Conery's MVC Storefront

    5 回复  |  直到 16 年前
        1
  •  5
  •   Venemo    11 年前

    暴露一个 IQueryable

    但是,如果您不使用 IQuery然后像GetAll()这样的方法 因为懒惰,所以不太实用 评估不会进行下去 线。我不想回来了 10000条记录只能使用其中的10条

    这不是真的。您的示例是正确的,您应该将GetAll()重命名为更具信息量的名称。

    如果你调用它,它不会返回所有项目。这就是IQuerying的作用。这个概念被称为“延迟加载”,因为它只在枚举数据时加载数据(并发出数据库请求) IQueryable .

    所以,假设我有一个这样的方法:

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

    那么,我可以这样称呼它:

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

    这只从数据库中检索一行。

    还有这个:

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

    in this MSDN article .

        2
  •  3
  •   ChadT    16 年前

    当然,这些方法可能在“服务”层,但这仍然意味着必须编写“GetProductsByName,GetProductsByDate”。..

    GetProducts(QueryObject);
    

        3
  •  3
  •   Meligy    16 年前

    嗯。根据我使用的ORM类型,我通过多种方式解决了这个问题。
    其主要思想是有一个存储库基类和一个查询方法,该方法接受如此多的参数,指示所有可能的/orderby/expand|include/paging等选项。

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

    通常,当您不需要分页等时,您会希望添加许多链接重载作为快捷方式。。

    这是另一个肮脏的。对不起,我不确定我是否能揭露最后的那些。这些是草稿,可以显示:

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

    在Conery的MVC店面中,他创建了 另一层称为“服务” 接收到IQuery的层

    在所有情况下,除了服务层之外,任何人都不应该直接与存储库交互。

    最灵活的方法是让服务以任何他们想要的方式与存储库交互,就像上面的代码一样(但通过一个点——就像例子中一样——编写DRY代码并找到优化的地方)。

        4
  •  0
  •   Zachary Scott    15 年前

    我最终创建了两组方法,一组返回IEnumerable(在您的例子中是IQuerying),另一组返回Collection(在将内容发送到存储库之前提取内容)

    这使我既可以在存储库外的服务中构建即席查询,也可以使用存储库方法直接返回抗副作用的集合。换句话说,将两个存储库实体连接在一起会产生一个选择查询,而不是为每个找到的实体都产生一个搜索查询。

        5
  •  0
  •   Joseph Woodward    12 年前

    Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10) 文章。

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

    本文没有讨论这个确切的问题,但确实讨论了通用的、可重用的存储库方法。

    到目前为止,这就是我能想出的解决方案。