简体   繁体   中英

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. 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.)

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. 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 . Which in turn means Each one has their own copy of DatabaseContext . so each intance of the BLL has its own DBContext. you need to have one Context or a unit of wor k. Basically there are a few options available, but with minimum changes to your architecture.

your IrepositoryDal need to be IDisposable as

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. 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

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. so for the ServiceLayerInterfaces have multiple implementations one for EF one for may be something else in future. you dont need generic repository. EF DBContext class already provides a Unit of Work etc Further Edit after last comment would this help.

 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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