Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Repository / Unit of Work Issue

I have been learning the Repository and Unit of Work patterns from various sources, including here:

http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

If you browse to thr link above to Creating a Unit of Work Class there is the following:

    private GenericRepository<Department> departmentRepository;
    private GenericRepository<Course> courseRepository;

The which is fine, but I wanted to have a go at extending the Unit of Work class that Generic and setup a collection of GenericRepositories so i can update then dynamnically based on the model I pass through.

I ultimately want to do the following in my Controller:

public class LedgerUserController : Controller
{
    private GenericUnitOfWork unitOfWork = new GenericUnitOfWork();
    LedgerUser ledgeruser = new LedgerUser();


    public ActionResult Index()
    {
        //var ledgerusers = db.LedgerUsers.Include(l => l.Image).Include(l => l.UserType);
        var view = unitOfWork.Repository(ledgeruser).Get(l => l.LastName == "smith");
        return View(view.ToList());
    }
}

So here is my classes and interfaces so far:

IRepository.cs

/// <summary>
/// Generic Repository for CRUD Operations and methods
/// to locate entities within your store. This is not specific to which Data Access
/// tools your are using (Direct SQL, EF, NHibernate, etc).
/// </summary>
public interface IRepository<T> where T : class
{
    //--Search Operations
    IQueryable<T> GetAll();
    IEnumerable<T> GetAllList();
    IEnumerable<T> Get(Expression<Func<T,bool>> filter);
    T GetIt(Expression<Func<T, bool>> filter);
    T GetById(object id);


    //--CRUD Operations
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);

}

GenericRepository.cs

/// /// Repository Class for looking afer Entities /// CRUD Operations /// /// public class GenericRepository : IRepository where TEntity : class {

    internal AccountsContext context;
    internal DbSet<TEntity> dbSet;
    internal IQueryable<TEntity> query;

    /// <summary>
    /// Default Constructor.
    /// </summary>
    /// <param name="context"></param>
    public GenericRepository(AccountsContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    #region Methods
    #region Search Functionality
    /// <summary>
    /// Obtain the whole Entity to query if needed.
    /// </summary>
    /// <returns>IQueryable object.</returns>
    public virtual IQueryable<TEntity> GetAll()
    {
        IQueryable<TEntity> query = dbSet;
        return query;

    }

    /// <summary>
    /// Obtain the whole Entity to Enumerate throught if needed.
    /// </summary>
    /// <returns>IEnumerble object.</returns>
    public virtual IEnumerable<TEntity> GetAllList()
    {
        IQueryable<TEntity> query = dbSet;
        return query.ToList();

    }

    /// <summary>
    /// Locate an Entity by its indexed id.
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual TEntity GetById(object id)
    {
        return dbSet.Find(id);
    }

    /// <summary>
    /// Gets a collection based on LINQ lambda expressions
    /// </summary>
    /// <param name="filter">Lambda Expression</param>
    /// <returns>Query</returns>
    public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter)
    {
        query = dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        return this.query.ToList();
    }

    /// <summary>
    /// Gets one record based on a one-to-one relationship.
    /// </summary>
    /// <param name="filter">Lambda Expression.</param>
    /// <returns>One record.</returns>
    public virtual TEntity GetIt(Expression<Func<TEntity, bool>> filter)
    {
        IQueryable<TEntity> query = dbSet;
        return query.SingleOrDefault(filter);

    }


    #endregion
    #region CRUD Functionality

    /// <summary>
    /// Used to create a new entity into the database.
    /// </summary>
    /// <param name="entity">Entity to create.</param>
    public virtual void Create(TEntity entity)
    {
        dbSet.Add(entity);
    }

    /// <summary>
    /// Used to update an entity that already exists in the
    /// database.
    /// </summary>
    /// <param name="entity">Entity to update.</param>
    public virtual void Update(TEntity entity)
    {
        dbSet.Attach(entity);
        context.Entry(entity).State = EntityState.Modified;
    }

    /// <summary>
    /// Used to delete an entity from the database.
    /// </summary>
    /// <param name="entity">Entity to delete.</param>
    public virtual void Delete(TEntity entity)
    {
        if (context.Entry(entity).State == EntityState.Detached)
        {
            dbSet.Attach(entity);
        }
        dbSet.Remove(entity);
    }

    #endregion
    #endregion

}
#endregion

GenericUnitOfWork.cs:

  /// <summary>
/// Unit of work class that handles multiple Repositories
/// and shares the context.
/// </summary>
public class GenericUnitOfWork : IUnitOfWork

{
    private AccountsContext context = new AccountsContext();

    Dictionary<string, GenericRepository<IRepository<IRepositoryEntity>>> repostories = null;

