I am writing a dynamic linq query to realize paging, and I am now facing a problem, I need the System.Linq.Expressions.Expression.Like function, but it does not exist in System.Linq.Expressions.Expression, here is my code.
Expression mWhereFunc; // Filter clause
ParameterExpression mLinqParam; // Linq param
// Get current request page
string mCurPage = this.Request.QueryString["page"];
if (String.IsNullOrEmpty(mCurPage))
{
mCurPage = "1";
}
mLinqParam = Expression.Parameter(typeof(ORD_Order), "p");
mWhereFunc = Expression.Equal(Expression.Property(mLinqParam,
typeof(ORD_Order).GetProperty("ItemIsValid")),
Expression.Constant(true));
string mOrderSN = this.Request.QueryString["txtOrderSN"];
if (!String.IsNullOrEmpty(mOrderSN))
{
mWhereFunc = Expression.And(mWhereFunc,
**Expression.Equal**(Expression.Property(mLinqParam,
typeof(ORD_Order).GetProperty("OrderSN")),
Expression.Constant(mOrderSN)));
}
var mLambdaWhere = (Expression<Func<ORD_Order,
bool>>)Expression.Lambda<Func<ORD_Order, bool>>(mWhereFunc,
new ParameterExpression[] { mLinqParam });
Func<ORD_Order, Int32> mLambdaOrder = p => p.OrderID;
IORD_OrderRepository rptOrder = new ORD_OrderRepository();
ICTM_CustomerRepository rptCtm = new CTM_CustomerRepository();
var list = from o in rptOrder.GetAll()
.Where(mLambdaWhere)
.OrderBy(mLambdaOrder)
.Skip((int.Parse(mCurPage) - 1) * mPageSize).Take(mPageSize)
join c in rptCtm.GetAll()
on o.CustomerID equals c.CustomerID
select new
{
o.OrderID,
o.OrderSN,
o.CustomerID,
c.ContactName,
o.Status,
o.CreateDate,
o.Description
};
Expression.Equal is the place where I want to change it to Expression.Like
Any help will be appreciated.
I modified my code like this
mWhereFunc = Expression.And(mWhereFunc, Expression.Call(
Expression.Property(mLinqParam, typeof(ORD_Order).GetProperty("OrderSN")),
typeof(String).GetMethod("Contains"),
new Expression[] { Expression.Constant(mOrderSN) }));
and it works, many thanks to all of you.
Like equivalents for linq are String.Contains, String.StartsWith etc. http://www.simonrhart.com/2008/06/using-like-in-linq-to-sql-under-c.html
if you are using NHibernate you can create a Like method and use a Expression to call that
You can check this post on how to do the "Like" extension here: NHibernate Linq Provider Extension
And then you build the "Like" Expression like this:
Expression.Call(typeof(MyLinqExtensions).GetMethod("IsLike"), Expression.Property(mLinqParam,
typeof(ORD_Order).GetProperty("OrderSN")),
Expression.Constant(mOrderSN)));
EDIT: Just as an additional comment, you should use Expression.AndAlso instead of Expression.And since the first one is the && operator and the last one is the & operator
EDIT 2: For Entity Framework check this post (Linq To Entities scenario): SQL User-Defined Functions in Entity Framework 4, I have no experience with it but it seems the same as a NH provider, after doing that then building the Linq Expression should be the same i posted before
Related
I have a requirement to work through several tables that need to be synchronized/backed up. All of these tables' classes implement ITrackModifiedDate:
interface ITrackModifiedDate
{
DateTime ModifiedDate { get; set; }
}
I need to work through these in batches, which means I need to bookmark where I last got up to. So I can sort by ModifiedDate, and just keep track of my last ModifiedDate. But there's a wrinkle in the plan: it can easily happen that multiple records have the identical ModifiedDate. That means I need a secondary identifier, and the only thing I can generically rely on being there is the key field, which is invariably a Guid.
I got some help in the Expression stuff from here, but when I tried tinkering to add in the "greater than" clause, things broke.
async Task<ICollection<T>> GetModifiedRecords<T>(DateTime modifiedSince, Guid lastId) where T : class, ITrackModifiedDate
{
var parameterExp = Expression.Parameter(typeof(T), "x");
var propertyExp = Expression.Property(parameterExp, keyField);
var target = Expression.Constant(lastId);
var greaterThanMethod = Expression.GreaterThan(propertyExp, target);
var lambda = Expression.Lambda<Func<T, bool>>(greaterThanMethod, parameterExp);
var query = db.Set<T>()
.Where(t => t.ModifiedDate > modifiedSince ||
t.ModifiedDate == modifiedSince && lambda.Invoke(t));
var orderByExp = Expression.Lambda(propertyExp, parameterExp);
var thenByMethodGeneric = typeof(Queryable)
.GetMethods()
.Single(mi => mi.Name == "ThenBy" && mi.GetParameters().Length == 2);
var thenByMethod = thenByMethodGeneric.MakeGenericMethod(typeof(T), propertyExp.Type);
// first order by date, then id
query = query.OrderBy(t => t.ModifiedDate)
.AsQueryable();
query = (IQueryable<T>)thenByMethod.Invoke(null, new object[] { query, orderByExp });
return await query.ToListAsync();
}
Attempting to run this query results in:
System.InvalidOperationException: The binary operator GreaterThan is not defined for the types 'System.Guid' and 'System.Guid'.
Oh dear. It seems Guids, like humans, don't like being compared with each other. Either that, or I'm using the wrong comparison expression.
The obvious solution that jumps to mind is to convert the Guid to a string for comparison purposes, but (a) that seems a little inefficient, and (b) I don't know how to write an Expression that converts a Guid to a string.
Is converting to a string the right way to go? If so, what Expression will do the job? If not, what's the correct approach?
The general approach is to replace the unsupported Guid operators with Guid.CompareTo(Guid) calls, e.g. instead of
guidA > guidB
use
guidA.CompareTo(guidB) > 0
In your case, replace
var greaterThanMethod = Expression.GreaterThan(propertyExp, target);
with
var compareTo = Expression.Call(propertyExp, "CompareTo", Type.EmptyTypes, target);
var greaterThanMethod = Expression.GreaterThan(compareTo, Expression.Constant(0));
This works for most of the query providers (LINQ to Objects, LINQ To Entities (EF6)). Unfortunately doesn't work with EF Core 2.x which requires a different approach. If that's the case, register the custom GuidFunctions class as explained in the linked answer, and use this instead:
var greaterThanMethod = Expression.Call(
typeof(GuidFunctions), "IsGreaterThan", Type.EmptyTypes,
propertyExp, target);
I am doing a query using linq to entities and I want to reuse the same code but with a slightly different select statement. This is the query:
return from f in GetFields<T>()
where f.Id == Id
select new Prop<V>
{
Name = f.Name,
Value = toValue(f.value)
};
where toValue is a Func that creates a new Value object. I want to use several toValue function which have this kind of body:
var toValue = f => new DerivedValueClass {
FirstField = f.FirstField,
SecondField = f.SecondField
}
So it is just simple assignment, which will be easily translated by sql to entities. However when I execute the code, linq to entities states:
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities
I guess it is not possible to call the toValue function, since it can't be translated. How can I create an expression from the function toValue in order to use it inside the linq query?
Eventually I find out that Linqkit allows to do this kind of things very easily:
Expression<Func<T, V>> toValue = a => new DerivedObject() {
// perform assignment here
}
return from f in GetFields<T>().AsExpandable() // <- LinqKit AsExpandable() extension method
where f.Id == Id
select new Prop<V>
{
Name = f.Name,
Value = toValue.Invoke(f.value) // <- LinqKit Invoke() extension method
};
It is really important that toValue is an Expression, since the compiler will create an ExpressionTree instead of simply generating a lambda expression. Linqkit use some trick in order to replace the toValue invocation with an expression tree. More info on the LinqKit site and here
After reading around I found that I could not use the .Contains method when querying CRM 2011 with the SDK Linq provider. This is a problem as I need to query for records which belong to a collection of Guid’s that get passed from my application. After more investigating I discovered LinqKit and the Predicate Builder; great! Using a single entityset in a query the predicate works fine:
var visits = Context.Crm_scheduled_visitSet.AsExpandable().Where(predicate);
The problem I have is when I expand the query to include a join to another entityset I cannot get the query to run when I include the predicate.
This is my query without the predicate which runs fine but I need to filter the Crm_scheduled_visitSet on a collection of Guid’s:
var scores = Context.Crm_scheduled_visitSet
.Join(Context.crm_business_risk_scoreSet, v => v.Crm_scheduled_visitId.Value, s => s.crm_business_risk_score_id.Id, (v, s) => new
{
OrgID = v.Crm_client_id.Id,
VisitStartdate = object.Equals(null, v.crm_actual_start_date) ? new DateTime() : v.crm_actual_start_date.Value,
SectionId = s.crm_business_risk_section_id.Id,
RisLevelId = s.crm_business_risk_level_id.Id,
RiskRatingId = s.crm_business_risk_rating_id.Id
});
What I need to do but this doesn't run:
var scores = base.Context.Crm_scheduled_visitSet.AsExpandable().Where(predicate)
.Join(base.Context.crm_business_risk_scoreSet, v => v.Crm_scheduled_visitId.Value, s => s.crm_business_risk_score_id.Id, (v, s) => new
{
OrgID = v.Crm_client_id.Id,
VisitStartdate = object.Equals(null, v.crm_actual_start_date) ? new DateTime() : v.crm_actual_start_date.Value,
SectionId = s.crm_business_risk_section_id.Id,
RisLevelId = s.crm_business_risk_level_id.Id,
RiskRatingId = s.crm_business_risk_rating_id.Id
});
I could not get this to work, resorted to using query expressions. I hope the CRM sdk Linq provider is improved in the future be more in line with features offered by Linq to Entities.
Well. you can get the visits alone first and join it with the other entity but with another
way of coding as follows.
var visits = Context.Crm_scheduled_visitSet.AsExpandable().Where(predicate);
var scores = from v in visits join r in Context.crm_business_risk_scoreSet
on v.Crm_scheduled_visitId.Value equals r.crm_business_risk_score_id.Id
select new {};
Note : Make sure to use the word "equal" in join and not to use this operator "=".
try it and waiting your feedback !
I'm drawing inspiration from this question:
Convert Linq to Sql Expression to Expression Tree
The original poster asked how to convert this to an Expression tree and got a good answer which can be seen in the above link.
List<Region> lst = (from r in dc.Regions
where r.RegionID > 2 && r.RegionDescription.Contains("ern")
select r).ToList();
How would I got about making a property with a get method that returns a bool that uses the ExpressionTree? I'd like to be able to do something like this (obviously I don't need the == true):
List<Region> lst = (from r in dc.Regions
where (r.AwesomeProperty == true)
select r).ToList();
How would I go about defining AwesomeProperty?
You would define AwesomeProperty just like any other property on your LINQ to SQL object. Assuming it is typed as bool (since you compare it to true), you would build the Where query like this:
// Build the parameter to the where clause predicate and access AwesomeProperty
var regionParameter = Expression.Parameter(typeof(Region), "region");
var awesomeProperty = Expression.Property(regionParameter, "AwesomeProperty");
// Build the where clause predicate using the AwesomeProperty access
var predicate = Expression.Lambda<Func<Region, bool>>(awesomeProperty);
// Get the table, which serves as the base query
var table = dc.Regions.AsQueryable();
// Call the Where method using the predicate and the table as the base query
var whereCall = Expression.Call(
typeof(Queryable),
"Where",
new[] { table.ElementType },
table.Expression,
predicate);
// Get an IQueryable<Region> which executes the where call on the table
var query = table.Provider.CreateQuery<Region>(whereCall);
var results = query.ToList();
I want to have a dynamic where condition.
In the following example:
var opportunites = from opp in oppDC.Opportunities
join org in oppDC.Organizations
on opp.OrganizationID equals org.OrgnizationID
where opp.Title.StartsWith(title)
select new
{
opp.OpportunityID,
opp.Title,
opp.PostedBy,
opp.Address1,
opp.CreatedDate,
org.OrganizationName
};
Some times I have Title and sometimes I don't. And also I want to add date in where clause dynamically.
For example, like this SQL:
string whereClause;
string SQL = whereClause == string.Empty ?
"Select * from someTable" : "Select * from someTable" + whereclause
You can rewrite it like this:
var opportunites = from opp in oppDC.Opportunities
join org in oppDC.Organizations on opp.OrganizationID equals org.OrgnizationID
select new
{
opp.OpportunityID,
opp.Title,
opp.PostedBy,
opp.Address1,
opp.CreatedDate,
org.OrganizationName
};
if(condition)
{
opportunites = opportunites.Where(opp => opp.Title.StartsWith(title));
}
EDIT: To answer your question in the comments, yes, you can keep appending to the original Queryable. Remember, this is all lazily executed, so at this point all it's doing it building up the IQueryable so you can keep chaining them together as needed:
if(!String.IsNullOrEmpty(title))
{
opportunites = opportunites.Where(.....);
}
if(!String.IsNullOrEmpty(name))
{
opportunites = opportunites.Where(.....);
}
You can dynamically add a where clause to your IQueryable expression like this:
var finalQuery = opportunities.Where( x => x.Title == title );
and for the date similarly.
However, you will have to wait to create your anonymous type until after you've finished dynamically added your where clauses if your anonymous type doesn't contain the fields you want to query for in your where clause.
So you might have something that looks like this:
var opportunities = from opp in oppDC.Opportunities
join org in oppDC.Organizations on
opp.OrganizationID equals org.OrgnizationID
select opp
if(!String.IsNullOrEmpty(title))
{
opportunities = opportunities.Where(opp => opp.Title == title);
}
//do the same thing for the date
opportunities = from opp in opportunities
select new
{
opp.OpportunityID,
opp.Title,
opp.PostedBy,
opp.Address1,
opp.CreatedDate,
org.OrganizationName
};
The WHERE clause could be done something like
//...
where string.IsNullOrEmpty(title) ? true : opp.Title.StartsWith(title)
//...
Dynamically returning records I don't think is possible in LINQ since it needs to be able to create a consistent AnonymousType (in the background)
Because queries are composable, you can just build the query in steps.
var query = table.Selec(row => row.Foo);
if (someCondition)
{
query = query.Where(item => anotherCondition(item));
}
If you know in advance all possible where queries like in the SQL example you have given you can write the query like this
from item in Items
where param == null ? true : ni.Prop == param
select item;
if you don't know all possible where clauses in advance you can add where dymically for example like this:
query = query.Where(item => item.ID != param);
The following questions and answers address this quite well:
Dynamic where clause in LINQ - with column names available at runtime
Is there a pattern using Linq to dynamically create a filter?
I was searching for creating a dynamic where clause in LINQ and came across a very beautifull solution on the web which uses ExpressionBuilder in C#.
I am posting it here since none of the above solution uses this approach. It helped me. Hope it helps you too
http://www.codeproject.com/Tips/582450/Build-Where-Clause-Dynamically-in-Linq