c# - 我刚刚使用通用存储库在 Entity Framework 之上实现了工作单位模式。我实际上完成了什么?

标签 c# entity-framework unit-testing repository-pattern unit-of-work

如果你不关心我写的代码,只想讨论一下抽象的抽象……跳到最后三段。
我是通过我正在观看的一个pluralsight课程被介绍到存储库和实体框架之上的工作单元的概念的。我还在微软自己的页面上详细介绍了这个过程: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();
    }

}

我仍然允许通过重载构造函数传入i simpleblogcontext,但默认构造函数是我们最终从中获取具体simpleblogcontext dbcontext的地方。
所以现在我只需要测试一下。因此,我编写了一个简单的控制台应用程序,它只会生成几个带有几个伪帖子的伪博客,并使用工作类单元保存它们。然后它循环博客和帖子并打印出来,这样我就可以验证它们是否真的保存到了数据库中。
P.S.杰克是我的狗,以防你想知道它的叫声…
    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这样的模拟框架,已经可以模拟dbset和dbcontext。我的存储库是通用的,因此实际上没有特定于业务逻辑的功能。工作类的单元只是dbcontext的包装器,通用存储库只是dbset的包装器。
有人能告诉我为什么有人会这样做吗?我看了大约4个小时的冥王星课程,加上自己经历了所有的麻烦,但我仍然没有得到它。

最佳答案

我最近也做了同样的工作,我相信您可以在不需要实际访问SQL数据库的情况下获得测试代码的灵活性。

关于c# - 我刚刚使用通用存储库在 Entity Framework 之上实现了工作单位模式。我实际上完成了什么? ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38274239/

相关文章:

c# - 如何强制派生类填充基类中定义的列表?

c# - 乐观并发 : IsConcurrencyToken and RowVersion

asp.net-mvc - Code First 中的一对多关系

django - 使用FileField测试Django模型

javascript - 什么相当于 sinonjs 中的 jasmine.createSpy().and.callFake(fn)

c# - 你如何为单元测试最小化一个类

c# - 在 c# 中使用 Rfc2898DeriveBytes 在 go 中使用 pbkdf2 生成相同的 key

xcode - NSURL 书签在 Xcode Server 上的单元测试中失败

c# - 具有并行任务时检测网页的最佳实践

php - 没有从 Symfony2 中的实体获取数据