"Include" lambda in generic repository - c#

I follow repository pattern and I have the following method in my generic repository class:
public virtual T Get(Expression<Func<T, bool>> where)
{
return dbset.Where(where).FirstOrDefault<T>();
}
I would like to add the a lambda expression for including navigation properties. Is it possible?

Here's one way you could do it
public virtual IQueryable<T> Include(this IQueryable<T> qry, params string[] includes)
{
foreach(var inc in includes)
qry = qry.Include(inc);
return qry;
}
Example usage:
var list = context.Products.Where(x => x.ProductID == 1).Include("Items", "Person").Single();

There's my generic repo Get method:
public virtual IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> Query = DbSet;
if (filter != null)
{
Query = Query.Where(filter);
}
if (!string.IsNullOrEmpty(includeProperties))
{
foreach (string IncludeProperty in includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
{
Query = Query.Include(IncludeProperty);
}
}
if (orderBy != null)
{
return orderBy(Query);
}
return Query;
}

Related

How to write unit test case for generic repository pattern in .net core

Hi I am trying to write nunit test case for generic repository pattern methods.
Below is my method in dbrepository.
public class DbRepository<T> : BaseRepository, IDbRepository<T>
where T : class
{
private readonly DbContext _dbContext;
private readonly DbSet<T> dbSet;
public DbRepository(DbContext dbContext)
{
_dbContext = dbContext;
dbSet = dbContext?.Set<T>();
}
public async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = this.dbSet;
foreach (Expression<Func<T, object>> include in includes)
{
query = query.Include(include);
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
return await query.ToListAsync().ConfigureAwait(false);
}
}
Then I planned to use FakeDbSet as below. I have little confusion here. I found one example in github https://github.com/tugberkugurlu/GenericRepository/blob/master/tests/GenericRepository.EntityFramework.Test/Infrastrucure/FakeDbSet.cs here they have some methods. In fakedbset do we need to write exact same methods there in my dbrepositry? Below is my fakedbset class
public class FakeDbSet<T> : DbSet<T> where T : class
{
ObservableCollection<T> _data;
IQueryable _query;
public FakeDbSet()
{
_data = new ObservableCollection<T>();
_query = _data.AsQueryable();
}
public async Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> query = _query;
foreach (Expression<Func<T, object>> include in includes)
{
query = query.Include(include);
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
query = orderBy(query);
}
return await query.ToListAsync().ConfigureAwait(false);
}
}
In the above code, I added same method GetAsync which was there in dbrepository but in above method code
IQueryable query = _query; which gives error cannot implicitly convert type system.linq.Iqueryable to system.linq.Iqueryable .
Then I wrote my one of the test
[Test]
public void TestGetAsync()
{
//Arrange
var mock = new Mock<OrdersRequestContext>();
mock.Setup(x => x.Set<OrdersRequestContext>())
.Returns(new FakeDbSet<OrderDetails>
{
new OrderDetails{ OrderDetailsId = 6}
});
var entityRepository = new DbRepository<OrderDetails>(mock.Object);
var orders = entityRepository.GetAsync();
}
I couldnt run this test because of the above error I stated. Also I want to know Fakedbset methods should be same as my dbrepository methods? I am facing error in IQueryable query = _query; as I mentioned. So I am missing something in the way I am doing so can someone help me to correct this. Any help would be greatly appreciated. Thank you

passing dynamic expression to order by in code first EF repository