    /// <summary>
    /// Generic Repository method which checks the repository is available if not,
    /// it sets it up.
    /// </summary>
    /// <param name="entity">Entity</param>
    /// <returns>Repository to use.</returns>
    public  GenericRepository<IRepository<IRepositoryEntity>> Repository (IRepositoryEntity entity)
    {

            string index = entity.GetType().ToString();

            if (!repostories.ContainsKey(index))
            {

                //Reflections to create the repoositiory if it is not needed.
                Type type1 = typeof(GenericRepository<>);
                Type[] typeArgs = {typeof(IRepositoryEntity)};

                Type constructed = type1.MakeGenericType(typeArgs);
                object o = Activator.CreateInstance(constructed);

                if(o is  GenericRepository<IRepository<IRepositoryEntity>>)
                {
                    var rep = (GenericRepository<IRepository<IRepositoryEntity>>)o;
                    rep.context = this.context;
                    repostories.Add(index, rep);  
                }

            }

            return this.repostories[index];
    }

    /// <summary>
    /// Save method.
    /// </summary>
    public void Save()
    {
        context.SaveChanges();

    }

    private bool disposed = false;

    /// <summary>
    /// Dispose the conxtext when finished.
    /// </summary>
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

}

}

Now I know the implementation of repositories Dictionary and the method Repository are incorrect (Generic Types are wrong) because in my lambda expression it cannot resolve the ledgeruser LastName.

var view = unitOfWork.Repository(ledgeruser).Get(l => l.LastName == "smith");

Am I over engineering the issue, or is there a good clean way to create Generic Unit of Work with my collection of Generic Repositories, which are set and created based on the model object give (LedgerUser for e.g. above).

like image 800
garfbradaz Avatar asked Sep 25 '12 19:09

garfbradaz


People also ask

What is repository and unit of work?

The repository and unit of work patterns are intended to create an abstraction layer between the data access layer and the business logic layer of an application.

What is generic repository?

It is a data access pattern that prompts a more loosely coupled approach to data access. We create a generic repository, which queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source.

What is the work of repository?

A repository is nothing but a class defined for an entity, with all the operations possible on that specific entity. For example, a repository for an entity Customer, will have basic CRUD operations and any other possible operations related to it.

What problem does repository pattern solve?

In essence, the Repository design pattern facilitates de-coupling of the business logic and the data access layers in your application with the former not having to have any knowledge on how data persistence would actually take place.


2 Answers

A few ideas :

  1. Instead of creating everything with reflection in your UnitOfWork implementation, you could use an IoC container and automate the creation of repositories that way. The MVC framework is actually built for this kind of approach. A simple explanation and example of using StructureMap for this purpose is available at http://www.mikesdotnetting.com/Article/117/Dependency-Injection-and-Inversion-of-Control-with-ASP.NET-MVC .

  2. If you don't want to use a full-blown IoC container, you could still standardize your factory logic and code against the interface. This plays the same kind of role, but may work better if you want to keep using a custom unit of work implementation. In my answer to C#/EF and the Repository Pattern: Where to put the ObjectContext in a solution with multiple repositories?, I posted a RepositoryProvider class that allows the creation of UnitOfWork-scoped repositories with customizeable factories. I would recommend at least looking over this code, as it is a similar, but more effective way to do what your code example aims at. One important thing to understand is that the example in that answer uses an ObjectContext as a UnitOfWork, so your change would need to account for this discrepancy by replacing occurrences of ObjectContext with an occurrence of IUnitOfWork. If anything about that approach is unclear after reviewing the code, let me know and I will try to explain how to adapt your specific use case.

  3. It seems like there is something circular about your factory logic. If the repository creates LedgerUsers, then it shouldn't require a LedgerUser to create the factory. It seems to me like what you really want is a Type parameter like unitOfWork.Repository(typeof(LedgerUser)). You could make this more fluent by creating a generic type parameter over load and do unitOfWork.Repository<LedgerUser>()`. Based on your example, there doesn't seem to be any reason at all that you would need an instance.

  4. It all seems like you would prefer strong-typing to an interface in your Repository method. I think maybe what you are going for is more like:


    public IRepository Repository()
        where T : IRepositoryEntity 
    { 
           //  your factory/cache-retrieval logic here
    }

instead of

public  GenericRepository<IRepository<IRepositoryEntity>> Repository (IRepositoryEntity entity)
{
      //  your factory/cache-retrieval logic here
}

Then, if your call was to Repository<LedgerUser>, your method would return a GenericRepository<LedgerUser>, even though the signature says IRepository<LedgerUser>. This is pretty much how the link to the RepositoryProvider implementation that I suggested works.

like image 189
smartcaveman Avatar answered Sep 28 '22 03:09

smartcaveman


i do not see why you want to pass an instance to the object instead of the type of the object, and hold repositories by type:

try this

ConcurrentDictionary<Type, object> _repositories = ...;

public GenericRepository<IRepository<TEntity>> Repository<TEntity>(IRepositoryEntity entity) where TEntity: IRepositoryEntity
{
    return (GenericRepository<IRepository<TEntity>>)_repositories.GetOrAdd(
        typeof(TEntity), 
        t => new GenericRepository<IRepository<TEntity>>(this.Context)
    );
}
like image 30
esskar Avatar answered Sep 28 '22 04:09

esskar