I am trying to create an query extension which would compare a nullable int sql column value with a value. But i am struggling already over 8 hours to find any working solution.
I have already found a lot of help on this side. But all the remarks did not helped me.
I have altered the code so many times, but nothing seems to work. I want to create something similar as WHERE ManagerID IN (10,20,30)
The main code
IQueryable<Users> query = _context.CreateObjectSet<Users>();
query = query.IsMember(a => a.ManagerID, new Int32?[] { 10,20,30 });
return query.ToList();
Currently while executing the query.ToList(); it returns me a
Unable to create a constant value of type 'System.Object'. Only primitive types or enumeration types are supported in this context.
public static IQueryable<T> IsMember<T>(this IQueryable<T> source, Expression<Func<T, Int32?>> stringProperty, params Int32?[] searchTerms)
{
if (searchTerms == null || !searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
var searchTermExpression = Expression.Constant(searchTerm, typeof(object)); // <<--- This cast would make it no longer a primitive type
var containsExpression = Expression.Call(stringProperty.Body, typeof(Int32?).GetMethod("Equals"), searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
return existingExpression == null ? expressionToAdd : Expression.OrElse(existingExpression, expressionToAdd);
}
The line marked to give the constant another datatype, is indeed causing the issue, but if I dont make it of type object, the Expression will not work, as it could not match the Int32? datatype.
Can anybody help me?
thanks
=============================================
Additional info
It is indeed to had a a larger picture.
I just want to create something more dynamic which could be used on other projects also.
I would like to use some functions which will look appealing than all those multilines
query = query.Like(a => a.UserName, filter.UserName, true);
query = query.Equals(a => a.UserTown, filter.UserTown, true);
query = query.IsMember(a => a.Division, filter.Division); // is an array of possible divisions
it worked fine for Like and Equals which are string based. But want to have a similar product for (nullable) integers
I was inspired by the following post. Which created a search function (which i renamed for my project to Like)
link
I wanted to create others similar. the last boolean is to verify if nullable in the column is allowed or not.
The reason also to use an extension, is that i also have alot of filters in my filter page.
With this extension, i would easily check in the beginning of my Like and Equal function if a filter was given without checking if my filter has a value 20x.
public static IQueryable<T> Like<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, string searchTerm, bool isnullValueAllowed)
if (String.IsNullOrEmpty(searchTerm))
{
return query;
}
It's not clear why you want to create this extension as you could simply write something like:
query.Where(user => (new[]{10,20,30}).Contains(user.ManagerId)).ToList();
However, assuming the real use case is somewhat more complicated than the example you've given, could you not construct your expression as either a comparison against a constant Int32 or a comparison against null, depending on whether searchTerm.HasValue() was true?
You need to use the equality operator == rather than the Equals method here. It actually makes your expression code a tad simpler:
foreach (var searchTerm in searchTerms)
{
var comparison = Expression.Equals(stringProperty.Body,
Expression.Constant(searchTerm));
orExpression = BuildOrExpression(orExpression, comparison);
}
Of course, as is mentioned by others, you don't need to build up an expression representing an OR of each of these comparisons, you can simply use Conatains on the set in a lambda and the query provider will do all of this for you.
Related
My ultimate goal here is to make a generic version of IQueryable<T>.OrderBy() that takes a string parameter and an optional sort direction. Something like these:
return myList.OrderBy("Property1");
return myList.Orderby("Property1", SortOrder.Descending);
(The use case would be a website passing in a field the list would sort by.)
To that end, I've been trying to come up with a way of creating the following expression:
obj => obj.PropertyName
I've found a few places in StackOverflow that could help me, and they've gotten me quite close (this one in particular). Butt it doesn't quite get me all the way there. Specifically I keep getting the dreaded
Expression of type 'System.Int64' cannot be used for return type 'System.Object' for value types. I thought I could get away with return dynamic here, but that seems to have fallen over. I could do a Convert, but that seems like it should be unnecessary.
My code for generating the property selector is as follows:
public static Expression<Func<T, dynamic>> GeneratePropertySelector<T>(string propertyName)
{
var objectType = typeof(T);
var property = objectType.GetProperty(propertyName);
ParameterExpression arg = Expression.Parameter(objectType, "obj");
MemberExpression propertyExpression = Expression.Property(arg, propertyName);
// Here is the line that dies
var selectorExpression = Expression.Lambda<Func<T, dynamic>>(propertyExpression, new ParameterExpression[] { arg });
return selectorExpression;
}
And then usage inside the extension method implementation would be:
var ordered = myList.OrderBy(GeneratePropertySelector("Property1"));
For a string property, this works fine; for an int property, error.
Does anyone know how to get around this? I would have thought that dynamic would have helped here, but I'm at a loss. And given my use case, I'm trying to avoid something like this:
return myList.Orderby<int>("MyIntProperty");
Given a simple class with arbitrary properties (for discussion lets say Id, Name, and Description)
and given an instance of that class, I want to find matching entries in the database by specifying the property to match
I'm trying to do something in this respect similar to the AddOrUpdate method of EF, but I need the entity returned to me for further processing.
var match = new SomeClass{Name="Whatever"};
var found = Context.SomeClass.Find(x=>x.Name, match);
public static T Find<T>(this DbSet<T> set, Expression<Func<T, object>> matchOn, T matchAgainst) where T : class {
var func = matchOn.Compile();
var valueToFind = func(matchAgainst);
var combinedExpression = //matchon + "=" + valueToFind;
var found = set.FirstOrDefault(combinedExpression);
return found;
}
That gives me the value of the property in the passed in object, but I need to now combine that value with the passed in expression and pass it to the db set.
IE, the code I'm effectively trying to run is set.FirstOrDefault(x=>x.Name==valueToFind) How do I take the matchon expression (which contains x=>x.Name) and combine that with the ==valueToFind to get the x=>x.Name==valueToFind from them?
How do I build the combined expression? (I realize the "string" code above is completely wrong, but I was trying to get across what I need the function to do, but I have no idea what that syntax would look like.)
For manually coded examples, it would be easy enough just to pass in a hardcoded lambda with the value set, but my use case involves running through a collection of objects and finding the match for each one, so the value will not be known until runtime, and the method must work against arbitrary types and various properties, so I can't hardcode the property name either.
If you have a property selector, and a value to compare to, you can get an expression tree like this:
public static Func<TEntity, bool> GetComparer<TEntity,TProperty>(
Expression<Func<TEntity,TProperty>> selector, TProperty value)
{
var propertyRef = selector.Body;
var parameter = selector.Parameters[0];
var constantRef = Expression.Constant(value);
var comparer
= Expression.Lambda<Func<TEntity, bool>>
(Expression.Equal(propertyRef, constantRef), parameter)
.Compile();
return comparer;
}
Sample usage:
var comparer = GetComparer<Person, string>(p => p.Name, "John");
var persons = Person.GetPersons();
var john = persons.FirstOrDefault(comparer);
In linq to Entities we needed a method that works like "sql like". We have implemented our own extension method to IQueryable, because contains method not work for us because doesn't accept patterns like '%a%b%'
The created code is:
private const char WildcardCharacter = '%';
public static IQueryable<TSource> WhereLike<TSource>(this IQueryable<TSource> _source, Expression<Func<TSource, string>> _valueSelector, string _textSearch)
{
if (_valueSelector == null)
{
throw new ArgumentNullException("valueSelector");
}
return _source.Where(BuildLikeExpressionWithWildcards(_valueSelector, _textSearch));
}
private static Expression<Func<TSource, bool>> BuildLikeExpressionWithWildcards<TSource>(Expression<Func<TSource, string>> _valueSelector, string _textToSearch)
{
var method = GetPatIndexMethod();
var body = Expression.Call(method, Expression.Constant(WildcardCharacter + _textToSearch + WildcardCharacter), _valueSelector.Body);
var parameter = _valueSelector.Parameters.Single();
UnaryExpression expressionConvert = Expression.Convert(Expression.Constant(0), typeof(int?));
return Expression.Lambda<Func<TSource, bool>> (Expression.GreaterThan(body, expressionConvert), parameter);
}
private static MethodInfo GetPatIndexMethod()
{
var methodName = "PatIndex";
Type stringType = typeof(SqlFunctions);
return stringType.GetMethod(methodName);
}
This works correctly and the code is executed entirely in SqlServer, but now we would use this extension method inside where clause as:
myDBContext.MyObject.Where(o => o.Description.Like(patternToSearch) || o.Title.Like(patterToSerch));
The problem is that the methods used in the where clause have to return a bool result if is it used with operators like '||' , and I don't know how to make that the code I have created returns a bool and keep the code executed in sqlserver. I suppose that I have to convert the returned Expression by BuildLinqExpression method to bool, but I don't know how to do it
To sum up! First of all it's possible to create our own extensiond methods in Linq to Entities that execute the code in SqlServer? if this is possible how I have to do it?
Thanks for your help.
No, you cannot educate EF to process your custom extension methods, even though you have code that builds expressions that would be usable to EF.
Either:
use the SqlFunctions methods directly in your EF Where expression
use an ExpressionVisitor to combine multiple expressions into a composite (OrElse / AndAlso) expression (note this won't help you have code like you want, but it will let you use your two methods and perform a || on them - it will look complex, though)
The first is simpler and clearer.
Answered here: https://stackoverflow.com/a/10726256/84206
His code: http://pastebin.com/4fMjaCMV
Allows you to tag an extension method as [Expandable] and so long as the expression it returns works with linq2entities, it will replace your function call with the inner expression. Note that inline lambdas give errors, so I had to declare them as local variables or static variable such as the IsCurrent variable here:
static Expression<Func<PropertyHistory, bool>> IsCurrent = (p) => p.Starts <= DateTime.Now;
[ExpandableMethod]
public static IQueryable<PropertyHistory> AsOf(this IQueryable<PropertyHistory> source)
{
return source.Where(IsCurrent);
}
I'm trying to extend SqlMethods.Like method to support property name rather than property value, i wrote the following extension method :
public static bool Like(this object obj, string propertyName, string pattern)
{
var properties = obj.GetType().GetProperties().Select(p => p.Name);
if(!properties.Contains(propertyName))
throw new Exception(string.Format("Object does not contain property:{0}", propertyName));
return SqlMethods.Like(obj.GetType().GetProperty(propertyName).GetValue(obj, null).ToString(), pattern);
}
however the method throws the following exception :
Method 'Boolean Like(System.Object, System.String, System.String)' has no supported translation to SQL.
how can i write an extension method with transaction to SQL support ?
I found this answer from RichardD that is exactly the correct answer. Reposting for clarity, but original is linked below.
using System;
using System.Linq;
using System.Linq.Expressions;
public static class Extensions
{
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, string pattern)
{
if (null == source) throw new ArgumentNullException("source");
if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
var a = Expression.Parameter(typeof(T), "a");
var prop = Expression.Property(a, propertyName);
var body = Expression.Call(typeof(SqlMethods), "Like", null, prop, Expression.Constant(pattern));
var fn = Expression.Lambda<Func<T, bool>>(body, a);
return source.Where(fn);
}
}
...
.WhereLike("Description", "%a%b%c%"));
The solution uses expression trees, but all advanced LinqToSql operations will require familiarity with that.
From: http://forums.asp.net/p/1488418/3503874.aspx
What you want to do does not seem to make sense in the contxt of what SqlMethods.Like actually does. When you pass in a property of a class you are essentially telling it to translate that into the equivelent field in the SQL query. e.g.
var result = from names in db.Names
where SqlMethods.Like(names.FullName, '%Smith%')
select names;
would translate to something like:
SELECT *
FROM Names
WHERE Fullname LIKE '%Smith%'
(in practice it would be different using parameters and sp_executeSQL but coneptually that is what it would do).
If you want to pass in the name of a property what does that mean in terms of SQL, conceptually it makes no sense e.g.
SELECT *
FROM Names
WHERE --what would go here-- LIKE '%Smith%'
As such you are not going to be able to create a Linq To SQL method that creates nonsense SQL.
What are you actually trying to do, the chance is that you are going about it completely the wrong way.
Edit:hmm from your comment i think i understand what you want to do, in essense you want to be able to specify the column you are doing a LIKE comparison with at run time. You cannot do it exactly. You could use a stored procedure that used dynamic SQL and took a string parameter for the column. You could then expose this as a method on your data context class.
From my recent question, I try to centralize the domain model by including some silly logic in domain interface. However, I found some problem that need to include or exclude some properties from validating.
Basically, I can use expression tree like the following code. Nevertheless, I do not like it because I need to define local variable ("u") each time when I create lambda expression. Do you have any source code that is shorter than me? Moreover, I need some method to quickly access selected properties.
public void IncludeProperties<T>(params Expression<Func<IUser,object>>[] selectedProperties)
{
// some logic to store parameter
}
IncludeProperties<IUser>
(
u => u.ID,
u => u.LogOnName,
u => u.HashedPassword
);
Thanks,
Lambdas are great for many scenarios - but if you don't want them, perhaps simply don't use them? I hate to say it, but simple strings are tried and tested, especially for scenarios like data binding. If you want fast access, you could look at HyperDescriptor, or there are ways of compiling a delegate to the property accessors, or you can build an Expression from the string and compile it (including a cast to object if you want a known signature, rather than calling the (much slower) DynamicInvoke).
Of course, in most cases even crude reflection is fast enough, and isn't the bottleneck.
I suggest starting with the simplest code, and check it is actually too slow before worrying about it being fast. If it isn't too slow, don't change it. Any of the above options would work otherwise.
Another thought; if you are using Expression, you could do something like:
public void IncludeProperties<T>(
Expression<Func<T,object>> selectedProperties)
{
// some logic to store parameter
}
IncludeProperties<IUser>( u => new { u.ID, u.LogOnName, u.HashedPassword });
and then take the expression apart? A bit tidier, at least... here's some sample code showing the deconstruction:
public static void IncludeProperties<T>(
Expression<Func<T, object>> selectedProperties)
{
NewExpression ne = selectedProperties.Body as NewExpression;
if (ne == null) throw new InvalidOperationException(
"Object constructor expected");
foreach (Expression arg in ne.Arguments)
{
MemberExpression me = arg as MemberExpression;
if (me == null || me.Expression != selectedProperties.Parameters[0])
throw new InvalidOperationException(
"Object constructor argument should be a direct member");
Console.WriteLine("Accessing: " + me.Member.Name);
}
}
static void Main()
{
IncludeProperties<IUser>(u => new { u.ID, u.LogOnName, u.HashedPassword });
}
Once you know the MemberInfos (me.Member in the above), building your own lambdas for individual access should be trivial. For example (including a cast to object to get a single signature):
var param = Expression.Parameter(typeof(T), "x");
var memberAccess = Expression.MakeMemberAccess(param, me.Member);
var body = Expression.Convert(memberAccess, typeof(object));
var lambda = Expression.Lambda<Func<T, object>>(body, param);
var func = lambda.Compile();
Here's the shortest expression I can come up with:
public static void IncludeProperties(Expression<Action<IUser>> selectedProperties)
{
// some logic to store parameter
}
public static void S(params object[] props)
{
// dummy method to get to the params syntax
}
[Test]
public void ParamsTest()
{
IncludeProperties(u => S(
u.Id,
u.Name
));
}