Jak mogę napisać czyste repozytorium bez wystawiania IQueryable na resztę mojej aplikacji?

Więc, przeczytałem wszystkie pytania i odpowiedzi tutaj NA so dotyczące tematu, czy ujawnić IQueryable do reszty projektu ,czy nie (Zobacz tutaj, i TUTAJ), i ostatecznie zdecydowałem, że nie chcę ujawnić IQueryable do niczego, ale mój Model. Ponieważ jestem związany z pewną uporczywością nie podoba mi się pomysł zamykania się w to. Podobnie, Nie jestem pewien, jak dobrze czuję się z klasami dalej w łańcuchu połączeń, modyfikując rzeczywiste zapytanie, którego nie ma w repozytorium.

Czy ktoś ma jakieś sugestie jak napisać czyste i zwięzłe repozytorium bez robienia tego? Jeden problem widzę, moje repozytorium wysadzi się z mnóstwem metod dla różnych rzeczy, które muszę odfiltrować moje zapytanie.

Mając kilka:

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

Gdybym pozwalał na przekazywanie IQueryable, to bez problemu mógłbym mieć ogólne repozytorium, które wyglądało tak:

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

Jednakże, jeśli nie używasz IQueryable to metody takie jak GetAll () nie są naprawdę praktyczne, ponieważ leniwa ocena nie będzie miała miejsca w dół linii. Nie chcę zwracać 10,000 płyt tylko po to, by wykorzystać 10 z nich później.

Jaka jest odpowiedź? W CONERY 's MVC Storefront stworzył kolejną warstwę o nazwie "Service" layer, która otrzymała IQueryable results od respository i była odpowiedzialna za stosowanie różnych filtrów.

To jest to, co powinienem zrobić, czy coś podobnego? Miej moje repozytorium zwraca IQueryable, ale ogranicza dostęp do niego, ukrywając go za kilkoma klasami filtrów, takimi jak GetProductByName, które zwrócą konkretny typ, taki jak IList lub IEnumerable?
Author: Community, 2009-06-23

5 answers

Ujawnienie an IQueryable jest bardzo realnym rozwiązaniem i tak robi obecnie większość implementacji repozytorium. (M.in. SharpArchitecture i fubumvc contrib.)

Tu się mylisz:

Jednakże, jeśli nie używasz IQueryable then methods like GetAll() nie są naprawdę praktyczne od leniwych ocena nie odbędzie się w dół linia. Nie chcę wracać. 10 000 rekordów tylko do wykorzystania 10 z nich później.

To nieprawda. Twój przykład jest poprawny i powinieneś zmienić nazwę GetAll () na bardziej informacyjną.

Nie zwraca wszystkich przedmiotów, jeśli go nazwiesz. Po to właśnie jestem. Pojęcie to nazywa się" deferred loading", ponieważ ładuje tylko Dane (i wysyła żądania do bazy danych), gdy wyliczysz IQueryable.

Powiedzmy, że mam taką metodę:

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

Wtedy mogę to nazwać tak:

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

To pobiera tylko jeden wiersz z bazy danych.

I to:

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

To również generuje odpowiednie zapytanie i jest wykonywane tylko wtedy, gdy je wyliczysz. (Generuje drzewo wyrażeń z zapytania, a następnie dostawca zapytania powinien przetłumaczyć je na odpowiednie zapytanie względem źródła danych.)

Możesz przeczytać więcej na ten temat w tym artykule MSDN .

 5
Author: Venemo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-19 13:12:10

Metoda Roba naprawdę nie rozwiązuje twojego podstawowego problemu, a to nie jest chęć pisania poszczególnych metod dla każdego typu zapytania, które chcesz uruchomić, i niestety, jeśli nie używasz IQueryable, to jest to, co Ci pozostało.

Oczywiście metody mogą znajdować się w warstwie" service", ale nadal oznacza to konieczność napisania"GetProductsByName, GetProductsByDate"...

Druga metoda to coś w stylu:

GetProducts(QueryObject);

To może dać ci jakąś korzyść z używania IQueryable w tym można ograniczyć to, co jest zwracane.

 3
Author: ChadT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-23 08:19:13

Hmm.. Rozwiązałem to na wiele sposobów w zależności od rodzaju ORM używam.
Główną ideą jest posiadanie jednej klasy bazowej repozytorium i jednej metody zapytań, która pobiera tak wiele parametrów wskazujących wszystkie możliwe gdzie / orderby|expand/include/paging / etc opcje.

Oto szybka i brudna próbka wykorzystująca LINQ do NHibernate (oczywiście całe repozytorium powinno być szczegółowo implementowane):

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

Zwykle będziesz chciał dodać wiele przeciążeń łańcucha jako skróty, gdy nie potrzebujesz stronicowanie np. itp..

Tu jest kolejny brudny. Przepraszam, nie jestem pewien, czy mogę ujawnić te ostatnie. To były szkice i są OK do pokazania:
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);
        }
    }
}

W sklepie CONERY ' ego MVC stworzył kolejna warstwa o nazwie " Serwis" warstwa, która otrzymała IQueryable wyniki z respository i był odpowiedzialny za stosowanie różnych filtry.

We wszystkich przypadkach nikt nie powinien wchodzić w interakcję bezpośrednio z repozytorium, z wyjątkiem usług warstwa.

Najbardziej elastyczną rzeczą jest umożliwienie usługom interakcji z repozytorium w dowolny sposób, tak samo jak w powyższym kodzie (jednak poprzez jeden punkt - jak w przykładzie również-napisać suchy kod i znaleźć miejsce do optymalizacji).
Jednak bardziej właściwym sposobem w odniesieniu do typowych wzorców DDD jest użycie wzorca" Specification", gdzie umieszczasz wszystkie swoje filtry itp. W zmiennych (członkowie klas, w LINQ typowych typów delegatów). LINQ może czerpać z tego duże korzyści z optymalizacji po połączeniu go z "skompilowanymi zapytaniami". Jeśli wygooglujesz {wzorzec specyfikacji} i {LINQ skompilowane zapytania} zbliżysz się do tego, co mam na myśli tutaj.

 2
Author: Meligy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-17 12:58:47

Skończyłem tworząc dwa zestawy metod, te, które zwracają IEnumerable (w Twoim przypadku IQueryable), i te, które zwracają Collection (wyciągnij zawartość przed wysłaniem jej z repozytorium.)

Pozwala mi to zarówno tworzyć zapytania ad hoc w usługach poza repozytorium, jak i używać metod repozytoriów zwracających bezpośrednio zbiory odporne na efekt uboczny. Innymi słowy, połączenie dwóch podmiotów repozytorium powoduje jedno zapytanie select, a nie jedno zapytanie select for każda znaleziona istota.

Wyobrażam sobie, że możesz ustawić swój poziom ochrony, aby zapobiec naprawdę złym rzeczom.

 0
Author: Zachary Scott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-31 18:20:26

Mając trudności ze znalezieniem realnego rozwiązania tego problemu, jest to, co wydaje się być dobrym rozwiązaniem w implementacji repozytorium i jednostki wzorców pracy w ASP.NET aplikacja MVC (9 z 10) artykuł.

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

Artykuł nie mówi dokładnie o tym problemie, ale mówi o generycznych metodach repozytoriów wielokrotnego użytku.

Do tej pory to wszystko, co udało mi się wymyślić jako rozwiązanie.
 0
Author: Joseph Woodward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-21 11:44:08