0

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 Solution

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?

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

8 Comments

Im not even sure that it is viable to make another abstractions like UoW and Repository over EF DbContext... DbContext itself is repository (DbSets) and UoW (IDisposable)
@Szer: It certainly is viable, I've done it many times. This example illustrates it, albeit somewhat simplified to fit into a general Stack Overflow answer.
me too, but it is abstraction over abstraction. It may be helpful only in situations when you switch to another ORM in existing project (I haven't done it yet). And looks like OP won't do it either.
@Szer: That abstraction appears to be the goal of the original question, no? Currently EF is obscured behind the DAL interfaces, this just re-arranges those interfaces to centralize a single DB Context. There's nothing inherently wrong with "an abstraction over an abstraction" in any case, if the latter is classified in the domain as a dependency. EF classifies the database as a dependency and abstracts it, but if the domain classifies EF as a dependency then it too should be abstracted.
See this explanation. It really isn't necessary to have a repository over entity framework. programmers.stackexchange.com/a/220126/196931
|
1

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

7 Comments

This looks good. But I still have two problems with this: - I am now directly accessing the data access layer via the presentation (I could work around this by creating a version of the RepositoryDal in the business logic layer (e.g. RepositoryBll and then access the repositoryDal via this)) - I would like to be able to inject the classes via constructor injection. If I do this I am not able to use the using statement as the 'instantiation' of the classes happens in the constructor. Do you have any ideas how I could do this?
First of all, as you are using entity framework, you will have to have a reference of it in your top level project. on another note. if your service methods are just one to one maps to your repository methods, its no point in creating a service layer its will be just an added complexity. Can i Ask what is the goal behind creating a Datalayer and then a service layer. I reckon you just need the Service Layer and then different implementations of it like a EF implementation, Dapper implementation or even a ADO.net implementation. you do not need a generic repository.
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
My Business Logic Layer contains additional stuff like for example setting the last change date or last change user when changing an Entity or checking if the user is allowed to change an entity. I was first having a data layer that had a one to one mapping to my business layer methods (e.g. AddBook would be in the Business layer and add certain data to the book and then call AddBook in the data layer). I then changed the data layer to a generic repository so that I will not have to write so much code. I would like to be able to use the Business layer in other applications (e.g. Services).
Ok, understood. I still think that you do not need a generic repository, because you are duplicating a lot of Functionality, All the reasons you have give for example "setting the last change date or last change user when changing an Entity or checking if the user is allowed to change an entity" are still data changes. Generic Repository is if you want to hide EF behind an abstraction so that its easily swapable for other ORMs, but even that is not so easy. From my experience of generic repository only fits very generic situations, soon you doing any complicated queries, its no good
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.