we have written a Generic function to get the records from EF code first in a repository pattern. Rest seems to be ok but when passing an Integer to the dynamic order by , it says Cannot cast System.Int32 to System.Object
the expression is as follows:
Expression<Func<HeadOffice, object>> orderByFunc = o => o.Id;
if (options.sort == "Id")
{
// this is an Integer
orderByFunc = o => o.Id;
}
if (options.sort =="Name")
{
// string
orderByFunc = o => o.Name;
}
if (options.sort == "Code")
{
orderByFunc = o => o.Code;
}
the generic method is as follows:
public virtual IEnumerable<TEntity> GetSorted<TSortedBy>(
Expression<Func<TEntity, object>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = dbSet;
foreach (var include in includes)
{
query = dbSet.Include(include);
}
IEnumerable<TEntity> data = query.OrderBy(order).Skip(skip).Take(take).ToList();
return data;
}
if we convert Expression<Func<TEntity, object>> to Expression<Func<TEntity, int>> then it seems to work fine with integer but consequently not with strings
any help appreciated.
Maybe if you change the type of that parameter for this Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy, it could make your life easier:
public virtual IEnumerable<TEntity> GetSorted(Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy,...)
{
IQueryable<TEntity> query = dbSet;
//...
if (orderBy != null)
{
query = orderBy(query);
}
//...
}
This way you can pass an Func like this:
Func<IQueryable<HeadOffice>, IOrderedQueryable<HeadOffice>> orderBy=null;
if (options.sort == "Id")
{
orderBy= query=>query.OrderBy(o => o.Id);
}
//...
Update
Another thing that I notice now is you are not using the TSortedBy generic parameter, so, you could also do this:
public virtual IEnumerable<TEntity> GetSorted<TSortedBy>(Expression<Func<TEntity, TSortedBy>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
}
But anyway I think is better use the first option and remove that generic parameter.
Create a Sorter class. We also need a property-type-neutral base class:
public class SorterBase<TEntity>
{
public abstract IEnumerable<TEntity> GetSorted( // Note, no order argument here
int skip, int take,
params Expression<Func<TEntity, object>>[] includes);
}
public class Sorter<TEntity, TSortProp> : SorterBase<TEntity>
{
private Expression<Func<TEntity, TSortProp>> _order;
public Sorter(Expression<Func<TEntity, TSortProp>> order)
{
_order = order;
}
public override IEnumerable<TEntity> GetSorted(...)
{
// Use _order here ...
}
}
Now change the sort decision to:
SorterBase<HeadOffice> sorter;
if (options.sort == "Id") {
sorter = new Sorter<HeadOffice, int>(o => o.Id);
} else if (options.sort == "Name") {
sorter = new Sorter<HeadOffice, string>(o => o.Name);
}
...
var result = sorter.GetSorted(skip, take, includes);
One solution is to have two overloaded methods, one takes
Expression<Func<TEntity, int>>
and one takes
Expression<Func<TEntity, string>>
To minimize code duplication, extract common code (for example the query initialization statement and the for loop) to a shared method, and just let the two methods call this shared method and then invoke OrderBy on the result.
If none of the answers work for you and you must have the order expression as:
Expression<Func<TEntity,object>>
then try the following solution:
public class ExpressionHelper : ExpressionVisitor
{
private MemberExpression m_MemberExpression;
public MemberExpression GetPropertyAccessExpression(Expression expression)
{
m_MemberExpression = null;
Visit(expression);
return m_MemberExpression;
}
protected override Expression VisitMember(MemberExpression node)
{
var property = node.Member as PropertyInfo;
if (property != null)
{
m_MemberExpression = node;
}
return base.VisitMember(node);
}
}
public class DataClass<TEntity>
{
private readonly IQueryable<TEntity> m_Queryable;
public DataClass(IQueryable<TEntity> queryable)
{
m_Queryable = queryable;
}
public virtual IEnumerable<TEntity> GetSorted(
Expression<Func<TEntity, object>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
var property_access_expression = new ExpressionHelper().GetPropertyAccessExpression(order);
if(property_access_expression == null)
throw new Exception("Expression is not a property access expression");
var property_info = (PropertyInfo) property_access_expression.Member;
var covert_method = this.GetType().GetMethod("Convert").MakeGenericMethod(property_info.PropertyType);
var new_expression = covert_method.Invoke(this, new object[] {property_access_expression, order.Parameters });
var get_sorted_method = this.GetType()
.GetMethod("GetSortedSpecific")
.MakeGenericMethod(property_info.PropertyType);
return (IEnumerable<TEntity>)get_sorted_method.Invoke(this, new object[] { new_expression, skip, take, includes });
}
public virtual IEnumerable<TEntity> GetSortedSpecific<TSortedBy>(
Expression<Func<TEntity, TSortedBy>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = m_Queryable;
//Here do your include logic and any other logic
IEnumerable<TEntity> data = query.OrderBy(order).Skip(skip).Take(take).ToList();
return data;
}
public Expression<Func<TEntity, TNewKey>> Convert<TNewKey>(
MemberExpression expression, ReadOnlyCollection<ParameterExpression> parameter_expressions )
{
return Expression.Lambda<Func<TEntity, TNewKey>>(expression, parameter_expressions);
}
}
Here is how I tested this:
void Test()
{
Expression<Func<Entity, object>> exp = (x) => x.Text;
List<Entity> entities = new List<Entity>();
entities.Add(new Entity()
{
Id = 1,
Text = "yacoub"
});
entities.Add(new Entity()
{
Id = 2,
Text = "adam"
});
DataClass<Entity> data = new DataClass<Entity>(entities.AsQueryable());
var result = data.GetSorted(exp, 0, 5, null);
}
I tested this with both integer and string properties.
Please note that this only works for simple property access expressions.
You can enhance the GetSorted method to make this work for more complex cases.

EntityFramework Generic Repository, multiple include?

