LINQ Generic GroupBy And SelectBy With Method Syntax - entity-framework

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);

Related

ArgumentException thrown When GroupJoin is applied on a DbSet as a first join and NOT when applied as a second or later

1. Exception Case:
LINQ: Here, I have a query which is doing a GroupJoin on an DbSet and then adding other inner joins.
Repository.ConvertToBigIntTable -> This is an output of a call to a Table-Valued function which returns an IQueryable (T is a Keyless entity having a long Id field)
private IQueryable<IDocumentTypePermission> GetDocumentTypePermissionsTest<T>(IQueryable<T> entitySet) where T : class, IDocumentType
{
var userGroupIds = RunComponent(GetActiveUserGroupsOfCurrentUser.New(new GetActiveUserGroupsOfCurrentUserParam() { CurrentUserId = CurrentUserId })).UserGroupIds;
return (from documentType in entitySet
join userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
on documentType.Id equals userGroupId.Id into UserGroupIds
from userGroupId in UserGroupIds.DefaultIfEmpty()
join documentTypePermission in Repository.DocumentTypePermissions
on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams
on documentTypePermission.UserSelectionId equals userSelectionParam.Id
where documentTypePermission.IsActive && ((userSelectionParam.UserGroupId != null && userGroupId.Id != null)
|| (userSelectionParam.UserId != null && userSelectionParam.UserId == CurrentUserId))
select documentTypePermission);
}
DbSet<EDocumentType>()
.GroupJoin(
inner: BananaContext.ConvertCSVToBigIntTable(
List: <>c__DisplayClass958_0.List,
Delim: <>c__DisplayClass958_0.Delim),
outerKeySelector: documentType => documentType.Id,
innerKeySelector: userGroupId => userGroupId.Id,
resultSelector: (documentType, UserGroupIds) => new {
documentType = documentType,
UserGroupIds = UserGroupIds
})
.SelectMany(
collectionSelector: <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.UserGroupIds
.DefaultIfEmpty(),
resultSelector: (<>h__TransparentIdentifier0, userGroupId) => new {
<>h__TransparentIdentifier0 = <>h__TransparentIdentifier0,
userGroupId = userGroupId
})
.Join(
inner: DbSet<EDocumentTypePermission>(),
outerKeySelector: <>h__TransparentIdentifier1 => (long?)<>h__TransparentIdentifier1.<>h__TransparentIdentifier0.documentType.Id,
innerKeySelector: documentTypePermission => documentTypePermission.DocumentTypeId,
resultSelector: (<>h__TransparentIdentifier1, documentTypePermission) => new {
<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1,
documentTypePermission = documentTypePermission
})
.Join(
inner: DbSet<EUserSelectionParam>(),
outerKeySelector: <>h__TransparentIdentifier2 => <>h__TransparentIdentifier2.documentTypePermission.UserSelectionId,
innerKeySelector: userSelectionParam => (long?)userSelectionParam.Id,
resultSelector: (<>h__TransparentIdentifier2, userSelectionParam) => new {
<>h__TransparentIdentifier2 = <>h__TransparentIdentifier2,
userSelectionParam = userSelectionParam
})
.Where(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.documentTypePermission.IsActive && <>h__TransparentIdentifier3.userSelectionParam.UserGroupId != null && (long?)<>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userGroupId.Id != null || <>h__TransparentIdentifier3.userSelectionParam.UserId != null && <>h__TransparentIdentifier3.userSelectionParam.UserId == (long?)AbstractDomainBehavior.CurrentUserId)
.Select(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.documentTypePermission)
Exception:
Expression of type 'System.Func`2[Lw.Domain.IDocumentType,System.Int64]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[Lw.Domain.EDocumentType,System.Int64]]' of method 'System.Linq.IQueryable`1[<>f__AnonymousType290`2[<>f__AnonymousType289`2[Lw.Domain.IDocumentType,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ITableValueFunctionResult]],Lw.Sys.Repository.ITableValueFunctionResult]] LeftJoin[EDocumentType,ITableValueFunctionResult,Int64,<>f__AnonymousType290`2](System.Linq.IQueryable`1[Lw.Domain.EDocumentType], System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ITableValueFunctionResult], System.Linq.Expressions.Expression`1[System.Func`2[Lw.Domain.EDocumentType,System.Int64]], System.Linq.Expressions.Expression`1[System.Func`2[Lw.Sys.Repository.ITableValueFunctionResult,System.Int64]], System.Linq.Expressions.Expression`1[System.Func`3[Lw.Domain.EDocumentType,Lw.Sys.Repository.ITableValueFunctionResult,<>f__AnonymousType290`2[<>f__AnonymousType289`2[Lw.Domain.IDocumentType,System.Collections.Generic.IEnumerable`1[Lw.Sys.Repository.ITableValueFunctionResult]],Lw.Sys.Repository.ITableValueFunctionResult]]])' (Parameter 'arg2')
Stack Trace:
at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1, Expression arg2, Expression arg3, Expression arg4)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.TryFlattenGroupJoinSelectMany(MethodCallExpression methodCallExpression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
at System.Linq.SystemCore_EnumerableDebugView`1.get_Items()
NOTE: Interestingly trying to do a very similar operation on LinqPad7 seems to work:
2. Working Case
LINQ: Here, I have a query which is performing inner joins on few DbSets and then applying GroupJoin on one of the DbSet.
Repository.ConvertToBigIntTable -> This is an output of a call to a Table-Valued function which returns an IQueryable (T is a Keyless entity having a long Id field)
private IQueryable<IDocumentTypePermission> GetDocumentTypePermissions<T>(IQueryable<T> entitySet) where T : class, IDocumentType
{
var userGroupIds = RunComponent(GetActiveUserGroupsOfCurrentUser.New(new GetActiveUserGroupsOfCurrentUserParam() { CurrentUserId = CurrentUserId })).UserGroupIds;
return (from documentType in entitySet
join documentTypePermission in Repository.DocumentTypePermissions on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams on documentTypePermission.UserSelectionId equals userSelectionParam.Id
join userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
on userSelectionParam.UserGroupId equals userGroupId.Id into UserGroupIds
from userGroupId in UserGroupIds.DefaultIfEmpty()
where documentTypePermission.IsActive && ((userSelectionParam.UserGroupId != null && userGroupId.Id != null)
|| (userSelectionParam.UserId != null && userSelectionParam.UserId == CurrentUserId))
select documentTypePermission);
}
Generated Expression:
DbSet<EDocumentType>()
.Join(
inner: DbSet<EDocumentTypePermission>(),
outerKeySelector: documentType => (long?)documentType.Id,
innerKeySelector: documentTypePermission => documentTypePermission.DocumentTypeId,
resultSelector: (documentType, documentTypePermission) => new {
documentType = documentType,
documentTypePermission = documentTypePermission
})
.Join(
inner: DbSet<EUserSelectionParam>(),
outerKeySelector: <>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.documentTypePermission.UserSelectionId,
innerKeySelector: userSelectionParam => (long?)userSelectionParam.Id,
resultSelector: (<>h__TransparentIdentifier0, userSelectionParam) => new {
<>h__TransparentIdentifier0 = <>h__TransparentIdentifier0,
userSelectionParam = userSelectionParam
})
.GroupJoin(
inner: BananaContext.ConvertCSVToBigIntTable(
List: <>c__DisplayClass958_0.List,
Delim: <>c__DisplayClass958_0.Delim),
outerKeySelector: <>h__TransparentIdentifier1 => <>h__TransparentIdentifier1.userSelectionParam.UserGroupId,
innerKeySelector: userGroupId => (long?)userGroupId.Id,
resultSelector: (<>h__TransparentIdentifier1, UserGroupIds) => new {
<>h__TransparentIdentifier1 = <>h__TransparentIdentifier1,
UserGroupIds = UserGroupIds
})
.SelectMany(
collectionSelector: <>h__TransparentIdentifier2 => <>h__TransparentIdentifier2.UserGroupIds
.DefaultIfEmpty(),
resultSelector: (<>h__TransparentIdentifier2, userGroupId) => new {
<>h__TransparentIdentifier2 = <>h__TransparentIdentifier2,
userGroupId = userGroupId
})
.Where(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.<>h__TransparentIdentifier0.documentTypePermission.IsActive && <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userSelectionParam.UserGroupId != null && (long?)<>h__TransparentIdentifier3.userGroupId.Id != null || <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userSelectionParam.UserId != null && <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.userSelectionParam.UserId == (long?)AbstractDomainBehavior.CurrentUserId)
.Select(<>h__TransparentIdentifier3 => <>h__TransparentIdentifier3.<>h__TransparentIdentifier2.<>h__TransparentIdentifier1.<>h__TransparentIdentifier0.documentTypePermission)
Generated SQL:
DECLARE #__List_1 nvarchar(4000) = N'1,5,6,7,8,14,15,44,46,62,63,64,67,69,73,85,88';
DECLARE #__Delim_2 nvarchar(1) = N',';
DECLARE #__CurrentUserId_3 bigint = CAST(1 AS bigint);
SELECT [d0].[Id], [d0].[Condition], [d0].[CreatedById], [d0].[CreatedTime], [d0].[DocumentTypeId], [d0].[IsActive], [d0].[IsOverridable], [d0].[IsReevaluate], [d0].[RowVersion], [d0].[UpdatedById], [d0].[UpdatedTime], [d0].[UserSelectionId], [d0].[AssignmentType], [d0].[ConditionFor], [d0].[CreationAllowed], [d0].[Permission]
FROM [DocumentTypes] AS [d]
INNER JOIN [DocumentTypePermissions] AS [d0] ON [d].[Id] = [d0].[DocumentTypeId]
INNER JOIN [UserSelectionParams] AS [u] ON [d0].[UserSelectionId] = [u].[Id]
LEFT JOIN [dbo].[ConvertCSVToBigIntTable](#__List_1, #__Delim_2) AS [c] ON [u].[UserGroupId] = [c].[Id]
WHERE ([d0].[IsActive] = CAST(1 AS bit)) AND (([u].[UserGroupId] IS NOT NULL AND [c].[Id] IS NOT NULL) OR ([u].[UserId] IS NOT NULL AND ([u].[UserId] = #__CurrentUserId_3)))
EF Core version: 6.0.1
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Win 10 Pro
IDE: Visual Studio 2022 v17.0.4
UPDATE: One more peculiar case: Instead of using generic object "entitySet" if I then use direct DbContext.DbSet reference, then all the errors go away and the Sql gets generated correctly:
private IQueryable<IDocumentTypePermission> GetDocumentTypePermissionsTest<T>(IQueryable<T> entitySet) where T : class, IDocumentType
{
var userGroupIds = RunComponent(GetActiveUserGroupsOfCurrentUser.New(new GetActiveUserGroupsOfCurrentUserParam() { CurrentUserId = CurrentUserId })).UserGroupIds;
return (from documentType in Repository.GetDbContext().DocumentTypes
join userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
on documentType.Id equals userGroupId.Id into UserGroupIds
from userGroupId in UserGroupIds.DefaultIfEmpty()
join documentTypePermission in Repository.DocumentTypePermissions
on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams
on documentTypePermission.UserSelectionId equals userSelectionParam.Id
where documentTypePermission.IsActive && ((userSelectionParam.UserGroupId != null && userGroupId.Id != null)
|| (userSelectionParam.UserId != null && userSelectionParam.UserId == CurrentUserId))
select documentTypePermission);
}
```
Try to replace GroupJoin with SelectMany
var query =
from documentType in entitySet
from userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
.Where(userGroupId => documentType.Id == userGroupId.Id)
.DefaultIfEmpty()
join documentTypePermission in Repository.DocumentTypePermissions
on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams
on documentTypePermission.UserSelectionId equals userSelectionParam.Id
....

constructor includeProperties for EntityFramework

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.

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.

best practice on conditional queries with linq to entities

I often find myself writing querys like this:
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => (fromDate.HasValue ? u.date >= fromDate.Value : true) &&
(toDate.HasValue ? u.date <= toDate.Value : true) &&
u.Carrier != null &&
u.status == (int)VoyageStatus.State.InProgress)
.OrderBy(u => u.date);
return voyages;
With conditionals inside the where statement:
fromDate.HasValue ? u.date >= fromDate.Value : true
I know the other way to do it'll be like:
var voyages = db.VoyageRequests.Include("Carrier").Where(u => u.Carrier != null &&
u.status == (int)VoyageStatus.State.InProgress);
if (fromDate.HasValue)
{
voyages = voyages.Where(u => u.date >= fromDate.Value);
}
if (toDate.HasValue)
{
voyages = voyages.Where(u => u.date <= toDate.Value);
}
return voyages.OrderBy(u => u.date);
Is there any real difference that may affect performance when this 2 approaches get transform to SQL expression?
The second query will create the simpler SQL because the evaluation of fromDate.HasValue and toDate.HasValue happens on the client. In the first query the ternary operators get evaluated on the database server as part of the SQL query. Both fromDate and toDate will be transmitted as constants to the server while in the second query only then if .HasValue is true.
We are talking about a few bytes more in length of the SQL statement and I don't believe that the server-side evaluation of the ternaries has any significant effect on query performance.
I would choose what you find more readable. Personally I would decide for the second query.
If you want the simple SQL and the more readable C# you can create an Extension as suggested by Viktor Mitev http://mentormate.com/blog/improving-linq-to-entities-queries/
public static class WhereExtensions
{
// Where extension for filters of any nullable type
public static IQueryable<TSource> Where<Tsource, TFilter>
(
this IQueryable <TSource> source,
Nullable <TFilter> filter,
Expression<Func<TSource, bool>> predicate
) where TFilter : struct
{
if (filter.HasValue)
{
source = source.Where(predicate);
}
return source;
}
// Where extension for string filters
public static IQueryable<TSource> Where<TSource>
(
this IQueryable<TSource> source,
string filter,
Expression<Func<TSource, bool>> predicate
)
{
if (!string.IsNullOrWhiteSpace(filter))
{
source = source.Where(predicate);
}
return source;
}
// Where extension for collection filters
public static IQueryable<TSource> Where<TSource, TFilter>
(
this IQueryable<TSource> source,
IEnumerable<TFilter> filter,
Expression<Func<TSource, bool>> predicate
)
{
if (filter != null && filter.Any())
{
source = source.Where(predicate);
}
return source;
}
Then your "secound query" will look like this:
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => u.Carrier != null && u.status == (int)VoyageStatus.State.InProgress)
.Where(u => u.date >= fromDate)
.Where(u => u.date <= toDate)
.OrderBy(u => u.date);
I don't know if it is more readable or if it will be confusing for some developers because it is more difficult to read directly form the code what part of the filtering is in use.
Maybe it will be more readable if you name the extensions function something like WhereIfFilterNotNull (or something meaningful :-)
var voyages = db.VoyageRequests.Include("Carrier")
.Where(u => u.Carrier != null && u.status == (int)VoyageStatus.State.InProgress)
.WhereIfFilterNotNull(u => u.date >= fromDate)
.WhereIfFilterNotNull(u => u.date <= toDate)
.OrderBy(u => u.date);

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));