65.9K
CodeProject 正在变化。 阅读更多。
Home

RepositoryUnit:有用的通用存储库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (8投票s)

2010 年 3 月 27 日

CPOL

3分钟阅读

viewsIcon

46829

downloadIcon

1155

本文的目的是介绍一种基于 Entity Framework 的、有用的仓储模式的实现方法。

引言

本文的目的是介绍一种基于 Entity Framework 的、有用的仓储模式的实现方法。 如果您愿意,您可以轻松创建 RepositoryUnit 的 Linq2Sql 实现并编写单元测试。 也许,我将在后续文章中进行介绍。

背景

仓储模式提供对数据的统一访问,是数据源和数据使用者之间的中间层。 它允许从应用程序的业务逻辑中抽象数据源。 在实现这个概念时,我们可能会遇到一些问题

  • 应用程序通常使用对象(类),对于这些对象,在抽象接口中定义了 CRUD(创建、读取、更新、删除)操作。 因此,即使类很少,仓储的接口也将包含每个类的相同 CRUD 函数集。 我们当然可以为每个类编写单独的接口,并将它们放在一个通用仓储中,但这样我们会有一堆相似的文件,这根本不好。 C# 的泛型类型可以帮助我们避免这个问题。
  • 为了进行 CRUD 操作,我们应该能够识别对象。 通常,这个问题通过继承具有 ID 字段的类来解决,但这种情况下我们必须编辑 EF 生成的文件,这通常不会带来很多乐趣。

让我们结束这些困难,看看提议的解决方案的好处。

特点

  • 强类型。
  • 无需从基类继承的 CRUD 操作。 对象的 ID 通过反射获取。
  • 安全性 - 您无法直接获取 IQueryable。 仓储只返回列表及其派生。 如果您想返回 Queryable,您可以在继承类中执行此操作。
  • Lambda 表达式排序,同时在仓储实例中保存排序。 您只能在创建仓储时定义一次排序,然后忘记它。 您还可以设置默认过滤,这将定义您的仓储将使用的数据(例如,忽略 IsDeleted=true 的对象)。
  • CRUD 事件。 通常,使用事件比继承和重写方法更容易。
  • 将类的属性和方法封装在通用仓储的一个属性中。

IRepositoryUnit

它很简单,没什么好写的。 该接口只是做了上面写的事情。

public interface IRepositoryUnit<T, TId>
{
    T Create(T objectToCreate);
    void Delete(TId id);
    T Edit(T objectToUpdate);
    T Get(TId id);
    T Find(Expression<Func<T, bool>> predicate);

    IEnumerable<T> List();
    PaginatedList<T> List(int? page, int pageSize);

    PaginatedList<T> FindList(int? page, int pageSize, 
			Expression<Func<T, bool>> predicate);
    IEnumerable<T> FindList(Expression<Func<T, bool>> predicate);

    void OrderBy<TKey>(Expression<Func<T, TKey>> key);
    void ThenBy<TKey>(Expression<Func<T, TKey>> key);
    void OrderByDesc<TKey>(Expression<Func<T, TKey>> key);
    void ThenByDesc<TKey>(Expression<Func<T, TKey>> key);

    Expression<Func<T, bool>> WherePredicate { get; set; }

    event Action<T> Deleted;
    event Func<T, bool> Creating;
    event Func<T, T, bool> Editing;
}

EntityRepositoryUnit

由于 EF 的实现相当大,我们将只研究最有趣的时刻。

构造函数获取 ObjectContext,可选地,还有 includePaths。 第二个参数定义了应该包含在查询中的表。 必要的数据集的名称由方法 FetchEntitySetName 检测(感谢 Manuel Felicio),在对象创建例程中。

方法 Query 是最重要的。 每个请求处理(Get 除外)都以 Query 开头。 它准备查询,应用过滤和排序,并返回方便的 IQueryable 对象。