I am trying to change my generic retrieve method from my generic repository. But I want instead to pass a string for the includeproperties, to pass this: params Expression<Func<TEntity, object>>[] includeProperties = null
The thing is when I call this method:
public virtual IEnumerable<TEntity> Retrieve(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includeProperties = null)
I want for example: TEntityExample.Retrieve(filter: c=>c.Id=Id, includeProperties:c=> c.propertynav1, e=> e.propertynav1.propertynav3, e=> e.Prop4)
Or just if dont need navigation properties TEntityExample.Retrieve(filter: c=>c.Id=Id)
But dont know why the includeProperties: is not working, is not accepted, anyone know why, or if I am doing something wrong. I want the possibility to dont pass the includeProperties or to pass it by specifying the includeProperties:
public virtual IEnumerable<TEntity> Retrieve(Expression<Func<TEntity, bool>> filter = null, string includeProperties = "")
{
IQueryable<TEntity> query = _dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (!string.IsNullOrEmpty(includeProperties))
{
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
return query.ToList();
}
I do something similar, but instead use expressions to eagerly load. Maybe this will help:
public TEntity Item(Expression<Func<TEntity, bool>> wherePredicate, params Expression<Func<TEntity, object>>[] includeProperties)
{
foreach (var property in includeProperties)
{
_dbSet.Include(property);
}
return _dbSet.Where(wherePredicate).FirstOrDefault();
}

Generic repository, function explanation / c#

I have found some interesting generic repository, but I can't figure out what the function does: PerformInclusions(includeProperties, query);
Call to PerformInclusions,
public T Single(Expression<Func<T, bool>> where, string includeProperties)
{
try
{
IQueryable<T> query = IDbSet;
query = PerformInclusions(includeProperties, query);
return query.Single(where);
}
catch (InvalidOperationException ex)
{
return null;
}
}
PerformInclusions
private static IQueryable<T> PerformInclusions(string includeProperties,
IQueryable<T> query)
{
if (includeProperties != null && includeProperties.Length > 0)
{
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
return query;
}
I can use the single function without the second parameter.
Euser test = Adapter.EuserRepository.Single(u => u.EuserEmail.Equals(user.EuserEmail), "");
So i have 2 questions what does the function PerformInclusions() do and can someone give me an example what the includeproperties would be in the call to the single() function.
Thanks in advance
as #Andrei says this is allowing you to use familiar EF include syntax to eager load navigation properties. The other alternative for nav property eager loading is via lambdas as below:
public T GetBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes)
{
var result = GetAll();
if (includes.Any())
{
foreach (var include in includes)
{
result = result.Include(include);
}
}
return result.FirstOrDefault(predicate);
}
If you have an entity with a navigation property as below:
public class Test{
public int Id {get;set;}
public SomethingElse Thing {get;set;}
}
_repo.Single(t => t.Id == 1, ""); OR _repo.GetBy(t => t.Id == 1);
will return an entity where Thing is null
_repo.Single(t => t.Id == 1, "Thing"); OR _repo.GetBy(t => t.Id == 1, t=>t.Thing);
will return an entity with Thing populated via the foreign key
For some more details on what Navigation properties in EF are check out my blog http://blog.staticvoid.co.nz/2012/7/17/entity_framework-navigation_property_basics_with_code_first

Use Include() method in repository

I have the following with EF 5:
var a = context.Posts.Include(x => x.Pack).Select(x => x.Pack.Id).ToList();
This works. Then I tried to replicate this in my generic repository:
public IQueryable<T> Include<T>(Expression<Func<T, Boolean>> criteria) where T : class
{
return _context.Set<T>().Include(criteria);
}
But in this case I am not able to do the following:
var b = repository.Include<Post>(x => x.Pack).Select(x => x.Pack.Id).ToList();
I get the error:
Cannot implicitly convert type 'Data.Entities.Pack' to 'bool'
How can I solve this?
What should I change in my Include() method?
Try:
Change
Expression<Func<T, Boolean>> criteria
To
Expression<Func<T, object>> criteria
Edit:
To Include multiple entities, you need to add an "include" extension:
public static class IncludeExtension
{
public static IQueryable<TEntity> Include<TEntity>(this IDbSet<TEntity> dbSet,
params Expression<Func<TEntity, object>>[] includes)
where TEntity : class
{
IQueryable<TEntity> query = null;
foreach (var include in includes)
{
query = dbSet.Include(include);
}
return query == null ? dbSet : query;
}
}
Then you can use it like this:
repository.Include(x => x.Pack, x => x.Pack.Roles, ...).Select(x => x.Pack.Id).ToList();
Just make sure "repository" return a "DbSet" Object.
I used the accepted answer but had to modify it a bit for EntityFramework Core.
In my experience I had to keep the chain going or the previous query references were overwritten.
public IQueryable<TEntity> Include(params Expression<Func<TEntity, object>>[] includes)
{
IIncludableQueryable<TEntity, object> query = null;
if(includes.Length > 0)
{
query = _dbSet.Include(includes[0]);
}
for (int queryIndex = 1; queryIndex < includes.Length; ++queryIndex)
{
query = query.Include(includes[queryIndex]);
}
return query == null ? _dbSet : (IQueryable<TEntity>)query;
}
This is what we ended by doing in EF 6
public IEnumerable<T> GetIncludes(params Expression<Func<T, Object>>[] includes)
{
IQueryable<T> query = table.Include(includes[0]);
foreach (var include in includes.Skip(1))
{
query = query.Include(include);
}
return query.ToList();
}

Categories

Resources