2009-06-23 7 views
19

Итак, я прочел все Q & A здесь, о том, что касается вопроса о том, следует ли выставлять IQueryable для остальной части вашего проекта или нет (см. here и here), и я в конечном итоге решил, что я не хотите подвергать IQueryable чему-либо, кроме моей модели. Поскольку IQueryable привязан к определенным реализациям настойчивости, мне не нравится идея запереть себя в этом. Точно так же я не уверен, насколько хорошо я отношусь к классам дальше по цепочке вызовов, изменяя фактический запрос, который не находится в репозитории.Как я могу написать чистый репозиторий, не подвергая IQueryable остальной части моего приложения?

Итак, есть ли у кого-нибудь предложения по написанию чистого и сжатого репозитория без этого? Одна из проблем, которую я вижу, - это мой репозиторий, который взорвется от множества методов для разных вещей, которые мне нужно отфильтровать из моего запроса.

Имея кучу:

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

Если я разрешал IQueryable быть розданы я мог бы легко иметь общий репозиторий, который выглядел как:

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

Однако, если вы не используя IQueryable, тогда методы, подобные GetAll(), не очень практичны, так как ленивая оценка не будет проходить по линии. Я не хочу возвращать 10 000 записей, чтобы использовать 10 из них позже.

Каков ответ здесь? В Conery's MVC Storefront он создал еще один слой, называемый слоем «Сервис», который получил результаты IQueryable из репозитория и отвечал за применение различных фильтров.

Это то, что я должен делать, или что-то подобное? Восстановите мой репозиторий IQueryable, но ограничьте его доступ, спрятав его за кучей классов фильтров, таких как GetProductByName, которые возвратят конкретный тип, например IList или IEnumerable?

+0

Вы нашли решение для этого? – Meligy

+0

Я в той же обуви. Не хотите раскрывать IQueryable, поскольку я определенно вижу, что он протекает вне репозитория и неправильно используется. Я также не хочу привязываться к устойчивости на основе linq. Что-то лучше может прийти, и переключение в этот момент не будет возможным. Лично я вижу здесь 2 варианта: а) раздутые репозитории; б) DAO поверх репозитория. Я действительно рассматриваю выбор b как мой вариант. Хотя это идет вразрез с принципом DDD, я бы предпочел гранулярность, чем огромные и сложные репозитории. – 2010-01-13 07:06:13

+0

В конечном счете, наилучшее решение, вероятно, связано с выяснением способа создания запроса в качестве параметра без наличия набора, который мы запрашиваем. Я работаю над тем, чтобы разобраться, как это сделать, но я еще не совсем там. то есть настроить что-то, где вы можете определить такой запрос: var groupFilter = new FilterBuilder.Where (g => g.groupId == GroupEnum.ExampleGroup); а затем позже можно вызвать repository.GetGroups (groupFilter). В конечном итоге вы создаете все фильтры, которые хотите, и передавайте их в репо, а репо применяет их все и возвращает список вместо IQueryable. – Mir

ответ

5

Выявление IQueryable является очень жизнеспособным решением, и именно так большинство реализаций Репозитория там делают прямо сейчас. (В том числе и SharpArchitecture FubuMVC вно, а также.)

Здесь вы ошибаетесь:

Однако, если вы не используете IQueryable затем методы, как GETALL() не очень практично так как ленивый оценка не будет проходить вниз линия. Я не хочу возвращать 10 000 записей, чтобы использовать только 10 из них .

Это неправда. Ваш пример верен, и вы должны переименовать GetAll() в более информативное имя.

Он НЕ возвращает все предметы, если вы его вызываете. Для этого и предназначен IQueryable. Эта концепция называется «отложенная загрузка», поскольку она загружает только данные (и делает запросы к базе данных), когда вы перечисляете 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.

+5

Я думаю, что он тоже это понимает. То, что он говорит, заключается в том, что когда вы не используете 'IQueryable', но вместо этого используете' IEnumerable' для GetAll, тогда вы будете получите все, даже если вы просто используете 10. – Rodi

+0

-1 По объяснению @Rodi. – Ergwun

+0

@ Ergwun & & Rodi - видимо, вы не понимаете, как работает IQueryable. Извини за это. – Venemo

3

