constructor includeProperties for EntityFramework - entity-framework

I have the following method in data access layer:
GetByFilterIncluding(Expression<Func<TEntity, bool>> filter, params Expression<Func<TEntity, object>>[] includeProperties)
Now I have to call it from business layer, including properties:
dll.GetByFilterIncluding(x => x.Id == id, x => x.Person, x => x.Address)
However, I want to call it like this:
if (needPerson) {
includeProperties.Add((x => x.Person));
}
if (needAddress) {
includeProperties = (x => x.Address);
}
dll.GetByFilterIncluding(x => x.Id == id, includeProperties)
I simply do not understand how to define includeProperties array/list?
EDIT:
As a note - I do not have access to Data access layer and I cannot change it.

Related

Entity Framework not including children in join

I am using inner join to return results with Entity Framework (v6.2.0) and the following code is not returning the RouteWaypoints children (i.e. route.RouteWaypoints is always null). Interestingly, single children are loading (Customer, OriginLocation, etc), but not multiple children:
public List<Route> GetAllForTripWithWaypoints(int tripId)
{
return (
from route in GetAllBaseWithWaypoints()
from tripTask in DbContext.TripTasks.Where(x =>
x.TripId == tripId && x.OriginLocationId == route.OriginLocationId)
select route
).ToList();
}
private IQueryable<Route> GetAllBaseWithWaypoints()
{
return DbContext.Routes
.Include(x => x.Customer)
.Include(x => x.OriginLocation)
.Include(x => x.DestinationLocation)
.Include(x => x.RouteWaypoints.Select(y => y.Location))
.OrderBy(x => x.OriginLocation.Name).ThenBy(x => x.DestinationLocation.Name)
.AsQueryable();
}
This approach does work if I load just the Route entity, but not when I do the join. As a reference, this does load the children successfully:
public Route GetByIdWithWaypoints(int id, bool validateExists = true)
{
var route = GetAllBaseWithWaypoints().FirstOrDefault(x => x.Id == id);
if (validateExists && route == null)
throw new Exception("Route not found for id: " + id);
return route;
}
How can I keep it working when joining?
I did an imperfect workaround by making two calls to the db - slightly less efficient, but it solves the problem:
public List<Route> GetAllForTripWithWaypoints(int tripId)
{
var routeIds = (
from route in GetAllBase()
from tripTask in DbContext.TripTasks.Where(x =>
x.TripId == tripId && x.OriginLocationId == route.OriginLocationId)
select route.Id
).ToList();
return GetAllBaseWithWaypoints().Where(x => routeIds.Contains(x.Id)).ToList();
}

EF send Includes list to repository via parametr

I at this moment I have repository filled with multiple gets methods.
E.q. Get1() => cxt.Entites.Include(e => e.obj1);
Get2() => cxt.Entities.Include(e => e.obj1).Include(e => e.obj2)
And so on.
Is there good method, pattern to have one GET method where I can send inclues via parameter?
public virtual IEnumerable<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);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
See repository pattern in msdn
You can use
_sampleRepostiory.Get(h=>h.Id>1,null,"Employees.Departments");
Including same with lambda
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (includes != null)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
Consume it like this
var query = context.Customers
.Get(x=>x.Id>1,null,
c => c.Address,
c => c.Orders.Select(o => o.OrderItems));
Similar SO question
I did the following in my projects:
public Entity[] GetAll(bool includeObj1, bool includeAllOthers) {
IQueryable<Entity> entity = ctx.Entities;
if (includeObj1)
entity = entity.Include(e => e.obj1);
if (includeAllOthers) {
entity = entity
.Include(e => e.obj2)
.Include(e => e.obj3)
.Include(e => e.obj4)
.Include(e => e.obj5);
}
return entity.ToArray();
}
Providing arguments like includeObj1 and includeObj2 separates a consumer of repository from implementation and encapsulates any data access logic.
Passing direct "include these properties" orders to a repository means that you know how repository works and assume that it is some sort ORM which blurs abstractions.

LINQ Generic GroupBy And SelectBy With Method Syntax

