Pergunta

Então, eu li tudo o Q&A aqui sobre ISSO a respeito de se ou não para expor IQueryable para o resto do seu projeto ou não (ver aqui, e aqui), e eu finalmente decidi que eu não quero expor IQueryable para nada, mas o meu Modelo.Porque IQueryable é vinculada a determinadas persistência implementações eu não gosto da idéia de fecho-me para isso.Da mesma forma, eu não tenho certeza de como é bom o que eu sinto sobre classes mais para baixo na cadeia de chamada de modificar a consulta real que não estão no repositório.

Então, alguém tem alguma sugestão de como escrever uma limpa e concisa Repositório sem fazer isso?Um problema que eu vejo, é o meu Repositório vai explodir de uma tonelada de métodos de várias coisas que eu preciso para filtrar a minha consulta de fora.

Ter um monte de:

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

Se eu estava permitindo que IQueryable para ser passado ao redor eu poderia facilmente ter um repositório genérico que parecia:

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

No entanto, se você não estiver usando IQueryable, em seguida, métodos como Obtemtodos() não são realmente prático, uma vez que preguiça de avaliação não ter lugar para baixo da linha.Eu não quero voltar 10.000 registros apenas para uso 10 de los mais tarde.

Qual é a resposta aqui?No Conery do MVC Loja ele criou outra camada denominado o "Serviço" camada que recebeu IQueryable resultados da respository e foi responsável pela aplicação de vários filtros.

É isso o que eu deveria fazer, ou algo semelhante?Ter o meu repositório de retorno IQueryable mas restringir o acesso a ele por escondê-la atrás de um monte de filtro de classes como GetProductByName, que irá retornar um tipo concreto como IList ou IEnumerable?

Foi útil?

Solução

Expondo um IQueryable é uma solução muito viável e é assim que a maioria das implementações do repositório por aí está fazendo agora. (Incluindo Sharparchitecture e Fubumvc Contrib também.)

É aqui que você está errado:

No entanto, se você não estiver usando o iQueryable, os métodos como getall () não são realmente práticos, pois a avaliação preguiçosa não estará ocorrendo na linha. Não quero devolver 10.000 registros apenas para usar 10 deles mais tarde.

Isso não é realmente verdade. Seu exemplo está correto e você deve renomear getAll () para um nome mais informativo.

Ele não retorna todos os itens se você ligar. É para isso que serve a IQueryable. O conceito é chamado de "carregamento diferido", pois carrega apenas os dados (e faz solicitações de banco de dados) quando você enumerar o IQueryable.

Então, digamos que eu tenho um método como este:

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

Então, eu posso chamá -lo assim:

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

Isso recupera apenas uma linha do banco de dados.

E isto:

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

Isso também gera uma consulta correspondente e é executada apenas quando você a enumora. (Ele gera uma árvore de expressão a partir da consulta e, em seguida, o provedor de consultas deve traduzir isso em uma consulta apropriada contra a fonte de dados.)

Você pode ler mais sobre isso Neste artigo do MSDN.

Outras dicas

O método de Rob realmente não resolve seu problema principal, e isso não deseja escrever métodos individuais para cada tipo de consulta que você gostaria de executar e, infelizmente, se você não estiver usando o iQueryable, é isso que resta.

Claro que os métodos podem estar na camada de "serviço", mas ainda significa ter que escrever "getProductSbyName, getProductSbydate" ...

O outro método é algo como:

GetProducts(QueryObject);

Isso pode lhe dar algum benefício ao usar o iQueryable, pois você pode restringir o que é retornado.

hmm..Eu resolvi isso de muitas maneiras, dependendo do tipo de ORM eu uso.
A idéia principal é ter um repositório da base de dados de classe e um método de consulta que leva tantos parâmetros, indicando todos os possíveis onde/orderby/expandir|include/paginação/etc opções.

Aqui está uma rápida e suja de exemplo usando LINQ to NHibernate (, claro, a todo o repositório deve ser detalhe de implementação):

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

Normalmente você vai querer adicionar muitos encadeamento sobrecargas como atalhos quando você não precisa de paginação por exemplo, etc..

Aqui é outra suja.Desculpe, eu não tenho certeza se eu posso expor os finais.Aqueles eram rascunhos e estão OK para mostrar:

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

Em Conery do MVC Montra ele criou outra camada denominado o "Serviço" camada que recebeu IQueryable resultados da respository e foi responsável pela aplicação de vários os filtros.

Em todos os casos, ninguém deve interagir com o repositório diretamente, exceto a camada de serviços.

Mais flexível coisa é deixar de Serviços de interagir com o Repositório da maneira que eles querem, mesmo que no código acima (ainda que através de um único ponto, como no exemplo também para gravação a SECO do código e encontrar um lugar para otimização).
No entanto, o mais certo caminho em termos de comum DDD padrões é usar a "Especificação" padrão, onde você encapsular todos os seus filtros, etc., de Variáveis (os alunos, em LINQ, normalmente, de tipos de delegado).LINQ pode ter grandes otimização de benefício fora isso, quando você combiná-lo com "Compilado consultas".Se você procurar no google o {Especificação Padrão} e {LINQ Compilado Consultas} você vai chegar mais perto do que eu quero dizer aqui.

Acabei criando dois conjuntos de métodos, aqueles que retornam Ienumerable (no seu caso iQueryable) e aqueles que retornam (puxe o conteúdo antes de enviá -lo para fora do repositório.)

Isso me permite fazer ambas as consultas ad hoc em serviços fora do repositório, bem como usar métodos de repositório retornando diretamente as coleções resistentes ao efeito colateral. Em outras palavras, unir duas entidades repositivas resulta em uma consulta selecionada, em vez de uma consulta selecionada para cada entidade encontrada.

Eu imagino que você possa definir seu nível de proteção para impedir que coisas realmente ruins aconteçam.

Tendo lutado para encontrar uma solução viável para esse problema, há o que parece ser uma boa solução no Implementando o repositório e os padrões de trabalho em um aplicativo ASP.NET MVC (9 de 10) artigo.

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

O artigo não fala sobre esse problema exato, mas fala sobre métodos genéricos de repositórios reutilizáveis.

Até agora, é tudo o que pude encontrar como solução.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top