Метод Rob действительно не решает вашу основную проблему и не хочет писать отдельные методы для каждого типа запросов, которые вы хотите запустить, и, к сожалению, если вы не используете IQueryable, то это то, что вы остаются с.

Конечно методы могут быть в слое «сервис», но это все еще означает, писать «GetProductsByName, GetProductsByDate» ...

Другой способ что-то вроде:

GetProducts(QueryObject); 

Этом может дать вам некоторую выгоду от использования IQueryable, поскольку вы можете ограничить возвращаемое.

2

hmm .. Я решил это разными способами в зависимости от типа используемого мной ОРМ.
Основная идея состоит в том, чтобы иметь один базовый класс репозитория и один метод запроса, который принимает так много параметров, указывающих все возможные варианты где/orderby/expand | include/paging/etc.

Вот быстрый и грязный образец с использованием LINQ для NHibernate (конечно, все хранилища должны быть подробно реализация):

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

Обычно вы хотите добавить много СЦЕПЛЕНИЯ перегрузок как ярлыки, когда вы надеваете» t нужен пейджинг, например, и т. д.

Вот еще один грязный. Извините, я не уверен, могу ли я представить последние.Это были проекты и OK, чтобы показать:

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

В Конери в MVC Storefront он создал еще один слой под названием «Service» слоя, который получил IQueryable результатов от respository и был ответственности за применение различных фильтры.

Во всех случаях никто не должен взаимодействовать с репозиторием напрямую, кроме уровня услуг.

Самое гибкое заключается в том, чтобы позволить Услугам взаимодействовать с репозиторием любым способом, как они хотят, так же, как и в предыдущем коде (но через одну единственную точку - например, в том числе - написать DRY-код и найти место для оптимизации).
Однако более правильным способом с точки зрения общих шаблонов DDD является использование шаблона «Спецификация», в котором вы инкапсулируете все свои фильтры и т. Д. В переменные (члены класса в LINQ, как правило, типов делегатов). LINQ может извлечь большую выгоду из этого преимущества при объединении его с «Скомпилированными запросами». Если вы google {Specification Pattern} и {LINQ Compiled Queries}, вы приблизитесь к тому, что я имею в виду здесь.

+0

Здесь я использую IEnumerable, так что легко писать вызовы методов с помощью инициализатора массива, и это возможно (при составлении элементов динамически) использовать общий список <> или так. Возможно, когда-нибудь вы захотите также создать ярлык, который принимает предложение where where и single ordrby, et .. – Meligy

+0

Я собирался вести блог с более чистой реализацией этого репозитория. Может быть, этот пост делает это раньше :). Спасибо, что спросили об этом. – Meligy

+0

Итак, нижняя строка этого длинного примера: «Все в порядке, чтобы открыть IQueryable через репозитории, если доступ к репозиториям ограничен классами обслуживания. Правильно? – Galilyou

0

Я в конечном итоге создать два набора методов, те, которые возвращают IEnumerable (в вашем случае IQueryable), а также те, которые возвращают коллекции (тянуть содержимое перед отправкой его из хранилища.)

Это позволяет мне как создавать специальные запросы в службах за пределами репозитория, так и использовать методы репозитория, которые напрямую возвращают коллекции, устойчивые к побочным эффектам. Другими словами, объединение двух объектов репозитория вместе приводит к одному запросу выбора вместо одного запроса выбора для каждого найденного объекта.

Я предполагаю, что вы можете установить свой уровень защиты, чтобы по-настоящему плохо происходить.

+1

'IEnumerable' находится в коллекции памяти и очень сильно отличается от IQueryable. IEnumerable будет извлекать все непосредственно в память, и, таким образом, фильтрация, объединение и т. Д. Будет храниться в памяти со всеми данными. Другими словами: наличие 'Collection' рядом с' IEnumerable' не будет иметь существенного значения для этой цели. – Rodi

0

Попытавшись найти жизнеспособное решение этой проблемы самостоятельно, есть то, что кажется хорошим решением в статье 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(); 
     } 
    } 

В статье не говорится об этой точной проблеме, но речь идет о универсальных многоразовых методах репозитория.

До сих пор это все, что я смог придумать как решение.

 Смежные вопросы

  • Нет связанных вопросов^_^