Search code examples
c#repository-patternonion-architecture

Does a Generic Repository need a Base Entity class to be applied everywhere?


I am creating an Intranet website with ASP.NET MVC and Onion Architecture. I have been implementing the repository pattern but I have a difficulty.

Let's say I have a Document table with IDDocument in it. Then this is my repo(with just one method):

class Repository<T> : IRepository<T> where T : class
{
    private readonly PrincipalServerContext context;
    private DbSet<T> entities;
    //Constructor and stuff here
    public T Get(long id)
    {
        return entities.SingleOrDefault(s => s.IDDocument == id);//Here is my problem
    }
}

The problem is that I cannot use this since T is not recognized as being from Document table. Solution is to create a BaseEntity:

public class BaseEntity{
  public int ID{get;set;}
}

Then My Document POCO becomes:

public class Document : BaseEntity{ 
   //Properties here
}

And my Repo:

 class Repository<T> : IRepository<T> where T : BaseEntity
    {
        private readonly PrincipalServerContext context;
        private DbSet<T> entities;
        public T Get(long id)
        {
            return entities.SingleOrDefault(s => s.ID == id);//Here is my problem
        }
    }

But I don't want to do this ideally. What I like in the generic repo is that it allows me to not repeat the same code for all the different tables (I have 300+). But having a BaseEntity would also mean restructuring alot of what I have already done. Is it possible to have a Generic repo that you can apply on any POCO without this BaseEntity class?

Thanks for your help


Solution

  • You're calling the Queryable.SingleOrDefault method.

    Its second parameter has the type Expression<Func<T, bool>> so you can build expression manually, using as identifier property as you wish.

    Short example:

    public T Get(long id)
    {
        var idName = "ID" + typeof(T).Name; // For Document would be IDDocument
        var parameter = Expression.Parameter(id.GetType());
        var property = Expression.Property(parameter, idName)
        var idValue = Expression.Constant(id, id.GetType());
        var equal = Expression.Equal(property, idValue);
        var predicate = Expression.Lambda<Func<T, bool>>(equal, parameter);
        return entities.SingleOrDefault(predicate);
    }
    

    Imagine you wrote lambda function (T obj) => obj.IdProperty == id. Here obj is parameter, and idName should store "IdProperty" string. property means obj.IdProperty, idValue means the value if id. equal means obj.IdProperty == id, and predicate means whole expression (T obj) => obj.IdProperty == id.