public class EntityRepositoryUnit<T, TId> : IRepositoryUnit<T, TId>
    where T : EntityObject
{
    protected ObjectContext ObjectContext { get; private set; }

    protected string EntitySetName { get; private set; }
    protected string[] IncludePaths { get; private set; }

    private Type orderByType;
    private Expression orderByExpression;
    private bool orderByDesc;

    private List<Type> thenByType;
    private List<Expression> thenByExpression;
    private List<bool> thenByDesc;

    public Expression<Func<T, bool>> WherePredicate { get; set; }

    ...

    public EntityRepositoryUnit(ObjectContext context, string[] includePaths)
    {
        this.ObjectContext = context;
        this.IncludePaths = includePaths;

        FetchEntitySetName();
    }

    //looks for an IQueryable<TEntity> property in the ObjectContext
    //and gets its name to be used in other methods
    private void FetchEntitySetName()
    {
        var entitySetProperty =
           this.ObjectContext.GetType().GetProperties()
               .Single(p => p.PropertyType.IsGenericType && typeof(IQueryable<>)
               .MakeGenericType(typeof(T)).IsAssignableFrom(p.PropertyType));

        this.EntitySetName = entitySetProperty.Name;
    }

    protected IQueryable<T> BuildQuery(ObjectQuery<T> q)
    {
        if (IncludePaths != null)
            foreach (string path in IncludePaths)
                q = q.Include(path);

        if (WherePredicate != null)
            return q.Where(WherePredicate);
        else
            return q;
    }

    protected IQueryable<T> ApplySorting(IQueryable<T> q)
    {
        if (orderByExpression != null)
        {
            var mc = Expression.Call(
                typeof(Queryable),
                orderByDesc ? "OrderByDescending" : "OrderBy",
                new Type[] { q.ElementType, orderByType },
                q.Expression,
                orderByExpression);

            for (int i = 0; i < thenByExpression.Count; i++)
            {
                mc = Expression.Call(
                    typeof(Queryable),
                    thenByDesc[i] ? "ThenByDescending" : "ThenBy",
                    new Type[] { q.ElementType, thenByType[i] },
                    mc,
                    thenByExpression[i]);
            }

            return q.Provider.CreateQuery(mc) as IQueryable<T>;
        }

        return q;
    }

    protected IQueryable<T> Query(Expression<Func<T, bool>> predicate)
    {
        var entitySet = String.Format("[{0}]", this.EntitySetName);
        ObjectQuery<T> baseQuery = this.ObjectContext.CreateQuery<T>(entitySet);

        var q = BuildQuery(baseQuery);

        if(predicate!=null)
            q = q.Where(predicate);

        q = ApplySorting(q);

        return q;
    }

    #region IRepositoryUnit<T,TId> Members

    ...

    public T Get(TId id)
    {
        string queryString = string.Format(@"SELECT VALUE x FROM 
				[{0}] as x WHERE x.Id = @Id", EntitySetName);

        var q = ObjectContext.CreateQuery<T>(queryString, new ObjectParameter("Id", id));

        return BuildQuery(q).FirstOrDefault();
    }

    ...

    public PaginatedList<T> FindList
	(int? page, int pageSize, Expression<Func<T, bool>> predicate)
    {
        return new PaginatedList<T>(Query(predicate), page, pageSize);
    }

     ...

    #endregion
}

PaginatedList

这是一个辅助类,它允许获取用于在指定页面上显示的数据。 您可以在下载的项目中查看其代码。

Using the Code

这非常容易。 您的项目通用仓储将包含属性 - 实现您需要的类型的接口 IRepositoryUnit 的对象。 只有在数据存储级别上运行复杂逻辑的非常复杂的方​​法才会被描述为您的通用仓储的方法。

例如

 public interface IRepository
{
    IRepositoryUnit<News, int> News { get; }

    IRepositoryUnit<Message, Guid> Messages { get; }

    IRepositoryUnit<Topic, Guid> Topics { get; }

    IRepositoryUnit<PhotoType, int> PhotoTypes { get; }

    IRepositoryUnit<Photo, Guid> Photos { get; }

    IEnumerable<Message> VeryIntricateMethod1();

    IEnumerable<Photo> VeryIntricateMethod1();
}

这样的仓储可以轻松地处理五个实体,并包含两个不宜在 RepositoryUnit 级别实现的复杂方法。

该接口的 EF 实现一定会让我们满意。 它简单而清晰

public class EntityRepository : IRepository
 {
     private YourEntities entities = new YourEntities();

     public EntityRepository()
     {
         News = new EntityRepositoryUnit<News, int>(entities);
         News.OrderByDesc(x => x.Date);

         PhotoTypes = new EntityRepositoryUnit<PhotoType, int>(entities);
         PhotoTypes.OrderBy(x => x.Name);

         Messages = new EntityRepositoryUnit<Message, Guid>(entities);
         Messages.WherePredicate = x => x.IsDeleted = false;
         Messages.OrderBy(x => x.Date);

         Topics = new EntityRepositoryUnit<Topic, Guid>
		(entities, new string[] { "Message" });
         Topics.WherePredicate = x => x.IsDeleted = false;
         Topics.OrderBy(x => x.Date);

         Photos = new EntityRepositoryUnit<Photo, 
		Guid>(entities, new string[] { "PhotoType" });
         Photos.OrderBy(x => x.PhotoType.Id);
         Photos.ThenBy(x => x.Date);

         Photos.Deleted += new Action<Photo>(s => s.DeleteFiles());
     }

     #region IRepository Members

     public IRepositoryUnit<News, int> News { get; private set; }

     public IRepositoryUnit<Message, Guid> Messages { get; private set; }

     public IRepositoryUnit<Topic, Guid> Topics { get; private set; }

     public IRepositoryUnit<PhotoType, int> PhotoTypes { get; private set; }

     public IRepositoryUnit<Photo, Guid> Photos { get; private set; }

     public IEnumerable<Message> VeryIntricateMethod1()
     {
         //your intricate logic here
         throw new NotImplementedException();
     }

     IEnumerable<Photo> IRepository.VeryIntricateMethod1()
     {
         //your intricate logic here
         throw new NotImplementedException();
     }
 }

请注意,为每个仓储定义排序 - 否则您将在 PaginatedList 构造函数中收到错误。 您还应该记住,每个 Entity 对象都必须具有 ID 属性(参见方法 EntityRepositoryUnit.Get)。 注意到照片文件在从仓储中删除 Photo 对象时是如何被漂亮地删除的。

我不会给出从业务逻辑中调用仓储函数的例子,因为它们已经足够明显了。 您将在下载的存档中找到一个简单的 MVC 示例。 如果您有任何问题,欢迎在下面的评论部分提问。

历史

  • 2010 年 3 月 26 日:初始版本
© . All rights reserved.