简体   繁体   English

多层ASP.NET MVC应用程序中的实体框架上下文

[英]Entity Framework Context in a multi-tier ASP.NET MVC Application

I have created an application that uses multiple Visual Studio Projects for the layers of an ASP.NET MVC application. 我创建了一个将多个Visual Studio项目用于ASP.NET MVC应用程序层的应用程序。 I have got a Data Access Layer, Business Logic Laye and Presentation Layer. 我有一个数据访问层,业务逻辑层和表示层。

The solution Looks like this: 解决方案如下所示:

Visual Studio解决方案

I am using a Repository that look like this: 我正在使用如下所示的存储库:

public class RepositoryDal: IRepositoryDal
{
    private readonly DatabaseContext context;

    public RepositoryDal()
    {
        context = new DatabaseContext();
    }

    public T Add<T>(T entity) where T : Entity
    {
        return context.Set<T>().Add(entity);
    }

    public void Delete<T>(T entity) where T : Entity
    {
        context.Set<T>().Remove(entity);
    }

    public IQueryable<T> GetAll<T>() where T : Entity
    {
        return context.Set<T>();
    }

    public T GetById<T>(int id) where T : Entity
    {
        return context.Set<T>().FirstOrDefault(x => x.Id == id);
    }

    public bool HasChanges()
    {
        return context.ChangeTracker.HasChanges();
    }

    public void Save()
    {
        context.SaveChanges();
    }
}

I am calling the methods of this repository from my Business logic layer: 我从业务逻辑层调用此存储库的方法:

public class BookBll: IBookBll
{
    private readonly IRepositoryDal _repositoryDal;
    public BookBll()
    {
        _repositoryDal = new RepositoryDal();
    }

    public Book AddBook(Book book)
    {
        book = _repositoryDal.Add(book);
        _repositoryDal.Save();

        return book;
    }
    ...
}

This works. 这可行。 But if I do something like this it will not work correctly: 但是,如果我这样做,将无法正常工作:

public class ShelfBll: IShelfBll
{
    private readonly IRepositoryDal _repositoryDal;
    public ShelfBll()
    {
        _repositoryDal = new RepositoryDal();
    }

    public Shelf AddShelf(Shelf shelf)
    {
        shelf = _repositoryDal.Add(shelf);
        _repositoryDal.Save();

        return shelf;
    }
    ...
}

The problem is that I connect the book to a shelf and the shelf is retrieved via another Business logic layer class. 问题是我将书连接到书架上,并且书架是通过另一个业务逻辑层类检索的。 By doing this I am loosing the Entity Framework context. 通过这样做,我失去了实体框架的上下文。 What happens is that the application will not connect to the shelf, but it will create a new shelf. 发生的情况是该应用程序将不会连接到机架,但会创建一个新的机架。 That means after this I will have two shelves with the same name. 这意味着在此之后,我将有两个具有相同名称的架子。

How can I solve this problem? 我怎么解决这个问题?

Instead of obscuring separate DB Context objects behind each repository, expose a central object which itself is analogous to the DB Context and which spans all repositories. 而不是遮盖每个存储库后面的单独的数据库上下文对象,而是公开一个本身类似于数据库上下文并跨越所有存储库的中心对象。 Something like a "unit of work" object. 类似于“工作单元”对象。

I have a pretty old example here , but the patterns still hold. 这里一个很老的例子 ,但是模式仍然成立。

Essentially the goal is to allow consuming code to do something as simple as this: 本质上,目标是允许使用代码的代码执行以下操作:

using (var uow = new UnitOfWork())
{
    // all database interactions happen inside of here

    var book = new Book();
    // set a bunch of properties, etc.
    uow.BookRepository.Add(book);
    uow.ShelfRepository.Add(someShelf);
    // and so on...

    uow.Commit();
}

The idea is that the repositories themselves aren't the primary focus of the database interaction. 这个想法是,存储库本身并不是数据库交互的主要焦点。 They are properties of the unit of work, which itself is that focus. 它们是工作单元的属性,而工作单元本身就是焦点。 It might look something like this: 它可能看起来像这样:

public class UnitOfWork : DbContext, IUnitOfWork
{
    public DbSet<Book> DBBooks { get; set; }
    public DbSet<Shelf> DBShelves { get; set; }

    private IBookRepository _bookRepository;
    public IBookRepository BookRepository
    {
        get
        {
            if (_bookRepository == null)
                _bookRepository = new BookRepository(this);
            return _bookRepository;
        }
    }

    private IShelfRepository _shelfRepository;
    public IShelfRepository ShelfRepository
    {
        get
        {
            if (_shelfRepository == null)
                _shelfRepository = new ShelfRepository(this);
            return _shelfRepository;
        }
    }

    public UnitOfWork() : base("Name=MyDB") { }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // If you use fluent syntax mapping, initialize those here
        modelBuilder.Configurations.Add(new BookMap());
        modelBuilder.Configurations.Add(new ShelfMap());
    }

    public void Commit()
    {
        SaveChanges();
    }
}

And any given repository, much like the ones you have, is mostly just a pass-through to the DB Context: 而且任何给定的存储库(就像您拥有的存储库一样)大多只是传递给数据库上下文:

public class BookRepository : IBookRepository
{
    private UnitOfWork _unitOfWork;

    public IQueryable<Book> Books
    {
        get { return _unitOfWork.DBBooks; }
    }

