I want to extend the default LINQ provider for NHibernate 3 with methods of my own. I want to be able to use some methods from my POCOs. I have a component named Range which is used quite often in many of my POCOs. This nhibernate component class has a method Contains(int value) that I want to use in LINQ query expressions
Mapping:
<class name="Foo">
...
<component name="AgeRange">
<property name="Min" column="age_min" />
<property name="Max" column="age_max" />
</component>
</class>
Class
public class Range {
public int Min { get; set; }
public int Max { get; set; }
public bool Contains(int value) {
return value >= this.Min && value <= this.Max;
}
}
// this is the LINQ query I want to be able to write
// which will generate 'SELECT * FROM Foo WHERE 25 BETWEEN age_min AND age_max'
var s = from x in session.Query<Foo> where x.AgeRange.Contains(25) select x;
// I know the following works
var s = from x in session.Query<Foo> where x.AgeRange.Min <= 25 && x.AgeRange.Max >= 25 select x;
I looked at several blog posts explaining how to extend the LINQ provider but I don't know how to build the expressions required for this to work.
public class RangeContainsGenerator : BaseHqlGeneratorForMethod
{
public MemberInfo RangeMin;
public MemberInfo RangeMax;
public RangeContainsGenerator() {
SupportedMethods = new[] {
ReflectionHelper.GetMethodDefinition<Range>(x=> x.Contains(0)),
};
RangeMin = ReflectionHelper.GetProperty<Range, int>(x => x.Min);
RangeMax = ReflectionHelper.GetProperty<Range, int>(x => x.Max);
}
public override NHibernate.Hql.Ast.HqlTreeNode BuildHql(
System.Reflection.MethodInfo method,
System.Linq.Expressions.Expression targetObject,
System.Collections.ObjectModel.ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
NHibernate.Hql.Ast.HqlTreeBuilder treeBuilder,
NHibernate.Linq.Visitors.IHqlExpressionVisitor visitor) {
// The targetObject parameter contains the "Foo.AgeRange" member access expression
throw new NotImplementedException();
}
}
In the BuildHql method I don't know how to access Min and Max properties of my Range class to build a HqlTreeNode
You can a manual approach: the easiest way is to create a LINQ tree that represents what you want: arguments[0] >= targetObject.Min && arguments[1] <= targetObject.Max. Here >= is Expression.GreaterThenOrEqual, . is Expression.Property and so on.
When you have an expression tree, just apply visitor to it and return what it returns (I do not remember the exact API, but I can look into it if additional help is needed).
Another solution may be to try my little library: Expressive.
It attempts to convert method IL into expressions, so you could do a LinqToHqlGeneratorsRegistry or IRuntimeMethodHqlGenerator that tries to inline any unknown property/method.
There are a few good examples of extending the LINQ provider here: http://www.primordialcode.com/
Related
I'm designing an application using ASP.NET Web API and Entity Framework 5 and LINQ to Entities. The Web API doesn't serve up the entities directly, it converts them to a set of data transfer objects that are similar but not identical to my entities. The API will be used by a Silverlight application initially but I will have to support non-.NET clients (e.g. iOS apps) down the road. I'd also like to give the client the ability to run a robust set of queries against the API.
These requirements have lead me to consider the query object pattern. Essentially, I want to create a homegrown query object client-side, post it to the Web API, and convert the query object to a lambda expression that I can use in LINQ to Entities. This last part is what's tripping me up.
Starting with a simple comparison query, I want to be able to convert an object that looks like the following into a lambda expression at runtime.
public enum QueryOperator
{
None = 0,
GreaterThan,
GreaterThanOrEqualTo,
EqualTo,
NotEqualTo,
LessThanOrEqualTo,
LessThan
}
public class SimpleQuery<T>
{
public SimpleQuery()
{
this.Field = null;
this.Operator = QueryOperator.None;
this.Value = null;
}
public string Field { get; set; }
public QueryOperator Operator { get; set; }
public object Value { get; set; }
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
// ????
}
}
How can I do this?
I've had to do things like this in the past. Here's what I came up with:
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
return queryTarget.Where(this.GetWhereExpression<T>());
}
private Expression<Func<T, bool>> GetWhereExpression<T>()
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, this.Field);
var value = Expression.Constant(this.Value, prop.Type);
Expression compare = null;
switch(this.Operator)
{
case QueryOperator.EqualTo:
compare = Expression.Equal(prop, value);
break;
...
}
return Expression.Lambda(compare, param);
}
I have the following custom type with a method on it to determine if one span of time overlaps another
public struct DateTimeSpan
{
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public bool Overlaps(DateTimeSpan overlap)
{
//....
}
}
I am trying to write a custom HQL generator so that when I use this method within my data access LINQ queries it will generate to appropriate SQL when querying the database.
This is the start of my BaseHqlGeneratorForMethod that attempts to compare the End property of one DateTimeSpan with the other
public class DateSpanOverlapsDateTimeSpanHqlGenerator : BaseHqlGeneratorForMethod
{
public DateSpanOverlapsDateTimeSpanHqlGenerator()
{
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeSpan>(x => x.Overlaps(new DateTimeSpan()))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder builder, IHqlExpressionVisitor visitor)
{
var endTargetProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
Expression endTargetExpression = Expression.MakeMemberAccess(targetObject, endTargetProperty);
var endArgumentProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
Expression endArgumentExpression = Expression.MakeMemberAccess(arguments[0], endArgumentProperty);
return builder.GreaterThanOrEqual(visitor.Visit(endTargetExpression).AsExpression(), visitor.Visit(endArgumentExpression).AsExpression());
}
}
I have proven that the End property in the targetObject is being evaluated fine but no matter what I do I cannot get it to evaluate the End property in arguments[0]. The above code is just one example of what I have tried (and seems the most obvious given it works for the targetObject) with most things I try ending up with the exception Antlr.Runtime.NoViableAltException
One obvious different between targetObject and arguments[0] is that targetObject is of type PropertyExpression and arguments[0] is of type ConstantExpression. I assume this means there needs to be different ways to access them but I cannot work out what it is!
treeBuilder.Constant(arg.SubProperty); will cache your object so you will end up to have problem when you will run your query.
you should add an overload into your DateTimeSpan
public bool Overlaps(DateTime? start, DateTime? end)
match the signature into your DateSpanOverlapsDateTimeSpanHqlGenerator
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeSpan>(span => span.Overlaps(default(DateTime?), default(DateTime?)))
};
and get the values in this way:
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder builder, IHqlExpressionVisitor visitor)
{
var startProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.Start);
var endProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
MemberExpression targetStartExpression = Expression.MakeMemberAccess(targetObject, startProperty);
MemberExpression targetEndExpression = Expression.MakeMemberAccess(targetObject, endProperty);
HqlExpression startDateExpression = visitor.Visit(arguments[0]).AsExpression();
HqlExpression endDateExpression = visitor.Visit(arguments[1]).AsExpression();
....
}
after that business as usual:
builder.LessThanOrEqual(visitor.Visit(targetStartExpression).AsExpression(), endDateExpression)
I don't know if you ever resolved this or are still interested in an answer, however I stumbled across this question myself while looking for a solution to this exact problem, so thought I would add my solution for others.
While debugging I noticed that the target object parameter is passed in as an expression of type PropertyExpression, whereas my argument was passed in as a type of ConstantExpression, so while it's perfectly valid to add to PropertyExpression using Expression.Property, that doesn't work with a constant value.
Instead I extract the value of the constant and access the values of sub-properties directly, creating new constant expressions which I can then use to build the query expression.
I don't know if this is the best, or most elegant way to do it, but it worked for me. I hope it helps.
public class MyObject
{
public double SubProperty { get; set; }
}
private HqlTreeNode GenerateHQL(
MethodInfo method,
Expression targetObject,
ReadOnlyCollection<Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
MyObject arg = (MyObject)((ConstantExpression)arguments[0]).Value;
HqlConstant argExpression = treeBuilder.Constant(arg.SubProperty);
...
...
}
I am building dynamic linq expressions which is working fine for a single entity.
For example:
I have a class called Employee and empeduinfo
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class EmpEduInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int EmpId { get; set; }
}
I need to get all the the employees and empeduinfo class starts with "x"
I prepared expression for startswith("x")
var temp= entities.employees.Include("EmpEduInfo").Where(mydynamicexpression);
In this case it is filtering only parent table not on child.
I need to prepare generic expression so than i need to filter both parent and child objects dynamically.
Without using expression I know a solution:
var temp= (from ee in entities.Employee.Include("EmpEduInfo").Where(x => x.name.StartsWith("t"))
where ee.EmpEduInfo.Where(x => x.name.StartsWith("t")).Count()>0
select ee).ToList();
using expressions I am building generic expression to provide dynamic advance search rather than writing in each and every entity.
Here is my expression details
// Get the method information for the String.StartsWith() method
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
// Build the parameter for the expression
ParameterExpression empparam= Expression.Parameter(typeof(employee), "ename");;
// Build the member that was specified for the expression
MemberExpression field = Expression.PropertyOrField(empparam, "name");
// Call the String.StartsWith() method on the member
MethodCallExpression startsWith = Expression.Call(field, mi, Expression.Constant("t"));
var namelamda = Expression.Lambda<Func<employee, bool>>(startsWith, new ParameterExpression[] { empparam });
var temp = entities.employees.Include("empedudetails").Where(namelamda).ToList();
You can look at the Expression the compiler generates using IQueryable:
IQueryable<Employee> query =
from ee in entities.Employee ...
var expression = query.Expression;
Look at expression in a debugger to see what you need to generate - LINQPad is good for this.
You might want to simplify your query a bit first:
IQueryable<Employee> query =
from ee in entities.Employee.Include("EmpEduInfo")
where
ee.name.StartsWith("t") &&
ee.EmpEduInfo.Any(x => x.name.StartsWith("t"))
select ee;
I was trying in EF 4.0 either we have write DB extentions for the same.
Option is provided in EF 4.1
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
Thanks.
I'm using Entity Framework 4.1 Code First. In my entity, I have three date/time properties:
public class MyEntity
{
[Key]
public Id { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
[NotMapped]
public DateTime? QueryDate { get; set; }
// and some other fields, of course
}
In the database, I always have the From/To dates populated. I query against them using a simple where clause. But in the result set, I want to include the date I queried for. I need to persist this for some other business logic to work.
I'm working on an extension method to do this, but I'm running into problems:
public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity
{
// this part works fine
var newQueryable = queryable.Where(e => e.FromDate <= queryDate &&
e.ToDate >= queryDate);
// in theory, this is what I want to do
newQueryable = newQueryable.Select(e =>
{
e.QueryDate = queryDate;
return e;
});
return newQueryable;
}
This doesn't work. It works if I use an IEnumerable, but I want to keep it as IQueryable so everything runs on the database side, and this extention method can still be used in any part of another query. When it's IQueryable, I get a compile error of the following:
A lambda expression with a statement body cannot be converted to an expression tree
If this was SQL, I would just do something like this:
SELECT *, #QueryDate as QueryDate
FROM MyEntities
WHERE #QueryDate BETWEEN FromDate AND ToDate
So the question is, how can I transform the expression tree I already have to include this extra property assignment? I have looked into IQueryable.Expression and IQueryable.Provider.CreateQuery - there's a solution in there somewhere. Maybe an assignment expression can be appended to the existing expression tree? I'm not familiar enough with the expression tree methods to figure this out. Any ideas?
Example Usage
To clarify, the goal is to be able to perform something like this:
var entity = dataContext.Set<MyEntity>()
.WhereDateInRange(DateTime.Now)
.FirstOrDefault();
And have the DateTime.Now persisited into the QueryDate of the resulting row, WITHOUT having more than one row returned from the database query. (With the IEnumerable solution, multiple rows are returned before FirstOrDefault picks the row we want.)
Another Idea
I could go ahead and map QueryDate like a real field, and set its DatabaseGeneratedOption to Computed. But then I would need some way to inject the "#QueryDate as QueryDate" into the SQL created by EF's select statements. Since it's computed, EF won't try to provide values during update or insert. So how could I go about injecting custom SQL into the select statements?
Ladislav is absolutely right. But since you obviously want the second part of your question to be answered, here is how you can use Assign. This won't work with EF, though.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace SO5639951
{
static class Program
{
static void Main()
{
AdventureWorks2008Entities c = new AdventureWorks2008Entities();
var data = c.Addresses.Select(p => p);
ParameterExpression value = Expression.Parameter(typeof(Address), "value");
ParameterExpression result = Expression.Parameter(typeof(Address), "result");
BlockExpression block = Expression.Block(
new[] { result },
Expression.Assign(Expression.Property(value, "AddressLine1"), Expression.Constant("X")),
Expression.Assign(result, value)
);
LambdaExpression lambdaExpression = Expression.Lambda<Func<Address, Address>>(block, value);
MethodCallExpression methodCallExpression =
Expression.Call(
typeof(Queryable),
"Select",
new[]{ typeof(Address),typeof(Address) } ,
new[] { data.Expression, Expression.Quote(lambdaExpression) });
var data2 = data.Provider.CreateQuery<Address>(methodCallExpression);
string result1 = data.ToList()[0].AddressLine1;
string result2 = data2.ToList()[0].AddressLine1;
}
}
}
Update 1
Here is the same code after some tweaking. I got rid of the "Block" expression, that EF choked on in the code above, to demonstrate with absolute clarity that it's "Assign" expression that EF does not support. Note that Assign works in principle with generic Expression trees, it is EF provider that does not support Assign.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace SO5639951
{
static class Program
{
static void Main()
{
AdventureWorks2008Entities c = new AdventureWorks2008Entities();
IQueryable<Address> originalData = c.Addresses.AsQueryable();
Type anonType = new { a = new Address(), b = "" }.GetType();
ParameterExpression assignParameter = Expression.Parameter(typeof(Address), "value");
var assignExpression = Expression.New(
anonType.GetConstructor(new[] { typeof(Address), typeof(string) }),
assignParameter,
Expression.Assign(Expression.Property(assignParameter, "AddressLine1"), Expression.Constant("X")));
LambdaExpression lambdaAssignExpression = Expression.Lambda(assignExpression, assignParameter);
var assignData = originalData.Provider.CreateQuery(CreateSelectMethodCall(originalData, lambdaAssignExpression));
ParameterExpression selectParameter = Expression.Parameter(anonType, "value");
var selectExpression = Expression.Property(selectParameter, "a");
LambdaExpression lambdaSelectExpression = Expression.Lambda(selectExpression, selectParameter);
IQueryable<Address> finalData = assignData.Provider.CreateQuery<Address>(CreateSelectMethodCall(assignData, lambdaSelectExpression));
string result = finalData.ToList()[0].AddressLine1;
}
static MethodCallExpression CreateSelectMethodCall(IQueryable query, LambdaExpression expression)
{
Type[] typeArgs = new[] { query.ElementType, expression.Body.Type };
return Expression.Call(
typeof(Queryable),
"Select",
typeArgs,
new[] { query.Expression, Expression.Quote(expression) });
}
}
}
No, I don't think there is a solution. It is true that you can modify expression tree but you will get exactly the same exception as you got with your linq query because that query actually is what you will build in expression tree. The problem is not in expression tree but in the mapping. EF can't map QueryData to the result. Moreover you are trying to do projection. Projection can't be done to mapped entity and anonymous type can't be returned from the method.
You can off course do the select you mentioned but simply you can't map it to your entity. You must create a new type for that:
var query = from x in context.MyData
where x.FromDate <= queryDate && x.ToDate >= queryDate
select new MyDateWrapper
{
MyData = x,
QueryDate = queryDate
};
Automapper has Queryable Extensions, i think it can resolve your needs.
You can use ProjectTo to calculate property on runtime.
Ef Core 2 set value to ignored property on runtime
http://docs.automapper.org/en/stable/Queryable-Extensions.html
Example configuration:
configuration.CreateMap(typeof(MyEntity), typeof(MyEntity))
.ForMember(nameof(Entity.QueryDate), opt.MapFrom(src => DateTime.Now));
Usage:
queryable.ProjectTo<MyEntity>();
Thank you for all of the valuable feedback. It sounds like the answer is "no - you can't do it that way".
So - I figured out a workaround. This is very specific to my implementation, but it does the trick.
public class MyEntity
{
private DateTime? _queryDate;
[ThreadStatic]
internal static DateTime TempQueryDate;
[NotMapped]
public DateTime? QueryDate
{
get
{
if (_queryDate == null)
_queryDate = TempQueryDate;
return _queryDate;
}
}
...
}
public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity
{
MyEntity.TempQueryDate = queryDate;
return queryable.Where(e => e.FromDate <= queryDate && e.ToDate >= queryDate);
}
The magic is that I'm using a thread static field to cache the query date so it's available later in the same thread. The fact that I get it back in the QueryDate's getter is specific to my needs.
Obviously this isn't an EF or LINQ solution to the original question, but it does accomplish the same effect by removing it from that world.
This problem occurs in both NHibernate 2 and 3. I have a Class A that has a member set of class B. Querying the classes directly executes nicely. But when I pass one of the expressions involving class B into a method I get the following error:
System.ArgumentException: Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
As far as I can see I am passing the exact same expression into the Any() method. But for some reason they are treated differently. I have done some debugging and it looks like in the first method, the expression is treated as an expression with NodeType 'Quote', while the same expression in the 2nd method seems to be treated as an expression with NodeType 'Constant'. The parent expression of the expression in the 2nd method has a NodeType 'MemberAccess'. So it looks like the expression tree is different in the different test methods. I just don't understand why and what to do to fix this.
Classes involvend:
public class A
{
public virtual int Id { get; set; }
public virtual ISet<B> DataFields { get; set; }
}
public class B
{
public virtual int Id { get; set; }
}
Sample test code:
[TestMethod]
public void TestMethod1()
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
Console.Write("Number of records is {0}", records.Count());
}
}
[TestMethod]
public void TestMethod2()
{
GetAsWhereB(b => b.Id == 1);
}
private void GetAsWhereB(Func<B, bool> where)
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(where));
Console.Write("Number of records is {0}", records.Count());
}
}
This is one problem:
private void GetAsWhereB(Func<B, bool> where)
That's taking a delegate - you want an expression tree otherwise NHibernate can't get involved. Try this:
private void GetAsWhereB(Expression<Func<B, bool>> where)
As an aside, your query is hard to read because of your use of whitespace. I would suggest that instead of:
var records = session.Query<A>().Where<A>(a => a.DataFields.
Any(b => b.Id == 1));
you make it clear that the "Any" call is on DataFields:
var records = session.Query<A>().Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
I'd also suggest that you change the parameter name from "where" to something like "whereExpression" or "predicate". Some sort of noun, anyway :)
Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.
private void GetAsWhereB(Func<B, bool> where)
{
Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(w));
Console.Write("Number of records is {0}", records.Count());
}
}