我刚刚使用通用存储库在实体框架之上实现了工作单元模式.我到底完成了什么
本文关键字:单元 工作 模式 什么 存储 框架 实体 实现 | 更新日期: 2023-09-27 17:56:14
如果你不关心我写的代码,只想讨论一下抽象现有的抽象......跳到最后 3 段。
通过我正在观看的 Pluralsight 课程,我被介绍到实体框架之上的存储库和工作单元的概念。我还偶然发现了Microsoft自己的页面,详细介绍了这个过程:http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
所以我决定尝试一下。我开始在实体框架之上使用通用存储库编写自己的工作单元类,使沿途的所有内容都利用接口,以便我可以注入自己的模拟进行测试。
首先,对于这个练习,我选择制作一个简单的博客应用程序。
所以我从 DbContext 开始。必须确保我使用界面!
public interface ISimpleBlogContext : IDisposable
{
IDbSet<Blog> Blogs { get; }
IDbSet<Post> Posts { get; }
void SaveChanges();
IDbSet<T> Set<T>() where T : class;
DbEntityEntry Entry<T>(T entity) where T : class;
}
我相信每个人都知道 IDbSet 的用途,但 SaveChanges、Set 和 Entry 方法可能看起来有点不合适。别担心,我们稍后会谈到这些。
所以现在我将我的接口连接到一个实际的具体 DbContext 中:
public class SimpleBlogContext : DbContext, ISimpleBlogContext
{
public SimpleBlogContext() {
Database.SetInitializer<SimpleBlogContext>(new DropCreateDatabaseAlways<SimpleBlogContext>());
}
public IDbSet<Blog> Blogs { get; set; }
public IDbSet<Post> Posts { get; set; }
public DbEntityEntry Entry<T>(T entity) where T : class
{
return Entry<T>(entity);
}
void ISimpleBlogContext.SaveChanges()
{
SaveChanges();
}
IDbSet<T> ISimpleBlogContext.Set<T>()
{
return Set<T>();
}
数据库初始值设定项只是确保每次运行应用程序时都会删除并重新创建此测试应用程序的数据库。这只是一个练习,毕竟不是一个真正的应用程序。您可以看到此处实现的 SaveChanges、Set 和 Entry 方法,它们都只不过是同名 DbContext 方法的包装器。
所以现在进入存储库...
我不打算为可能添加到应用程序中的每个实体重写几乎相同的存储库代码(尽管在这种情况下,我最终只会使用一个存储库),因此我创建了一个通用存储库。不要跳过界面!
public interface IGenericRepository<T>
where T : class
{
IEnumerable<T> GetAll();
T GetById(object id);
IEnumerable<T> GetByExpression(Expression<Func<T, bool>> expression);
void Add(T entity);
void Delete(T entity);
void Update(T entity);
}
和具体版本...(请注意,我在这里使用的是ISimpleBlogContext而不是具体的DbContext类,因为我希望能够测试所有内容。另外,现在你知道为什么我必须在ISimpleBlogContext接口中编写这些Set,Entry和SaveChanges方法了)
public class GenericRepository<T> : IGenericRepository<T>
where T : class
{
public GenericRepository(ISimpleBlogContext context)
{
this.context = context;
}
private ISimpleBlogContext context;
public void Add(T entity)
{
context.Set<T>().Add(entity);
}
public void Delete(T entity)
{
context.Set<T>().Remove(entity);
}
public IEnumerable<T> GetAll()
{
return context.Set<T>().ToList<T>();
}
public IEnumerable<T> GetByExpression(Expression<Func<T, bool>> expression)
{
return context.Set<T>().Where<T>(expression).ToList<T>();
}
public T GetById(object id)
{
return context.Set<T>().Find(id);
}
public void Update(T entity)
{
context.Entry(entity).State = EntityState.Modified;
}
}
现在终于,工作单元类
public class UnitOfWork : IDisposable
{
public void Dispose()
{
if (context != null)
{
context.Dispose();
context = null;
}
}
public UnitOfWork()
{
context = new SimpleBlogContext();
}
public UnitOfWork(ISimpleBlogContext context)
{
this.context = context;
}
private ISimpleBlogContext context;
public GenericRepository<TEntity> GetRepository<TEntity>() where TEntity : class
{
return new GenericRepository<TEntity>(context);
}
public void Save()
{
context.SaveChanges();
}
}
我仍然允许通过重载的构造函数传入ISimpleBlogContext,但默认构造函数是我们最终从中获取具体SimpleBlogContext DbContext的地方。
所以现在我只需要测试这一切。所以我写了一个简单的控制台应用程序,它只不过是生成几个带有几个假帖子的假博客,并使用工作单元类保存它们。然后它循环博客和帖子并打印出来,以便我可以验证它们是否确实保存到数据库中。
附言杰克是我的狗,以防你想知道吠叫......
class Program
{
static void Main(string[] args)
{
UnitOfWork unitOfWork = new UnitOfWork();
GenericRepository<Blog> blogRepository = unitOfWork.GetRepository<Blog>();
Blog paulsBlog = new Blog()
{
Author = "Paul",
Posts = new List<Post>()
{
new Post()
{
Title = "My First Post",
Body = "This is my first post"
},
new Post()
{
Title = "My Second Post",
Body = "This is my second post"
}
}
};
Blog jakesBlog = new Blog()
{
Author = "Jake",
Posts = new List<Post>()
{
new Post()
{
Title = "Dog thoughts",
Body = "Bark bark bark"
},
new Post()
{
Title = "I like barking",
Body = "Bark bark bark"
}
}
};
blogRepository.Add(paulsBlog);
blogRepository.Add(jakesBlog);
unitOfWork.Save();
List<Blog> blogs = blogRepository.GetAll() as List<Blog>;
foreach (Blog blog in blogs)
{
System.Console.WriteLine("ID: {0}, Author: {1}'n", blog.Id, blog.Author);
if (blog.Posts != null && blog.Posts.Count > 0)
{
foreach (Post post in blog.Posts)
{
System.Console.WriteLine("Posted at: {0}, Title: {1}, Body: {2}", post.PostTime, post.Title, post.Body);
}
}
else
{
System.Console.WriteLine("No posts");
}
System.Console.WriteLine("'n");
}
}
}
它有效。耶!
然而,我的问题很简单...我通过做这些到底得到了什么?
DbContext 不是已经是一个工作单元,而 DbSet 已经是一个存储库吗?似乎我所做的只是为这两件事编写了一个非常复杂的包装器,根本没有添加任何功能。你可能会说它对测试更友好,因为一切都使用接口,但是使用像Moq这样的模拟框架,已经可以模拟DbSets和DbContexts。我的存储库是通用的,因此实际上没有特定于业务逻辑的功能。工作单元类只是 DbContext 的包装器,而通用存储库只是 DbSet 的包装器。
有人可以向我解释为什么有人会这样做吗?我看了~4个小时的Pluralsight课程,加上自己实际做的所有麻烦,我仍然不明白。
我最近做了同样的精简工作,我相信您可以灵活地测试代码,而无需实际访问 SQL 数据库。