I'm trying to make it generic but didn't succeed. It doesnt get related User entity.
return from transactions in context.Transaction
where transactions.Date.Month == date.Month && transactions.Date.Year == date.Year
join users in context.UserProfile on transactions.UserID equals users.UserID
orderby transactions.MonthlyTotalExperiencePoint
group transactions by transactions.UserID into groupedList
select groupedList.First()).Take(50).ToList();
I've tried that:
public async Task<List<object>> GetAllGroup<TReturn, TOrderKey, TGroupKey>(Expression<Func<IGrouping<TGroupKey, TEntity>, TReturn>> selectExp,
Expression<Func<TEntity, bool>> whereExp,
Expression<Func<TEntity, TOrderKey>> orderbyExp,
bool descending,
int top,
Expression<Func<TEntity, TGroupKey>> groupByExp,
params Expression<Func<TEntity, object>>[] includeExps)
{
var query = DbSet.Where(whereExp);
query = !descending ? query.OrderBy(orderbyExp) : query.OrderByDescending(orderbyExp);
if (includeExps != null)
query = includeExps.Aggregate(query, (current, exp) => current.Include(exp));
return await query.GroupBy(groupByExp).Select(selectExp).Take(top).ToListAsync();
}
It gives error at usage:
var item = await transactionRepository.GetAllGroup(x => x.FirstOrDefault(), x => x.Date != null, x => x.MonthlyTotalExperiencePoint, true, 50, x => x.MonthlyTotalExperiencePoint, x => x.User);

Entity Framework - How to pass Include off to method caller?

I have a method that calls a DbSet from an Entity Framework database:
public static List<CostEntryVM> ToViewModelList(this DbSet<CostEntry> CostEntrys, Expression<Func<CostEntry, bool>> query) {
return AutoMapper.Mapper.Map<List<CostEntry>, List<CostEntryVM>>(
CostEntrys
.Include(x => x.Job)
.Include(x => x.User)
.Where(query)
.ToList());
}
To use this I can then do for example:
CostEntrys.ToViewModelList(x => x.Active == true);
I want to also be able to call:
CostEntrys.ToViewModelList(x => x.Include(y => y.Job).Include(y.User), x => x.Active == true);
I can't for the life of me figure out how the method signature should look or how I would then apply that to the DbSet.
How can I do this?
First you need to change the extension method to:
public static List<CostEntryVM> ToViewModelList(
this DbSet<CostEntry> CostEntrys,
Expression<Func<CostEntry, bool>> query,
Func<IQueryable<CostEntry>, IQueryable<CostEntry>> func)
{
// Adding the predicate query
IQueryable<CostEntry> queryable = CostEntrys.Where(query);
// Adding include paths
IQueryable<CostEntry> queryableWithFetch = func(queryable);
// Executing the query and map it to the view model object
return AutoMapper.Mapper.Map<List<CostEntry>, List<CostEntryVM>>(
queryableWithFetch.ToList());
}
And then you can call it:
CostEntrys.ToViewModelList(
x => x.Active == true,
x => x.Include(y => y.Job).Include(y.User));

How to use 'Or' in Where clauses?

I need something like this: Select name from users where id = 1 or id = 2.
I know I can do this:
_db.Users
.Where(u => u.Id == 1 || u.Id == 2);
But is there any alternative to this?
Is there something like this:
_db.User
.Where(u => u.Id == 1)
.Or
.Where(u => u.Id == 2)
Not directly. Remember, _db.Users.Where(u => u.Id == 1) is the user whose id is 1. You cannot get the user with id 2 from that, because it isn't there.
You can use some other approach, such as
var user1 = _db.Users.Where(u => u.Id == 1);
var user2 = _db.Users.Where(u => u.Id == 2);
var users = user1.Union(user2);
or
var userids = new int[] { 1, 2 };
var users = _db.Users.Where(u => userids.Contains(u.Id));
though.
I usually use next form to build dynamic linq with ORs and ANDs.
public class Linq
{
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1,
Expression<Func<T, bool>> expression2)
{
if (expression1 == null) throw new ArgumentNullException("expression1", "Consider setting expression1 to Linq.True<T>() or Linq.False<T>()");
var invokedExpr = Expression.Invoke(expression2, expression1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expression1.Body, invokedExpr), expression1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1,
Expression<Func<T, bool>> expression2)
{
if (expression1 == null) throw new ArgumentNullException("expression1", "Consider setting expression1 to Linq.True<T>() or Linq.False<T>()");
var invokedExpr = Expression.Invoke(expression2, expression1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expression1.Body, invokedExpr), expression1.Parameters);
}
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
}
And then use it in your code:
Expression<Func<User, bool>> searchExpression = Linq.False<User>();
searchExpression = Linq.Or<User>(searchExpression, b => b.Id==1);
searchExpression = Linq.Or<User>(searchExpression, b => b.Id==2);
Then you use this searchExpression as
_db.Users.Where(searchExpression);
With this form it is very easy to build dynamic queries, like:
Expression<Func<User, bool>> searchExpression = Linq.False<User>();
searchExpression = Linq.Or<User>(searchExpression, b => b.Id==1);
if (!String.IsNullOrEmpty(name))
searchExpression = Linq.Or<User>(searchExpression, b => b.Name.StartsWith(name));
//combine with OR / AND as much as you need
It will generate only one query to the DB.
You just want:
_db.Users.Where(u => (u.Id == 1 || u.Id == 2));