Frage

Also habe ich alle Fragen und Antworten hier in Bezug auf das Thema gelesen, ob ich IQueryable für den Rest Ihres Projekts offenbaren soll oder nicht (siehe hier, und hier), und ich habe letztendlich entschieden, dass ich IQueryable für nichts anderes als mein Modell aussetzen möchte. Weil iQueryable an bestimmte Persistenz -Implementierungen gebunden ist, mag ich die Idee, mich in das zu sperren, nicht. In ähnlicher Weise bin ich mir nicht sicher, wie gut ich über Klassen weiter in der Call -Kette fühle, die die tatsächliche Abfrage ändern, die nicht im Repository sind.

Hat jemand Vorschläge, wie man ein sauberes und prägnantes Repository schreibt, ohne dies zu tun? Ein Problem, das ich sehe, ist, dass mein Repository von einer Menge Methoden für verschiedene Dinge in die Luft jagen wird, von denen ich meine Abfrage filtern muss.

Ein paar::

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

Wenn ich zulassen würde, dass IQueryable weitergegeben werden kann, könnte ich leicht ein generisches Repository haben, das aussah:

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

Wenn Sie jedoch nicht iQueryable verwenden, sind Methoden wie Getall () nicht wirklich praktisch, da die faule Bewertung nicht auf der ganzen Linie stattfindet. Ich möchte nicht 10.000 Datensätze zurückgeben, um später 10 davon zu verwenden.

Was ist die Antwort hier? Im MVC -Storefront von CONERY Er erstellte eine andere Ebene namens "Service" -Schicht, die iQueryable -Ergebnisse aus dem Respository erhielt und für die Anwendung verschiedener Filter verantwortlich war.

Soll ich das tun oder ähnliches? Hat mein Repository zurückgegeben, aber einschränken Sie den Zugriff darauf, indem Sie es hinter einer Reihe von Filterklassen wie GetProductByName versteckt, die einen konkreten Typ wie Ilist oder Ienumerable zurückgeben?

War es hilfreich?

Lösung

Auf aussetzen IQueryable ist eine sehr praktikable Lösung und so tun die meisten Repository -Implementierungen derzeit. (Einschließlich Sharparchitecture und Fubumvc -Beitrag.)

Hier liegen Sie falsch:

Wenn Sie jedoch nicht iQueryable verwenden, sind Methoden wie Getall () nicht wirklich praktisch, da die faule Bewertung nicht auf der ganzen Linie stattfindet. Ich möchte nicht 10.000 Datensätze zurückgeben, um später 10 davon zu verwenden.

Dies ist nicht wirklich wahr. Ihr Beispiel ist korrekt und Sie sollten getall () in einen informativeren Namen umbenennen.

Es gibt nicht alle Artikel zurück, wenn Sie es nennen. Dafür ist IQueringable. Das Konzept wird als "aufgeschobenes Laden" bezeichnet, da es nur die Daten lädt (und Datenbankanfragen stellt), wenn Sie das aufzählen IQueryable.

Nehmen wir also an, ich habe eine Methode wie diese:

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

Dann kann ich es so nennen:

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

Dadurch wird nur eine Zeile aus der Datenbank abgerufen.

Und das:

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

Dies erzeugt auch eine entsprechende Abfrage und wird nur ausgeführt, wenn Sie sie aufgezählt. (Es generiert einen Ausdrucksbaum aus der Abfrage, und dann sollte der Abfrageanbieter dies in eine geeignete Abfrage gegen die Datenquelle übersetzen.)

Sie können mehr darüber lesen In diesem MSDN -Artikel.

Andere Tipps

Robs Methode löst Ihr Kernproblem wirklich nicht, und das möchte nicht einzelne Methoden für jede Art von Abfrage schreiben, die Sie ausführen möchten, und wenn Sie nicht iQueryable verwenden, sind Sie leider noch, mit dem Sie übrig bleiben.

Sicher, die Methoden könnten sich in der "Service" -Schicht befinden, aber es bedeutet immer noch, "GetProductsByName, GetProductsByDate" zu schreiben ...

Die andere Methode ist so etwas wie:

GetProducts(QueryObject);

Dies kann Ihnen einen gewissen Nutzen über die Verwendung von iQueryable geben, da Sie das, was zurückgegeben wird, einschränken können.

Hmm .. Ich habe dies je nach Art der Orm, die ich verwende, in vielerlei Hinsicht gelöst.
Die Hauptidee besteht darin, eine Repository -Basisklasse und eine Abfragemethode zu haben, bei der so viele Parameter angeben, wobei alle Möglichkeiten angeben, wo/orderBy/erweitert | Include/Paging/ETC -Optionen.

Hier ist ein schnelles und schmutziges Beispiel mit LINQ zu NHiberNate (natürlich sollte das gesamte Repository Implementierungsdetail sein):

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

Normalerweise möchten Sie viele Kettenüberladungen als Verknüpfungen hinzufügen, wenn Sie beispielsweise keine Paging benötigen usw.

Hier ist ein anderer schmutziger. Entschuldigung, ich bin mir nicht sicher, ob ich die letzten entlarven kann. Das waren Entwürfe und sind in Ordnung zu zeigen:

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

In Conerys MVC StoreFront erstellte er eine andere Ebene namens "Service", die iQueryable -Ergebnisse aus dem Respository erhielt und für die Anwendung verschiedener Filter verantwortlich war.

In allen Fällen sollte niemand direkt mit dem Repository mit Ausnahme der Diensteschicht interagieren.

Am flexibelsten ist es, Dienste mit dem Repository interagieren zu lassen, was auch immer sie wollen, wie im obigen Code (dennoch über einen einzelnen Punkt - auch in Beispiel -, um Trockencode zu schreiben und einen Ort für die Optimierung zu finden).
Der richtigere Weg in Bezug auf gemeinsame DDD -Muster ist jedoch die Verwendung des "Spezifikationsmusters", in dem Sie alle Ihre Filter usw. in Variablen zusammenfassen (Klassenmitglieder, in LINQ typischerweise von Delegiertentypen). LINQ kann davon einen großen Optimierungsvorteil nutzen, wenn Sie es mit "kompilierten Abfragen" kombinieren. Wenn Sie das {Spezifikationsmuster} und {linq kompilierte Abfragen} googeln} Sie nähern sich dem, was ich hier meine.

Am Ende habe ich zwei Methoden erstellt, die IEnumerable (in Ihrem Fall iQueryable) zurückgeben, und solche, die die Sammlung zurückgeben (ziehen Sie den Inhalt, bevor Sie ihn aus dem Repository senden).)

Auf diese Weise kann ich beide Ad -hoc -Abfragen in Diensten außerhalb des Repositorys erstellen sowie Repository -Methoden verwenden, die direkt zurückgegebene Sammlungen für Nebeneffekte zurückgeben. Mit anderen Worten, die gemeinsame Abfrage mit zwei Repository -Entitäten zusammenzuführen, führt dies zu einer Auswahlabfrage anstelle einer ausgewählten Abfrage für jede gefundene Entität.

Ich kann mir vorstellen, dass Sie Ihr Schutzniveau festlegen könnten, um wirklich schlechte Dinge abzuhalten.

Nachdem ich mich bemüht habe, selbst eine praktikable Lösung für dieses Problem zu finden, scheint es eine gute Lösung in der Implementierung des Repositorys und der Einheit von Arbeitsmustern in einer ASP.NET -MVC -Anwendung (9 von 10) Artikel.

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

Der Artikel spricht nicht über genaues Problem, sondern über generische, wiederverwendbare Repository -Methoden.

Bisher ist dies alles, was ich als Lösung finden konnte.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top