I have a big code chunk below, the actual code is not that important, but is included in order to demonstrate what I'm trying to do.
I have a public static Expression> GetFreeSpotCount(...)
this method returns an Expression which I would like to reuse in another method
The expression itself return a number which indicates the number of [model] in a list of [model] which fulfills certain criteria.
I want the new method to return all [model]'s where the number above is greater than 0.
below is what code I have presently, I want to avoid having to duplicate so much code
public static Expression<Func<ExamTimeSlot, int>> GetFreeSpotCountFor(List<Guid> drivingSchoolIds)
{
return ets =>
ets.Participants
- ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Count(e => !ets.ExamTimeSlotReservations.Any(r => r.DrivingSchoolId == e.BookedByDrivingSchoolId))
- ((int?)ets.ExamTimeSlotReservations.Sum(r => (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count()
- r.ReservedSpots) > 0 ? (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count() - r.ReservedSpots) : 0) ?? 0)
- ((int?)ets.ExamTimeSlotReservations.Sum(r => r.ReservedSpots) ?? 0)
+ ((((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
- ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count()) >= 0 ?
(((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
- ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count())
: 0);
}
and:
public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
return ets =>
(ets.Participants
- ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Count(e => !ets.ExamTimeSlotReservations.Any(r => r.DrivingSchoolId == e.BookedByDrivingSchoolId))
- ((int?)ets.ExamTimeSlotReservations.Sum(r => (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count()
- r.ReservedSpots) > 0 ? (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count() - r.ReservedSpots) : 0) ?? 0)
- ((int?)ets.ExamTimeSlotReservations.Sum(r => r.ReservedSpots) ?? 0)
+ ((((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
- ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count()) >= 0 ?
(((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
- ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count())
: 0)) > 0;
}
I wanted to do something like:
public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
return ets => GetFreeSpotCountFor(drivingSchoolIds) > 0;
}
I have tried using Expression.GreaterThan with the first expression, but since I need the result AND [model] I could not figure out a way to make it work.
There isn't really an easier way (without a third party library) to do this. It seems like we should easily be able to do what you've written, but we're mislead because of the syntactic sugar we rely on when creating expression trees.
public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
var param = Expression.Parameter(typeof(ExamTimeSlot));
//We want to create a new lambda which will invoke `GetFreeSpotCountFor` with our parameter, and then check it's greater than 0
var newBody =
Expression.GreaterThan(
//View this as GetFreeSpotCountFor(drivingSchoolIds)(param) - where param will be given to us when this lambda is invoked
Expression.Invoke(
GetFreeSpotCountFor(drivingSchoolIds),
param
),
//Pass the right-hand value (0) to the GreaterThan check
Expression.Constant(0)
);
var lambda = Expression.Lambda<Func<ExamTimeSlot, bool>>(newBody, param);
return lambda;
}
Once we've got the Expression object, we need to build the expression trees manually.
The above will wrap your lambda in a new lambda, which invokes and compares the result to 0. An alternative is to check the existing lambda's .Body, and append .GreaterThan to it, and return an entirely new lambda.
Ie:
public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
//This grabs the existing lambda, which we will work on
var oldLambda = GetFreeSpotCountFor(drivingSchoolIds);
var newBody =
//Invoke `GreaterThan` directly on the old lambda's Body
Expression.GreaterThan(
oldLambda.Body,
//Pass the right-hand value (0) to the GreaterThan check
Expression.Constant(0)
);
//Now, we need to pass in the old parameters, and build a new lambda.
var lambda = Expression.Lambda<Func<ExamTimeSlot, bool>>(newBody, oldLambda.Parameters);
return lambda;
}
Why do you specifically want to combine the expressions instead of combining the query calls? One simple way to do this would be to make your GetExamTimeSlotsWithFreeSpotsFor not take and return an expression but take and return a query, and add two where clauses to that query (each being the expression you stored).
public static IQueryable<ExamTimeSlot> WhereExamTimeSlotsWithFreeSpotsFor (IQueryable<ExamTimeSlot> Source, List<Guid> drivingSchoolIds)
{
var FirstFilter = GetFreeSpotCountFor(drivingSchoolIds);
var SecondFilter = GetExamTimeSlotsWithFreeSpotsFor(drivingSchoolIds);
return Source
.Where(FirstFilter)
.Where(SecondFilter);
}
That way you don't need to combine anything, let your LINQ provider combine the two where clause by himself.
Note that if you really wanted to combine the expressions manually it is possible but as far as i know it will require either a third party API or working with expression trees directly (not just enjoying the compiler magic doing it for you). It's not hard if it's just to return an expression that takes 2 expressions and tosses an and operator in between but that seems overkill for the same result.
Related
I want to combile two expressions :
public Expression<Func<AnyType,bool>> BuildExpression(int id, string filtreA, string filtreB)
{
Expression<Func<AnyType, bool>> extraExpression = null;
Expression<Func<AnyType, bool>> expression = null;
expression = a => a.ID == id;
var fullExpression = expression.Body;
if (!String.IsNullOrEmpty(filtreA))
{
extraExpression = a => a.TYPE == filtreA;
fullExpression = Expression.AndAlso(fullExpression, extraExpression.Body);
}
if (!String.IsNullOrEmpty(filtreB))
{
extraExpression = a => a.TYPE == filtreB;
fullExpression = Expression.AndAlso(fullExpression, extraExpression.Body);
}
expression = Expression.Lambda<Func<AnyType, bool>>(fullExpression, expression.Parameters[0]);
}
return expression;
}
//I want my final expression to be a.ID == id && a.TYPE == filtreB for example
The problem is that I get the following error : System.InvalidOperationException. Le paramètre 'a' n'est pas dans la portée.. It only happens if enter in one of my ifs.
Does anyone know how I can handle this ? Thanks
ps: My question il similar to this post but it looks like the solution doesn't work anymore : Combining two expressions (Expression<Func<T, bool>>)
You have missed parameters replacement. It is a tricky with visitors.
Trying to simplify your life, I would suggest to use popular library LINQKit
and rewrite your expression building.
public Expression<Func<AnyType, bool>> BuildExpression(int id, string filtreA, string filtreB)
{
var predicate = PredicateBuilder.New<AnyType>(true);
predicate = predicate.And(a => a.ID == id);
if (!string.IsNullOrEmpty(filtreA))
{
predicate = predicate.And(a => a.TYPE == filtreA);
}
if (!string.IsNullOrEmpty(filtreB))
{
predicate = predicate.And(a => a.TYPE == filtreB);
}
return predicate;
}
Each parameter is a different ParameterExpression instance. This problem would be more obvious if you had defined each expression with different parameter names;
expression = a => a.ID == id;
// ...
extraExpression = b => b.TYPE == filtreA;
// ...
extraExpression = c => c.TYPE == filtreB;
Note that entity framework core 2.1 and earlier didn't care if your parameters were different, and would seem to look through .Invoke operations. But since version 3, with an internal rewrite of the expression compiler, neither of those are supported. Hence why earlier examples may not work anymore.
An expression visitor to swap parameter expressions isn't too complicated;
public class MapParameters : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> mapping;
public MapParameters(IEnumerable<ParameterExpression> before, IEnumerable<ParameterExpression> after)
{
this.mapping = new Dictionary<ParameterExpression, ParameterExpression>(
before
.Zip(after)
.Select(p => KeyValuePair.Create(p.First,p.Second))
);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (mapping.TryGetValue(node, out var replace))
return replace;
return base.VisitParameter(node);
}
}
Expression.Lambda<Func<AnyType, bool>>(
Expression.AndAlso(
expression.Body,
new MapParameters(
extraExpression.Parameters,
expression.Parameters
).Visit(extraExpression.Body)
),
expression.Parameters);
Also note that if you are using this to build a single expression for IQueryable<T>.Where(...). You could consider calling .Where(...) twice, which would achieve the same end result.
There is a known issue with CosmosDb where if you use an ORDER BY clause it excludes documents that do not have this property defined
To work round this I'm trying to create functionality that takes a LINQ query and replaces the Order clause with a check for documents where the property is not defined so we can then run both queries and combine the results.
So:
ordersDb.Where(x => x.Name == customerName).OrderBy(x => x.CompanyName)
would become:
ordersDb.Where(x => x.Name == customerName)
.Where(x => !x.CompanyName.IsDefined()) // IsDefined is a built in CosmosDb function
Using Expression Builder I've created the following. However, I'm having issues trying to call my expression as a Where method - :
private sealed class OrderByToIsNotDefinedVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Queryable) &&
(node.Method.Name == "OrderBy" || node.Method.Name == "OrderByDescending"))
{
// Get the IsDefined method
var methodIsDefined = typeof(TypeCheckFunctionsExtensions).GetMethod("IsDefined",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null,
new Type[] { typeof(object) }, null);
// Apply the IsDefined method to the property that was being used for OrderBy
var isDefinedItem = Expression.Call(methodIsDefined, node.Arguments[1]);
// Alter the expression to check for !IsDefined()
var isNotDefinedItem = Expression.Not(isDefinedItem);
var entityType = node.Method.GetGenericArguments()[0];
var genericWhere = BuildGenericWhere();
var methodWhere = genericWhere.MakeGenericMethod(entityType);
var param = Expression.Parameter(entityType);
Expression newExpression =
Expression.Call(
methodWhere,
node.Arguments[0],
Expression.Lambda(
typeof(Func<,>).MakeGenericType(entityType, typeof(bool)),
isNotDefinedItem,
param));
return newExpression;
}
return base.VisitMethodCall(node);
}
}
private static MethodInfo BuildGenericWhere()
{
var genericWhereMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(x => x.Name == "Where" && x.GetGenericArguments().Length == 1)
.Select(x => new { Method = x, Parameters = x.GetParameters() })
.Where(x => x.Parameters.Length == 2 &&
x.Parameters[0].ParameterType.IsGenericType &&
x.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
x.Parameters[1].ParameterType.IsGenericType &&
x.Parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => x.Method)
.Single();
return genericWhereMethod;
}
This compiles OK, but when I execute the query I get:
Microsoft.Azure.Documents.Linq.DocumentQueryException: Expression with
NodeType Lambda is not supported.
...as documentDb cannot handle the lambda clause
I've also tried replacing the lambda clause with a direct call to the where method, so the call becomes:
var updatedQueryExpression = Expression.Call(node.Arguments[0], methodWhere, isNotDefinedItem);
return updatedQueryExpression;
...however, this results in:
System.ArgumentException: Static method requires null instance,
non-static method requires non-null instance
Firstly,we are told that we can only sort with properties of document, not derived values.
So,for me,i also recommand you following the suggestion which is mentioned by #Paul in the comment: Querying all the matching data from cosmos db,then try to sort the result list and take the top elements.I believe that you already know the expression How to get first N elements of a list in C#?:
var firstFiveArrivals = myList.OrderBy(i => i.ArrivalTime).Take(5);
Since you have to get the top elements and if your matching data set is large enough, whether you use LINQ or SQL, you will encounter thresholds which could be solved by Continuation Token.
Summary: I want to know how can I detect specific definitions from the expression's body then change it in the way I want, such as
e.Entity.ListA.Union(e.ListB).Any(...)...
To
e.Entity != null &&
((e.Entity.ListA != null && e.Entity.ListA.Any(...))
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))
Only using Linq Expression techniques as I see it is the ideal solution
As a part of writing a clean C# code, I have written a set of predefined expressions and using LinqKit extensions I can combine between them, hence it will extend the dynamism of writing complex expressions easily, until that everything is okay. In addition, I want to use them to filter both IQuerable and IEnumerable cases. However, as you know, there are some cases where the defined expression will not work in the Former or the latter, I successfully have avoided a lot of such problems. Until I came to the case where I made a solution but I am still feeling is not the ideal.
I will first start by showing the problem, then explain the wished solution, in the end, I will share my attempt.
//---
public class AssignmentsEx : BaseEx
{
//.........
/// <summary>
/// (e.FreeRoles AND e.RoleClass.Roles) ⊆ ass.AllRoles
/// </summary>
public static Expression<Func<T, bool>> RolesInclosedBy<T>(IAssignedInstitution assignedInstitution) where T : class, IAssignedInstitution
{
var allStaticRoles = AppRolesStaticData.AdminRolesStr.GetAll();
var assAllRoles = assignedInstitution.AllRoles.Select(s => s.Name).ToList();
var hasAllRoles = allStaticRoles.All(assR => assAllRoles.Any(sR => sR == assR));
if (hasAllRoles)
return e => true;
// for LINQ to SQL the expression works perfectly as you know
// the expression will be translated to an SQL code
// for IEnumerable case the nested object Roles with throw null obj ref
// exception if the RoleClass is null (and this is a healthy case from code execution
//
return Expression<Func<T, bool>> whenToEntity = e => e.FreeRoles.Union(e.RoleClass.Roles).All(eR => assAllRoles.Any(assR => assR == eR.Name));
}
//.........
}
As you see If I use this method to define a list of objects with RoleClass is null or FreeRoles is null it will throw a NullException.
-- the best-expected suggestion I think it will play on three factors:
possibility to detect the desired fragment from the expression body
modify the fragment to be as needed for the IEnumerable case or vice versa
reconstruct and return new expression
this way will help me to keep the method static and modify it via extension method: e.g. ex.WithSplittedUnion()
rather than the traditional way i.e. I am using now as follow
public class AssignmentsEx
{
public LinqExpressionPurpose purpose{get;}
public AssignmentsEx(LinqExpressionPurpose purpose) : base(purpose)
{
Purpose = purpose
}
public Expression<Func<T, bool>> RolesInclosedBy<T>(IAssignedInstitution assignedInstitution) where T : class, IAssignedInstitution
{
var allStaticRoles = AppRolesStaticData.AdminRolesStr.GetAll();
var assAllRoles = assignedInstitution.AllRoles.Select(s => s.Name).ToList();
var hasAllRoles = allStaticRoles.All(assR => assAllRoles.Any(sR => sR == assR));
if (hasAllRoles)
return e => true;
Expression<Func<T, bool>> whenToObject = e => (e.FreeRoles == null || e.FreeRoles.All(eR => assAllRoles.Any(assR => assR == eR.Name)))
&& (e.RoleClass == null || e.RoleClass.Roles == null || e.RoleClass.Roles.All(eR => assAllRoles.Any(assR => assR == eR.Name)));
Expression<Func<T, bool>> whenToEntity = e => e.FreeRoles.Union(e.RoleClass.Roles).All(eR => assAllRoles.Any(assR => assR == eR.Name));
return Purpose switch
{
LinqExpressionPurpose.ToEntity => whenToEntity,
LinqExpressionPurpose.ToObject => whenToObject,
_ => null,
};
}
}
I hope the explaination is clear, Thanks in advance
From how I see it, what you need is ExpressionVisitor to traverse and modify ExpressionTree. One thing I would change is the way you call Any.
Instead of
e.Entity != null &&
((e.Entity.ListA != null && e.Entity.ListA.Any(...))
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))
I'd go for
(
e.Entity != null && e.Entity.ListA != null && e.Entity.ListB != null
? e.Entity.ListA.Union(e.Entity.ListB)
: e.Entity != null && e.Entity.ListA != null
? e.Entity.ListA
: e.Entity.ListB != null
? e.Entity.ListB
: new Entity[0]
).Any(...)
I find it easier to construct ExpressionTree and the outcome will be the same.
Example code:
public class OptionalCallFix : ExpressionVisitor
{
private readonly List<Expression> _conditionalExpressions = new List<Expression>();
private readonly Type _contextType;
private readonly Type _entityType;
private OptionalCallFix(Type contextType, Type entityType)
{
this._contextType = contextType;
this._entityType = entityType;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
// Replace Queryable.Union(left, right) call with:
// left == null && right == null ? new Entity[0] : (left == null ? right : (right == null ? left : Queryable.Union(left, right)))
if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.Union))
{
Expression left = this.Visit(node.Arguments[0]);
Expression right = this.Visit(node.Arguments[1]);
// left == null
Expression leftIsNull = Expression.Equal(left, Expression.Constant(null, left.Type));
// right == null
Expression rightIsNull = Expression.Equal(right, Expression.Constant(null, right.Type));
// new Entity[0].AsQueryable()
Expression emptyArray = Expression.Call
(
typeof(Queryable),
nameof(Queryable.AsQueryable),
new [] { this._entityType },
Expression.NewArrayInit(this._entityType, new Expression[0])
);
// left == null && right == null ? new Entity[0] : (left == null ? right : (right == null ? left : Queryable.Union(left, right)))
return Expression.Condition
(
Expression.AndAlso(leftIsNull, rightIsNull),
emptyArray,
Expression.Condition
(
leftIsNull,
right,
Expression.Condition
(
rightIsNull,
left,
Expression.Call
(
typeof(Queryable),
nameof(Queryable.Union),
new [] { this._entityType },
left,
Expression.Convert(right, typeof(IEnumerable<>).MakeGenericType(this._entityType))
)
)
)
);
}
return base.VisitMethodCall(node);
}
protected override Expression VisitMember(MemberExpression node)
{
Expression expression = this.Visit(node.Expression);
// Check if expression should be fixed
if (this._conditionalExpressions.Contains(expression))
{
// replace e.XXX with e == null ? null : e.XXX
ConditionalExpression condition = Expression.Condition
(
Expression.Equal(expression, Expression.Constant(null, expression.Type)),
Expression.Constant(null, node.Type),
Expression.MakeMemberAccess(expression, node.Member)
);
// Add fixed expression to the _conditionalExpressions list
this._conditionalExpressions.Add(condition);
return condition;
}
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == this._contextType)
{
// Add ParameterExpression to the _conditionalExpressions list
// It is used in VisitMember method to check if expression should be fixed this way
this._conditionalExpressions.Add(node);
}
return base.VisitParameter(node);
}
public static IQueryable<TEntity> Fix<TContext, TEntity>(TContext context, in Expression<Func<TContext, IQueryable<TEntity>>> method)
{
return ((Expression<Func<TContext, IQueryable<TEntity>>>)new OptionalCallFix(typeof(TContext), typeof(TEntity)).Visit(method)).Compile().Invoke(context);
}
}
You can call it like this:
OptionalCallFix.Fix(context, ctx => ctx.Entity.ListA.Union(ctx.ListB));
atm im writting generic filter module in my app.
I have problem with creating proper Expression>. I general SQL query look like:
SELECT distinct ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE CODE ='MyName'
AND EXISTS (SELECT *
FROM dbo.VIEW_ITEM
WHERE ROW_NUMBER = item.ROW_NUMBER
AND CODE='MyName'
AND (COL_NUMBER=1 AND DISPLAY='UserName'))
AND EXISTS (SELECT *
FROM VIEW_ITEM
WHERE ROW_NUMBER = item.ROW_NUMBER
AND CODE='MyName'
AND (COL_NUMBER=3 and DISPLAY='2261'))
ORDER BY ROW_NUMBER
This is (in my opinion) best way to get all records I need. I can not use join option because im checking in my AND EXISTS same table as in main query.
So my linq look like:
dboMVI.Where(mvi => mvi.Code == "MyCode")
.Where(mvi => dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && innerMvi.Display == "UserName").Any())
.Where(mvi => dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && innerMvi.Display == "2261").Any())
.Select(mvi => mvi.RowNumber)
.OrderBy(rn => rn)
.Distinct();
That should return me all Rows nnumbers that passed my filtering.
Im managed to create Expression but im sure there is better way to make it more generic and put this in my filtering module not in service from where im passing it to DbContext.
Expression<Func<ViewItem, bool>> filter =mvi =>
dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber
&& innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 &&
innerMvi.Display == "2261").Any()
And For second filter:
Expression<Func<ViewItem, bool>> filter =mvi =>
dboMVI.Where(innerMvi => innerMvi.RowNumber == mvi.RowNumber
&& innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 &&
innerMvi.Display == "UserName").Any()
My question is how I can create generic Expression tree for this LINQ query?
I could not find anywhere example of creating such tree.
I found example of passing arguments in join statement.
But in my case im passing from main query number of current row and in subquery its checked if for this specific row any conditions are meet or not.
EDIT: After some comments i noticed i did mistake in rewritting querys from real values to demo. Srry for that hope its fixed now:)
In general its working solution just looking for better way.
EDIT2:
What is my problem here:
im trying to generate from LINQ SQL query that can be consume by EF Core.
in my SQL in using AND EXISTS where in body im referring to same table as in main query. What more in sub query im using ROW_NUMBER from main query.
What is my problem? I dont know how to create expression func (responsible for my sub query) because I dont know how to pass into it my current ROW_NUMBER that im checking. I know how to make expression tree for easy examples. But there I have constant in my body fe int or string . But in this case my const is changing every time to different value so it can not be hard coded.
Answer
I managed to resolve this issue. First of all had to simplify linq query.
dboMVI.Where(mvi => mvi.Code == "MyCode")
.Where(mvi => dboMVI.Any(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 1 && innerMvi.Display == "UserName"))
.Where(mvi => dboMVI.Any(innerMvi => innerMvi.RowNumber == mvi.RowNumber && innerMvi.Code == "MyCode" && innerMvi.ColNumber == 3 && innerMvi.Display == "2261"))
.Select(mvi => mvi.RowNumber)
.OrderBy(rn => rn)
.Distinct()
Than had to create Expression tree for elements inside Any expression:
IQueryable<MaterializedViewItem> MyDtoList = Enumerable.Empty<MaterializedViewItem>().AsQueryable();
var insideProperty = Expression.Parameter(typeof(MaterializedViewItem), "mviAny");
var baseProperty = Expression.Parameter(typeof(MaterializedViewItem), "mviBaseAny");
MemberExpression condition0Code = Expression.Property(baseProperty, "MvCode");
ConstantExpression condition0CodeValue = Expression.Constant("ARAPP");
var condition0 = Expression.Equal(condition0Code, condition0CodeValue);
var predicateFirstElement = Expression.Lambda<Func<T, bool>>(condition0, baseProperty);
MemberExpression conditionACode = Expression.Property(insideProperty, "MvCode");
ConstantExpression conditionACodeValue = Expression.Constant("MyCode");
var conditionA = Expression.Equal(conditionACode, conditionACodeValue);
MemberExpression conditionACol = Expression.Property(insideProperty, "ColNumber");
ConstantExpression conditionAColValue = Expression.Constant((byte)1);
var conditionB = Expression.Equal(conditionACol, conditionAColValue);
MemberExpression conditionDisplay = Expression.Property(insideProperty, "ValueDisplay");
ConstantExpression conditionDisplayValue = Expression.Constant("UserName");
var conditionC = Expression.Equal(conditionDisplay, conditionDisplayValue);
MemberExpression conditionRow = Expression.Property(insideProperty, "RowNumber");
var newValueToCompare = Expression.PropertyOrField(baseProperty, "RowNumber");
ConstantExpression conditionRowValue = Expression.Constant(0);
var conditionD = Expression.Equal(conditionRow, newValueToCompare);
var condition = Expression.AndAlso(conditionA, conditionB);
var condition2 = Expression.AndAlso(conditionC, conditionD);
var condition3 = Expression.AndAlso(condition, condition2);
var predicate = Expression.Lambda<Func<MaterializedViewItem, bool>>(condition3, insideProperty);
var callCondtions = BuildAny<MaterializedViewItem>(predicate, MyDtoList.Expression);
var myPredicate = Expression.Lambda<Func<T, bool>>(callCondtions, baseProperty);
MemberExpression conditionCol2 = Expression.Property(insideProperty, "ColNumber");
ConstantExpression conditionCol2Value = Expression.Constant((byte)3);
var conditionE = Expression.Equal(conditionCol2, conditionCol2Value);
MemberExpression conditionColDisplay2 = Expression.Property(insideProperty, "ValueDisplay");
ConstantExpression conditionColDisplay2Value = Expression.Constant("2261");
var conditionF = Expression.Equal(conditionColDisplay2, conditionColDisplay2Value);
var condition22 = Expression.AndAlso(conditionA, conditionD);
var condition23 = Expression.AndAlso(conditionE, conditionF);
var condition2Final = Expression.AndAlso(condition22, condition23);
var predicate2 = Expression.Lambda<Func<T, bool>>(condition2Final, insideProperty);
var callCondtions2 = BuildAny<T>(predicate2, MyDtoList.Expression);
Neede extra function to build for me final Any with all parameters
public static Expression BuildAny<T>(Expression<Func<T, bool>> predicate, Expression expression)
{
var overload = typeof(Queryable).GetMethods().FirstOrDefault(method => method.Name == "Any" && method.GetParameters().Count() == 2);
var specificMethod = overload.MakeGenericMethod(typeof(T));
var call = Expression.Call(
specificMethod,
expression,
predicate);
return call;
}
What is important to remember we are building IQueryable based on temporaty object. Later it has to replaced with real db table. It can be done with:
IQueryable<T> queryList = this.DbSet;
var filtersForDbSet = ExpressionTreeConstantReplacer.CopyAndReplace<DbSet<T>, T>(condition, typeof(EnumerableQuery<T>), this.DbSet);
class ExpressionTreeConstantReplacer
{
internal static Expression<Func<T2, bool>> CopyAndReplace<T, T2>(Expression<Func<T2, bool>> expression, Type originalType, T replacementConstant)
{
var modifier = new ExpressionTreeConstantReplacer<T>(originalType, replacementConstant);
var newLambda = modifier.Visit(expression) as LambdaExpression;
return Expression.Lambda<Func<T2, bool>>(newLambda.Body, newLambda.Parameters.FirstOrDefault());
}
}
and
class ExpressionTreeConstantReplacer<T> : ExpressionVisitor
{
Type originalType;
T replacementConstant;
internal ExpressionTreeConstantReplacer(Type originalType, T replacementConstant)
{
this.originalType = originalType;
this.replacementConstant = replacementConstant;
}
protected override Expression VisitConstant(ConstantExpression c)
{
return c.Type == originalType ? Expression.Constant(replacementConstant) : c;
}
}
If anyone will have similar problem in expression tree. Query is build same as in normal query. To pass some vale from main expression to inside expression you have to just show that you are comparing them as:
MemberExpression conditionRow = Expression.Property(insideProperty, "RowNumber");
var newValueToCompare = Expression.PropertyOrField(baseProperty, "RowNumber");
var conditionD = Expression.Equal(conditionRow, newValueToCompare
As i mentioned in the comment to the question, your query can be simpliefied.
[Initial note]
Accordingly to the discussion in comments, i think, your query can be still improved.
[Version #1] - first look
SELECT DISTINCT ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE CODE ='MyName'AND COL_NUMBER IN(1, 3) AND DISPLAY IN ('UserName', '2261')
ORDER BY ROW_NUMBER
Based on your SQL query, a Linq version may look like:
int[] nums = {1, 3};
string[] disp = {"UserName", "2261"};
var result = dboMVI
.Where(mvi=> mvi.Code == 'MyName' &&
nums.Any(n=> n.Contains(mvi.ColNumber)) &&
disp.Any(d=> d.Contains(mvi.Display))
.OrderBy(x=>x.RowNumber)
.Select(x=>.RowNumber);
In case when above query does not meet your criteria, you'd try to combine the conditions with parentheses:
[Version #2] - second look
SELECT DISTINCT ROW_NUMBER
FROM dbo.VIEW_ITEM item
WHERE (CODE ='MyName'AND COL_NUMBER IN(1, 3)) AND
(CODE ='MyName' AND DISPLAY IN ('UserName', '2261'))
ORDER BY ROW_NUMBER
A Linq equivalent:
var result = dboMVI
.Where(mvi=> (mvi.Code == 'MyName' &&
nums.Any(n=> n.Contains(mvi.ColNumber))) &&
(mvi.Code == 'MyName' &&
disp.Any(d=> d.Contains(mvi.Display)))
.OrderBy(x=>x.RowNumber)
.Select(x=>.RowNumber);
As you can see, both conditions have to be meet to return data.
[EDIT#2]
As to the Expression... i think it should look like this:
Expression<Func<ViewItem, string, int, string, bool>> filter = (mvi, code, colNo, disp) =>
dboMVI.Any(innerMvi =>
innerMvi.RowNumber == mvi.RowNumber &&
innerMvi.Code==code &&
innerMvi.ColNumber == colNo &&
innerMvi.Display == disp);
[Finall note]
Note: i can't access to your data and i'm not able to guarantee for 100% that above query will meet your criteria.
Consider the following code which provides two methods: One to return an IQueryable, and one which leverages a compiled query to very efficient return the location matching a specific ID:
public IQueryable<Location> GetAllLocations()
{
return from location in Context.Location
where location.DeletedDate == null
&& location.Field1 = false
&& location.Field2 = true
&& location.Field3 > 5
select new LocationDto
{
Id = location.Id,
Name = location.Name
}
}
private static Func<MyDataContext, int, Location> _getByIdCompiled;
public Location GetLocationById(int locationId)
{
if (_getByIdCompiled == null) // precompile the query if needed
{
_getByIdCompiled = CompiledQuery.Compile<MyDataContext, int, Location>((context, id) =>
(from location in Context.Location
where location.DeletedDate == null
&& location.Field1 = false
&& location.Field2 = true
&& location.Field3 > 5
&& location.Id == id
select new LocationDto {
Id = location.Id,
Name = location.Name
})).First());
}
// Context is a public property on the repository all of this lives in
return _getByIdCompiled(Context, locationId);
}
This is a pretty big simplification of the actual code, but I think it gets the idea accross, and it works fine. The next thing I want to do is refactor the code, so that the common bit of the expression can be reused, since it will be used in many other types of compiled queries. In other words, this expression:
from location in Context.Location
where location.DeletedDate == null
&& location.Field1 = false
&& location.Field2 = true
&& location.Field3 > 5
select new LocationDto
{
Id = location.Id,
Name = location.Name
};
How can I somehow capture this in a variable or function and reuse it in multiple compiled queries? My attempts so far have led to errors complaining about things not being translatable to SQL, Member access not allowed, etc.
Update: Another potentially better way I could have asked this question is as follows:
Consider the two compiled queries below:
_getByIdCompiled = CompiledQuery.Compile<MyDataContext, int, LocationDto>((context, id) =>
(from location in Context.Location // here
where location.DeletedDate == null // here
&& location.Field1 = false // here
&& location.Field2 = true // here
&& location.Field3 > 5 // here
&& location.Id == id
select new LocationDto { // here
Id = location.Id, // here
Name = location.Name
})).First()); // here
_getByNameCompiled = CompiledQuery.Compile<MyDataContext, int, LocationDto>((context, name) =>
(from location in Context.Location // here
where location.DeletedDate == null // here
&& location.Field1 = false // here
&& location.Field2 = true // here
&& location.Field3 > 5 // here
&& location.Name == name
select new LocationDto { // here
Id = location.Id, // here
Name = location.Name // here
})).First()); // here
All of the lines marked // here are duplicate very un-dry pieces of code. (In my code base, this actually 30+ lines of code.) How do I factor it out and make it reusable?
So, this whole thing is somewhat odd in that the Compile method needs to not only see the Expression objects passed to each query operator (Where, Select, etc.) as something it can understand, but it needs to see the whole query, including the use of all of the operators, as something it can comprehend as Expression objects. This pretty much removes more traditional query composition as an option.
This is going to get a tad messy; more so than I would really like, but I don't see a whole lot of great alternatives.
What we're going to do is create a method to construct our queries. It's going to accept a filter as a parameter, and that filter will be an Expression that represents a filter for some object.
Next we're going to define a lambda that will look almost exactly like what you would pass to Compile, but with an extra parameter. That extra parameter will be of the same type as our filter, and it will represent that actual filter. We'll use that parameter, instead of the filter, throughout the lambda. We'll then use a UseIn method to replace all instances of that third parameter in our new lambda with the filter expression that we are providing to the method.
Here is the method to construct the query:
private static Expression<Func<MyDataContext, int, IQueryable<LocationDto>>>
ConstructQuery(Expression<Func<Location, bool>> filter)
{
return filter.UseIn((MyDataContext context, int id,
Expression<Func<Location, bool>> predicate) =>
from location in context.Location.Where(predicate)
where location.DeletedDate == null
&& location.Field1 == false
&& location.Field2 == true
&& location.Field3 > 5
&& location.Id == id
select new LocationDto
{
Id = location.Id,
Name = location.Name
});
}
Here is the UseIn method:
public static Expression<Func<T3, T4, T5>>
UseIn<T1, T2, T3, T4, T5>(
this Expression<Func<T1, T2>> first,
Expression<Func<T3, T4, Expression<Func<T1, T2>>, T5>> second)
{
return Expression.Lambda<Func<T3, T4, T5>>(
second.Body.Replace(second.Parameters[2], first),
second.Parameters[0],
second.Parameters[1]);
}
(The typing is a mess here, and I can't figure out how to give the generic types meaningful names.)
The following method is used to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now that we've gotten through this gory mess, comes the easy part. ConstructQuery should be able to be modified at this point to represent your real query, without much difficulty.
To call this method we simply need to provide whatever filter we want applied to this alteration of the query, such as:
var constructedQuery = ConstructQuery(location => location.Id == locationId);
Linq statements have to end in either select or group clause so you can't cut out part of the query and store it elsewhere, but if you will always be filtering by the same four conditions, you can use the lambda syntax instead, and then add any additional where clauses in new queries.
And as pointed out by #Servy, you need to call First() or FirstOrDefault() to get a single element from the query result.
IQueryable<Location> GetAllLocations()
{
return Context.Location.Where( x => x.DeletedDate == null
&& x.Field1 == false
&& x.Field2 == true
&& x.Field3 > 5
).Select( x => x );
}
Location GetLocationById( int id )
{
return ( from x in GetAllLocations()
where x.Id == id
select x ).FirstOrDefault();
}
//or with lambda syntax
Location GetLocationById( int id )
{
return GetAllLocations()
.Where( x => x.Id == id )
.Select( x => x )
.FirstOrDefault();
}