كيف يمكنني كتابة نظيفة مستودع دون تعريض IQueryable إلى بقية طلبي ؟

StackOverflow https://stackoverflow.com/questions/1030992

سؤال

لقد قرأت جميع Q&A هنا حتى فيما يتعلق بموضوع ما إذا كان أو لا تعرض 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() ليست في الحقيقة العملية منذ كسول التقييم لن تجري أسفل الخط.أنا لا أريد العودة إلى سجلات 10000 فقط إلى استخدام 10 منهم في وقت لاحق.

ما هو الحل هنا ؟ في Conery MVC واجهة خلق طبقة جديدة تسمى "الخدمة" الطبقة التي تلقى IQueryable النتائج من respository وكان مسؤولا عن تطبيق مرشحات مختلفة.

هذا ما يجب أن أقوم به, أو شيئا من هذا القبيل ؟ يكون لي عودة مستودع IQueryable ولكن تقييد الوصول إلى ذلك من خلال الاختباء وراء حفنة من مرشح فئات مثل GetProductByName الذي سيعود ملموسة نوع مثل IList أو IEnumerable?

هل كانت مفيدة؟

المحلول

بفضح IQueryable هو حل عملي جدا و هذه هي الطريقة الأكثر من مستودع تطبيقات هناك من يفعل الآن.(بما في ذلك SharpArchitecture و FubuMVC contrib،.)

هذا هو المكان الذي كنت على خطأ:

ومع ذلك, إذا كنت لا تستخدم IQueryable ثم أساليب مثل GetAll() ليست العملية منذ كسول التقييم لن يكون أخذ المكان خط.أنا لا أريد العودة سجلات 10000 فقط إلى استخدام 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);

هذا أيضا يولد المقابلة الاستعلام فقط أعدم عند تعداد ذلك.(فإنه يولد شجرة التعبير من الاستعلام ثم الاستعلام مزود يجب ترجمة ذلك إلى مناسبة استعلام مصدر البيانات.)

يمكنك قراءة المزيد حول هذا الموضوع في هذه المقالة MSDN.

نصائح أخرى

وطريقة روب حقا لا يحل المشكلة الأساسية الخاصة بك، وأنه لا يريد أن يكتب الأساليب الفردية لكل نوع من الاستعلام كنت ترغب في تشغيل، وللأسف إذا كنت لا تستخدم IQueryable ثم وهذا هو ما كنت غادر معها.

وبالتأكيد قد يكون الأساليب في طبقة "الخدمة"، لكنه لا يزال يعني الحاجة إلى كتابة "GetProductsByName، GetProductsByDate" ...

والأسلوب الآخر هو شيء من هذا القبيل:

GetProducts(QueryObject);

وهذا قد تعطيك بعض الفوائد على استخدام IQueryable في ذلك يمكنك تقييد ما يتم إرجاعها.

وهم .. أنا تحل هذه بطرق عديدة تبعا لنوع من ORM يمكنني استخدام.
الفكرة الرئيسية هي أن يكون مستودع واحد الفئة الأساسية وطريقة الاستعلام واحد أن يأخذ ذلك الكثير من العوامل تشير إلى كل ما يمكن حيث / orderby / توسيع | تشمل / الترحيل / الخ الخيارات.

وهنا عينة سريعة وقذرة باستخدام 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();
        }
    }

وعادة سترغب في إضافة العديد من الزائدة تسلسل اختصارات عندما لا تحتاج الترحيل على سبيل المثال، الخ ..

وهنا هو واحد القذرة آخر. عذرا لست متأكدا ما اذا كان يمكنني كشف نهائية. كانت تلك المسودات وهي موافق لعرض:

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 واجهة المخزن Conery في خلقه   طبقة أخرى تسمى "الخدمة"   الطبقة التي تلقت IQueryable   النتائج من respository وكان   المسؤولة عن تطبيق مختلف   المرشحات.

في جميع الأحوال لا أحد يجب أن يتفاعل مع مستودع مباشرة باستثناء طبقة الخدمات.

والشيء الأكثر مرونة هو السماح خدمات تتفاعل مع مستودع بأي طريقة يريدون، نفسها كما في التعليمات البرمجية أعلاه (بعد من خلال واحدة -كما نقطة واحدة في المثال also- لكتابة رمز جاف ويجد لنفسه مكانا لالأمثل).
ومع ذلك، فإن الطريقة الصحيحة أكثر من حيث أنماط DDD شيوعا هو استخدام "مواصفات" النمط، حيث يمكنك تغليف كل ما تبذلونه من المرشحات، وغيرها في المتغيرات (الدرجة الأعضاء، في LINQ عادة من أنواع مندوب). LINQ يمكن استفادة الأمثل كبيرة للخروج من هذا عند دمجها مع "استعلامات جمعت". إذا كنت جوجل لل{مواصفات نمط} و {LINQ جمعت استعلامات} ستحصل على أقرب إلى ما أعنيه هنا.

وانتهى بي المطاف خلق مجموعتين من الأساليب، تلك التي تعود IEnumerable (في قضيتك IQueryable)، وتلك التي تعود مجموعة (سحب المحتوى قبل إرساله للخروج من مستودع).

وهذا يتيح لي أن تفعل كل بناء الاستفسارات المخصصة في الخدمات خارج المستودع وكذلك استخدام أساليب مستودع عودته مباشرة الآثار الجانبية مجموعات المقاومة. وبعبارة أخرى، والانضمام إلى كيانين مستودع معا النتائج في استعلام تحديد واحد، بدلا من استعلام تحديد واحد لكل كيان وجد.

وأتصور يمكنك تعيين مستوى الحماية الخاص للحفاظ على أشياء سيئة حقا من الحدوث.

وبعد أن كافح لإيجاد حل ناجع لهذه المشكلة نفسي، هناك ما يبدو أن يكون حلا جيدا في في تنفيذ مستودع وحدة من أنماط العمل في ASP.NET MVC التطبيق (9 من 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();
        }
    }

وهذه المادة لا تتحدث عن هذه المسألة بالضبط ولكن لا يتحدثون عن طرق مستودع عامة، قابلة لإعادة الاستخدام.

وحتى الآن هذا هو كل ما كنا قادرين على الخروج مع كحل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top