    public BookRepository(UnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public void Add(Book model)
    {
        _unitOfWork.DBBooks.Add(model);
    }

    public void Remove(Book model)
    {
        _unitOfWork.DBBooks.Remove(model);
    }
}

(Note that my original code makes strict use of dependency injection so that the consuming code never actually sees these implementations, only the interfaces. So the public DBSet<> properties are still internal only to the DAL. You may need to tweak for your needs.) (请注意,我的原始代码严格使用了依赖项注入,因此使用代码永远不会真正看到这些实现,只能看到接口。因此,公共DBSet<>属性仍然仅在DAL内部。您可能需要进行调整以满足您的需求。)

The idea behind the unit of work in this design is that you have a single object which coordinates the actual database interactions, and the repositories hang off of that object. 此设计中的工作单元背后的思想是,您有一个对象来协调实际的数据库交互,并且存储库挂在该对象上。 This allows you have fully interact with the data exposed by an Entity Framework DB Context and just have a single save (or roll back) at the end. 这使您可以与Entity Framework DB Context公开的数据进行完全交互,并且最后只进行一次保存(或回滚)。 An added benefit to that, of course, being that you're not committing half-finished changes as your logic switches from one repository to another, the whole thing is wrapped in a kind of implicit transaction. 当然,这样做的另一个好处是,当您的逻辑从一个存储库切换到另一个存储库时,您不必提交半完成的更改,因此整个过程都封装在一种隐式事务中。

The problem here is that every instance of the bll objects for example BookBll and ShelfBLL have there own copy of the IRepositoryDal . 这里的问题是,bll对象的每个实例(例如BookBllShelfBLL都有自己的IRepositoryDal副本。 Which in turn means Each one has their own copy of DatabaseContext . 这又意味着每个人都有自己的DatabaseContext副本。 so each intance of the BLL has its own DBContext. 因此BLL的每个实例都有其自己的DBContext。 you need to have one Context or a unit of wor k. 您需要有一个Context或一个unit of wor Basically there are a few options available, but with minimum changes to your architecture. 基本上有几个选项可用,但对体系结构的更改最少。

your IrepositoryDal need to be IDisposable as 您的IrepositoryDal必须是IDisposable,因为

public interface IRepositoryDal: IDisposable
    {
        T Add<T>(T entity) where T : Entity;

        void Delete<T>(T entity) where T : Entity;

        IQueryable<T> GetAll<T>() where T : Entity;

        T GetById<T>(int id) where T : Entity;

        bool HasChanges();

        void Save();
    }

implementation of the above interface does not need to change much, further on you can Also take DbContext as a dependency in the contructor. 上面接口的实现不需要做太多更改,进一步,您还可以将DbContext作为构造函数中的依赖项。 but thats for later. 但是那是以后的事。 for now 目前

public class RepositoryDal : IRepositoryDal
    {
        private readonly DatabaseContext context;

        public RepositoryDal()
        {
            this.context = new DatabaseContext();
        }

        public T Add<T>(T entity) where T : Entity
        {
            return this.context.Set<T>().Add(entity);
        }

        public void Delete<T>(T entity) where T : Entity
        {
            this.context.Set<T>().Remove(entity);
        }

        public IQueryable<T> GetAll<T>() where T : Entity
        {
            return this.context.Set<T>();
        }

        public T GetById<T>(int id) where T : Entity
        {
            return this.context.Set<T>().FirstOrDefault(x => x.Id == id);
        }

        public bool HasChanges()
        {
            return this.context.ChangeTracker.HasChanges();
        }

        public void Save()
        {
            this.context.SaveChanges();
        }

        public void Dispose()
        {
            this.context.Dispose();
        }
    }

the Bll classes should take IRepositoryDal as a dependency like Bll类应将IRepositoryDal作为依赖项,例如

public class BookBll
    {
        private readonly IRepositoryDal _repositoryDal;
        public BookBll(IRepositoryDal dal)
        {
            this._repositoryDal = dal;
        }

        public Book AddBook(Book book)
        {
            book = this._repositoryDal.Add(book);
            this._repositoryDal.Save();

            return book;
        }

    }

public class ShelfBll
    {
        private readonly IRepositoryDal _repositoryDal;
        public ShelfBll(IRepositoryDal dal)
        {
            this._repositoryDal = dal;
        }

        public Shelf AddShelf(Shelf shelf)
        {
            shelf = this._repositoryDal.Add(shelf);
            this._repositoryDal.Save();

            return shelf;
        }

    }

And your executing code becomes 您执行的代码变成

class Program
    {
        static void Main(string[] args)
        {

            using (var repo = new RepositoryDal())
            {
                var shelfbll = new ShelfBll(repo);
                var bookBll = new BookBll(repo);
                var shelf = new Shelf { Location = "some location" };
                var book = new Book() { Name = "some book" };
                shelfbll.AddShelf(shelf);
                book.ShelfId = shelf.Id;
                bookBll.AddBook(book);
            }
        }
    }

Hope this helps Edit - your project structure is right, you have separated the interfaces and implementation. 希望这对Edit有所帮助-您的项目结构正确,接口和实现已分离。 so for the ServiceLayerInterfaces have multiple implementations one for EF one for may be something else in future. 因此,对于ServiceLayerInterfaces,它有多种实现方式,一种是针对EF,另一种可能是将来的实现。 you dont need generic repository. 您不需要通用存储库。 EF DBContext class already provides a Unit of Work etc Further Edit after last comment would this help. EF DBContext类已经提供了工作单位等, 在最后评论之后将有进一步的编辑帮助。

 public class ServiceFactory: IDisposable // hide this behind an interface
{
    private DatabaseContext _context;

    private IRepositoryDal _repository;

    public ServiceFactory()
    {
         _context = new DatabaseContext();
        _repository = new RepositoryDal(_context);
    }

    public IShelfBll ShelfService()
    {
        return new ShelfBll(_repository);
    }

    public void Dispose()
    {
        _repository.Dispose();
        _context.Dispose();

    }
}

and then in your calling code 然后在您的呼叫代码中

static void Main(string[] args)
        {

            using (var factory = new ServiceFactory())
            {
                var shelf = new Shelf { Location = "some location" };
                var book = new Book() { Name = "some book" };
                factory.ShelfService().AddShelf(shelf);
                book.ShelfId = shelf.Id;
                factory.BookService.AddBook(book);
            }
        }

You might need to tune it a little bit.... but its good to go off on 您可能需要对其进行一点调整。。。。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM