i want to build a generic search window using linq to sql.
This is what i was trying to do:
class SearchWindow<T> : Form : Where T: class
{
public SearchWindow(Func<T, string> codeSelector,
Func<T, string> nameSelector)
{
var db = new DataContext();
var table = db.GetTable<T>();
var query = from item in table where
codeSelector(item).Contains(someText) &&
nameSelector(item).Contains(someOtherText)
select item;
}
}
And i was trying to use it like:
var searchWindow = new SearchWindow<SomeTable>(x => x.CodeColumn,
y => y.NameColumn).Show();
Bud saddly that doesn't work, i read about expression trees so i tried to do that with them, and i got:
public SearchWindow(codeColumn, nameColumn)
{
Table<T> table = db.GetTable<T>();
var instanceParameter = Expression.Parameter(typeof(T), "instance");
var methodInfo = typeof(string).GetMethod("Contains",
new Type[] { typeof(string) });
var codigoExpression = Expression.Call(Expression.Property(instanceParameter,
codeColumn),
methodInfo,
Expression.Constant("someText",
typeof(string)));
var nombreExpression = Expression.Call(Expression.Property(instanceParameter,
nameColumn),
methodInfo,
Expression.Constant("someOtherText",
typeof(string)));
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.And(codigoExpression, nombreExpression), instanceParameter);
var query = table.Where(predicate);
}
And to use it i need to do:
new SearchWindow<SomeTable>("codeColumn", "nameColumn");
But i don't like the approach to need to enter the column names as a string, is there any way to do it in a fashion similar to my first approach (in order to have intellisense and strong typing)?
Thank you for your help.
Untested, but something like:
static IQueryable<T> Search<T>(
IQueryable<T> source,
Expression<Func<T, string>> codeSelector,
Expression<Func<T, string>> nameSelector,
string code, string name)
{
var row = Expression.Parameter(typeof(T), "row");
var body = Expression.AndAlso(
Expression.Call(
Expression.Invoke(codeSelector, row),
"Contains", null,
Expression.Constant(code, typeof(string))),
Expression.Call(
Expression.Invoke(nameSelector, row),
"Contains", null,
Expression.Constant(name, typeof(string))));
var lambda = Expression.Lambda<Func<T, bool>>(body, row);
return source.Where(lambda);
}
You pass in your table (GetTable<T>) as the source, and lambdas to indicate the columns (x => x.CodeColumn / y => y.NameColumn etc).
Update; tested on LINQ-to-Objects, I'm hopeful it'll work on LINQ-to-SQL as well:
var data = new[] {
new { Code = "abc", Name = "def"},
new { Code = "bcd", Name = "efg"},
new { Code = "ghi", Name = "jkl"}
}.AsQueryable();
var filtered = Search(data, x => x.Code, x => x.Name, "b", "f");
var arr = filtered.ToArray();
Use PredicateBuilder- it'll do the heavy lifting for you.
Related
I want to create an extension method for a LINQ expression but I'm stuck. What I need is just to create a method which will add a specific Where clause to a Queryable. Something like:
var hierarchy = "a string";
Session.Query<SomeClass>.Where(x => x.Layer.Hierarchy.StartsWith(hierarchy) ||
x.Layer.Hierarchy == hierarchy);
to become:
var hierarchy = "a string";
Session.Query<SomeClass>.LayerHierarchy(x => x.Layer, hierarchy);
And do that Where logic inside. So basicly the extension method LayerHierarchy() is running over the Queryable of T but the subject is of type Layer:
public static IQueryable<T> LayerHierarchy<T>(this IQueryable<T> query,
Expression<Func<T, Layer>> layer,
string hierarchy)
{
var parameterExp = Expression.Parameter(typeof(Layer), "layer");
var propertyExp = Expression.Property(parameterExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
var startsWith = Expression.Lambda<Func<Layer, bool>>(methodExpStartsWith, parameterExp);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var equals = Expression.Lambda<Func<Layer, bool>>(methodExpEquals, parameterExp);
return query
.Where(startsWith)
.Where(equals);
}
Everything works fine above the return line. It complains that...
Cannot convert from System.Linq.Expressions.Expression<System.Func<Layer, bool>> to System.Linq.Expressions.Expression<System.Func<T, int, bool>>
when trying to pass the expressions to query.Where() method. How can I fix it?
Well, the problem is how you are creating the Lambdas. They should begin from T, not from Layer:
var startsWith = Expression.Lambda<Func<T, bool>>(methodExpStartsWith, parameterExp);
var equals = Expression.Lambda<Func<T, bool>>(methodExpEquals, parameterExp);
However, in order for this to work, you are missing one more PropertyExpression.
Your query now looks like:
(Layer)x => x.Hierarchy.StartsWith(...)
When, what you want is this:
(T)x => x.Layer.Hierarchy.StartsWith(...)
So, use this instead:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
Your logic should change a little though, since two .Where will generate an AND condition between them, and it seems like you want one of them to be true (StartsWith or Equals), so:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var orElseExp = Expression.OrElse(methodExpStartsWith, methodExpEquals);
var orElse = Expression.Lambda<Func<T, bool>>(orElseExp, parameterExp);
return query.Where(orElse);
I'm trying to figure out for a IQueryable how I can build a csv file by dynamically selecting objects as strings.
for example:
I read this about dynamically selecting properties of a T ...
LINQ : Dynamic select
That would allow me to do something like this ...
var data = new List<T> { items };
var fields = new string[] { "Field1", "Field2", "Field3" };
// build row strings
var rows = set.Select(BuildRowObjectExpression<T, ProjectionOfT>(fields))
.Select(i => Serialise<ProjectionOfT>(i));
string Serialise<T>(T i, string separator)
{
var properties = typeof(T).GetProperties();
var values = new List<string>();
foreach (var p in properties)
values.Add(p.GetValue(i).ToString());
return string.Join(separator, values);
}
Func<T, Tout> BuildRowObjectExpression<T, Tout>(string[] fields)
{
// input parameter "o"
var xParameter = Expression.Parameter(typeof(T), "o");
// new statement "new Data()"
var xNew = Expression.New(typeof(T));
// create initializers
var bindings = fields.Select(o => {
// property "Field1"
var mi = typeof(T).GetProperty(o);
// original value "o.Field1"
var xOriginal = Expression.Property(xParameter, mi);
// set value "Field1 = o.Field1"
return Expression.Bind(mi, xOriginal);
}
);
// initialization "new T { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit(xNew, bindings);
// expression "o => new T { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<T, string>>(xInit, xParameter);
// compile to Func<T, string>
return lambda.Compile();
}
What I was wondering however is:
How do I build this as an expression / func that I can use with an IQueryable to do something like this
// this would build me a string array from the specified properties
// in a collection of T joining the values using the given separator
var results = data.Select(i => BuildString(fields, "|")).ToArray();
I would ideally like to use this with an entity set.
String conversion/concatenation is not a database job. You'd better keep the two parts separate - data retrieval in database query and data transformation in memory query.
For instance, you can use the following custom extensions methods:
public static class Extensions
{
public static IQueryable<T> Select<T>(this IQueryable source, string[] fields)
{
var parameter = Expression.Parameter(source.ElementType, "o");
var body = Expression.MemberInit(
Expression.New(typeof(T)),
fields.Select(field => Expression.Bind(
typeof(T).GetProperty(field),
Expression.PropertyOrField(parameter, field))
)
);
var selector = Expression.Lambda(body, parameter);
var expression = Expression.Call(
typeof(Queryable), "Select", new[] { parameter.Type, body.Type },
source.Expression, Expression.Quote(selector)
);
return source.Provider.CreateQuery<T>(expression);
}
public static IEnumerable<string> Serialize<T>(this IEnumerable<T> source, string separator)
{
var properties = typeof(T).GetProperties();
return source.Select(item => string.Join(separator, properties.Select(property => property.GetValue(item))));
}
}
like this
var results = db.Data.Select<ProjectionOfT>(fields).Serialize("|");
If you want to avoid the ProjectionOfT class, there is no easy way to do that since it requires dynamic runtime class generation, so you'd better resort to System.Linq.Dynamic package.
I have been trying to write a method that will build an expression based on types and parameters passed in. Currently the method is:
// tuple: CollectionName, ClassName, PropertyName
public Expression<Func<T, bool>> BuildCollectionWithLike<T, TSub>(Dictionary<Tuple<string, string, string>, string> properties)
{
// each one should generate something like:
// x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
try
{
var type = typeof(T);
List<Expression> expressions = new List<Expression>();
var xParameter = Expression.Parameter(typeof(T), "x");
foreach (var key in properties.Keys)
{
var collectionType = typeof(TSub);
var yParameter = Expression.Parameter(typeof(TSub), "y");
var propertyExp = Expression.Property(yParameter, key.Item3);
MethodInfo methodContains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(properties[key], typeof(string));
var containsMethodExp = Expression.Call(propertyExp, methodContains, someValue);
var whereProperty = type.GetProperty(key.Item1);
var wherePropertyExp = Expression.Property(xParameter, whereProperty);
Func<IEnumerable<T>, Func<T, bool>, IEnumerable<T>> whereDelegate = Enumerable.Where;
MethodInfo whereMethodInfo = whereDelegate.Method;
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
expressions.Add(whereMethodExp);
}
Expression final = expressions.First();
foreach (var expression in expressions.Skip(1))
{
final = Expression.Or(final, expression);
}
Expression<Func<T, bool>> predicate =
(Expression<Func<T, bool>>)Expression.Lambda(final, xParameter);
return predicate;
}
catch (Exception ex)
{
return null;
}
}
However at this line:
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
I get this exception:
Expression of type 'System.Collections.Generic.ICollection`1[Model.ProductPreviousSku]'
cannot be used for parameter of type 'System.Collections.Generic.IEnumerable`1[Model.Product]'
of method 'System.Collections.Generic.IEnumerable`1[Model.Product] Where[Product](System.Collections.Generic.IEnumerable`1[Model.Product], System.Func`2[Model.Product,System.Boolean])'"
My class Model.Product has a property of type ICollection called PreviousSKUs.
I have a class called ProductPreviousSku which has a property of type string called PreviousSku.
As per my comment in at the start of the method I am trying to get this method to be able to construct an expression inside the foreach loop that looks like:
x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
I'm struggling to get passed this error at the moment so any help would be fantastic !
I need to sort in ajax response grid by column name. Column value is number stored as a string.
Let's say some trivial class (in real-life situation there is no possibility to modify this class):
class TestObject
{
public TestObject(string v)
{
this.Value = v;
}
public string Value { get; set; }
}
then simple test:
[Test]
public void LambdaConstructionTest()
{
var queryable = new List<TestObject>
{
new TestObject("5"),
new TestObject("55"),
new TestObject("90"),
new TestObject("9"),
new TestObject("09"),
new TestObject("900"),
}.AsQueryable();
var sortingColumn = "Value";
ParameterExpression parameter = Expression.Parameter(queryable.ElementType);
MemberExpression property = Expression.Property(parameter, sortingColumn);
//// tried this one: var c = Expression.Convert(property, typeof(double));
LambdaExpression lambda = Expression.Lambda(property, parameter); //// constructs: o=>o.Value
var callExpression = Expression.Call(typeof (Double), "Parse", null, property);
var methodCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new[] { queryable.ElementType, property.Type },
queryable.Expression,
Expression.Quote(lambda)); // works, but sorts by string values.
//Expression.Quote(callExpression)); // getting: System.ArgumentException {"Quoted expression must be a lambda"}
var querable = queryable.Provider.CreateQuery<TestObject>(methodCallExpression);
// return querable; // <- this is the return of what I need.
}
Sorry for not being clear in my first post as #SLaks answer was correct but I do not know how to construct correct lambda expression in this case.
Finally found solution that is good for anyone who has strings in column and needs to sort by converted double value: (Special thanks for #SLaks his post was an eye opener):
[Test]
public void LambdaConstructionTest2()
{
// GIVEN
var queryable = new List<TestObject>
{
new TestObject("5"),
new TestObject("55"),
new TestObject("90"),
new TestObject("9"),
new TestObject("09"),
new TestObject("900"),
}.AsQueryable();
var sortingColumn = "Value";
// WHEN
ParameterExpression parameter = Expression.Parameter(queryable.ElementType);
MemberExpression property = Expression.Property(parameter, sortingColumn);
MethodCallExpression callExpression = Expression.Call(typeof (Double), "Parse", null, property);
LambdaExpression lambda = Expression.Lambda(callExpression, parameter); // = {Param_0 => Parse(Param_0.Value)}
UnaryExpression unaryExpression = Expression.Quote(lambda); // Expression<Func<TestObject,double>> = {Param_0 => Parse(Param_0.Value)}
var methodCallExpression = Expression.Call(
typeof (Queryable),
"OrderByDescending",
new[] { queryable.ElementType, lambda.ReturnType },
queryable.Expression,
unaryExpression);
var querable = queryable.Provider.CreateQuery<TestObject>(methodCallExpression);
// THEN
var expectedMaxValue = queryable.Max(x => Convert.ToDouble(x.Value));
var expectedMinValue = queryable.Min(x => Convert.ToDouble(x.Value));
var list = querable.ToList();
var actualMaxValue = Convert.ToDouble(list.First().Value);
var actualMinValue = Convert.ToDouble(list.Last().Value);
Assert.AreEqual(expectedMaxValue, actualMaxValue);
Assert.AreEqual(expectedMinValue, actualMinValue);
}
You can call Expression.Call() to create an expression node that calls a method.
Expression.Call(typeof(Double), "Parse", null, property)
Look the code below. I'd like to replace USERNAME by the field name received in the parameter field. This method must be able to make some search on several fields.
Thank,
public void Searching(string field, string stringToSearch)
{
var res =
from user in _dataContext.USERs where
user.USERNAME.Contains(stringToSearch)
select new
{
Id = user.ID,
Username = user.USERNAME
};
}
You need to forget about the anonymous type, maybe use Tuple<int,string> instead; but: how about:
IQueryable<Foo> source = // YOUR SOURCE HERE
// in-memory dummy example:
// source = new[] {
// new Foo {Id = 1, Bar = "abc"},
// new Foo {Id = 2, Bar = "def"}
// }.AsQueryable();
string field = "Bar";
string stringToSearch = "d";
var param = Expression.Parameter(typeof (Foo), "x");
var predicate = Expression.Lambda<Func<Foo, bool>>(
Expression.Call(
Expression.PropertyOrField(param, field),
"Contains", null, Expression.Constant(stringToSearch)
), param);
var projection = Expression.Lambda<Func<Foo, Tuple<int, string>>>(
Expression.Call(typeof(Tuple), "Create", new[] {typeof(int), typeof(string)},
Expression.PropertyOrField(param, "Id"),
Expression.PropertyOrField(param, field)), param);
Tuple<int,string>[] data = source.Where(predicate).Select(projection).ToArray();
As a matter of fact it is possible using the Expression API:
public void Searching(Expression<Func<User,string>> field, string stringToSearch)
{
var call = Expression.Call(field.Body, typeof (string).GetMethod("Contains"), new[] {Expression.Constant(value)});
Expression<Func<User, bool>> exp = Expression.Lambda<Func<User, bool>>(Expression.Equal(call, Expression.Constant(true)), field.Parameters);
var res = _dataContext.USERs.Where(exp).Select(u=>new { id= u.ID, Username = u.USERNAME});
}
What you are trying is not possible. You can however use the dynamic linq library to achieve what you want