I am trying to make a generic Duplicate linq extension method.
But I cannot get the Expression tree right.
Here is the linq statement I am trying to mimic.
var query = cars.AsQueryable().GroupBy(x => new { x.Color, x.Length }).Where(g => g.Count() > 1).SelectMany(p => p);
But I want to call my extension like this. Note that I can send as many properties as I want.(Color, length )etc…
var test = cars.AsQueryable().GetDuplicates2(new[] { "Color", "Length" });
I am getting stuck in the where expression as I am trying to get a count on an anonymous type.
The groupby expression works as expected already.
Please note I know there are lots of other ways to do this however I am trying to gain experience using the expressions. so please keep answers directed at this.
Here is my current code:
public static IEnumerable<TSource> GetDuplicates2<TSource>(this IQueryable<TSource> source, IEnumerable<string> fieldNames)
{
IQueryable groups = null;
try
{
Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
ParameterExpression sourceItem = Expression.Parameter(typeof(TSource), "x");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression e1 = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
MethodCallExpression groupByExpression = Expression.Call(typeof(Queryable), "GroupBy", new Type[] { source.ElementType, dynamicType },
Expression.Constant(source), e1);
sourceItem = Expression.Parameter(source.ElementType, "group");
Expression left = Expression.Call(sourceItem, typeof(Queryable).GetMethods().FirstOrDefault(p => p.Name == "Count"));
Expression right = Expression.Constant(0);
Expression e2 = Expression.GreaterThan(left, right);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { typeof(TSource) },
groupByExpression,
Expression.Lambda<Func<TSource, bool>>(e2, new ParameterExpression[] { sourceItem }));
sourceItem = Expression.Parameter(typeof(TSource), "p");
MethodCallExpression selectManyCallExpression = Expression.Call(
typeof(IQueryable<TSource>),
"SelectMany",
null,
whereCallExpression,
Expression.Lambda<Func<TSource, TSource>>(sourceItem, new ParameterExpression[] { sourceItem }));
groups = source.Provider.CreateQuery(selectManyCallExpression);
}
catch (Exception ex) { }
if (groups != null)
foreach (var group in groups)
foreach (var item in #group)
yield return item;
}
public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
{
Dictionary<string, PropertyInfo> sourceProperties = fieldNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
return source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType },
Expression.Constant(source), selector));
}
public static class LinqRuntimeTypeBuilder
{
private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };
private static ModuleBuilder moduleBuilder = null;
private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();
static LinqRuntimeTypeBuilder()
{
moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);
}
private static string GetTypeKey(Dictionary<string, Type> fields)
{
//TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter
string key = string.Empty;
foreach (var field in fields)
key += field.Key + ";" + field.Value.Name + ";";
return key;
}
public static Type GetDynamicType(Dictionary<string, Type> fields)
{
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try
{
Monitor.Enter(builtTypes);
string className = GetTypeKey(fields);
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var field in fields)
typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
}
catch (Exception ex)
{
}
finally
{
Monitor.Exit(builtTypes);
}
return null;
}
private static string GetTypeKey(IEnumerable<PropertyInfo> fields)
{
return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
{
return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
}
}
public class Car
{
public int Length { set; get; }
public int Width { set; get; }
public string Color { set; get; }
public string Model { set; get; }
public string Make { set; get; }
}
}
You're making it way too complicated. Just say:
public static IEnumerable<TSource> GetDuplicatesByKey<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
) {
return source.GroupBy(keySelector)
.Where(g => g.Skip(1).Any())
.SelectMany(g => g);
}
You can even have overloads that take an IEqualityComparer<TKey>, etc.
Related
I am writing a method which accepts a lambda expression as a parameter and parses its properties from left to right. The following criteria should be met:
The expression must only use simple property or fields -- no method calls or LINQ queries or anything more complex. (e.g. p => p.HomeAddress.City) The method can throw an exception if the expression does not meet these criteria.
The method should return a list of info about each property or field: name and type.
How can this be accomplished?
private List<SomeClass> ParseExpression<T1,T2>(Expression<Func<T1, T2>> func)
{
// ??
}
This:
private static IReadOnlyList<Tuple<string, Type>> ParseExpression<T1, T2>(Expression<Func<T1, T2>> func)
{
var par = func.Parameters[0];
var lst = new List<Tuple<string, Type>>();
Expression exp = func.Body;
while (exp != par)
{
if (exp.NodeType != ExpressionType.MemberAccess)
{
throw new Exception(exp.ToString());
}
MemberExpression me = (MemberExpression)exp;
MemberInfo mi = me.Member;
if (mi.MemberType == MemberTypes.Field)
{
FieldInfo fi = (FieldInfo)mi;
lst.Add(Tuple.Create(fi.Name, fi.FieldType));
}
else if (mi.MemberType == MemberTypes.Property)
{
PropertyInfo pi = (PropertyInfo)mi;
lst.Add(Tuple.Create(pi.Name, pi.PropertyType));
}
else
{
throw new Exception(exp.ToString());
}
exp = me.Expression;
}
lst.Reverse();
return lst;
}
Example:
class Cl1
{
public Cl2 Cl2;
}
class Cl2
{
public string Str { get; set; }
}
and then:
var result = ParseExpression<Cl1, string>(x => x.Cl2.Str);
foreach (var el in result)
{
Console.WriteLine($"{el.Item1}: {el.Item2}");
}
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 1 year ago.
I have a list of objects. How can I order this list using property name?
string orderbyField = "Code";
List<object> l = FillList();
l = l.OrderBy(o => orderbyField);
Can I have an extension for this problem?
If you don't have to provide the property name as a string, it's pretty simple using dynamic:
List<object> l = FillList();
l = l.OrderBy(o => ((dynamic)o).Id);
If the property name has to be a string, then it gets more a bit complicated but can be done using reflection (although it is not very efficient):
l = l.OrderBy(o => o.GetType()
.GetProperty("Code")
.GetValue(o, null));
You should also think about adding some error handling, e.g. if the property doesn't exist.
Also, if all the elements in the list have same runtime type, then it would be much more efficient to compile a getter function using expression trees and reusing it (instead of directly using reflection).
public static Func<object, object> CreateGetter(Type runtimeType, string propertyName)
{
var propertyInfo = runtimeType.GetProperty(propertyName);
// create a parameter (object obj)
var obj = Expression.Parameter(typeof(object), "obj");
// cast obj to runtimeType
var objT = Expression.TypeAs(obj, runtimeType);
// property accessor
var property = Expression.Property(objT, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return (Func<object, object>)Expression.Lambda(convert, obj).Compile();
}
and use it like:
var codeGetter = CreateGetter(l[0].GetType(), "Code"); // using the 1st element as an example
l = l.OrderBy(o => codeGetter(o));
Order by property name without type reflection
public static class IQueryableExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string
propertyName)
{
return (IQueryable<T>)OrderBy((IQueryable)source, propertyName);
}
public static IQueryable OrderBy(this IQueryable source, string propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var body = propertyName.Split('.').Aggregate<string, Expression>(x,
Expression.PropertyOrField);
var selector = Expression.Lambda
(Expression.PropertyOrField(x, propertyName), x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderBy", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source,
string propertyName)
{
return (IQueryable<T>)OrderByDescending((IQueryable)source, propertyName);
}
public static IQueryable OrderByDescending(this IQueryable source, string
propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var selector = Expression.Lambda(Expression.PropertyOrField(x,
propertyName),x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
}
public static IEnumerable<TSource> ComplexOrderBy<TSource>(this IEnumerable<TSource> source, string orderString)
{
if (string.IsNullOrWhiteSpace(orderString))
{
return source;
}
IOrderedEnumerable<TSource> orderedQuery = null;
var sortingFields = orderString.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
var propertyNames = typeof(TSource).GetProperties().Select(prop => prop.Name.ToLower()).ToImmutableHashSet();
for (var i = 0; i < sortingFields.Length; i++)
{
var sortingSet = sortingFields[i].Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
var sortBy = sortingSet[0].ToLower();
var isDescending = sortingSet.Length > 1 && sortingSet[1].Trim().ToLower() == "desc";
try
{
var propertySelector = sortBy.GetPropertySelector<TSource>();
orderedQuery = isDescending
?
(i == 0 ? source.OrderByDescending(propertySelector.Compile()) : orderedQuery.OrderByDescending(propertySelector.Compile()).ThenByDescending(propertySelector.Compile()))
:
(i == 0 ? source.OrderBy(propertySelector.Compile()) : orderedQuery.ThenBy(propertySelector.Compile()));
}
// Just ignoring illegal properties for simplicity
catch (ArgumentNullException) { }
catch (ArgumentException) { }
}
return orderedQuery ?? source;
}
public static Expression<Func<T, object>> GetPropertySelector<T>(this string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(T), "x");
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = Expression.Property(body, member);
}
return Expression.Lambda<Func<T, object>>(Expression.Convert(body, typeof(object)), parameterExpression);
}
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 1 year ago.
I have a list of objects. How can I order this list using property name?
string orderbyField = "Code";
List<object> l = FillList();
l = l.OrderBy(o => orderbyField);
Can I have an extension for this problem?
If you don't have to provide the property name as a string, it's pretty simple using dynamic:
List<object> l = FillList();
l = l.OrderBy(o => ((dynamic)o).Id);
If the property name has to be a string, then it gets more a bit complicated but can be done using reflection (although it is not very efficient):
l = l.OrderBy(o => o.GetType()
.GetProperty("Code")
.GetValue(o, null));
You should also think about adding some error handling, e.g. if the property doesn't exist.
Also, if all the elements in the list have same runtime type, then it would be much more efficient to compile a getter function using expression trees and reusing it (instead of directly using reflection).
public static Func<object, object> CreateGetter(Type runtimeType, string propertyName)
{
var propertyInfo = runtimeType.GetProperty(propertyName);
// create a parameter (object obj)
var obj = Expression.Parameter(typeof(object), "obj");
// cast obj to runtimeType
var objT = Expression.TypeAs(obj, runtimeType);
// property accessor
var property = Expression.Property(objT, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return (Func<object, object>)Expression.Lambda(convert, obj).Compile();
}
and use it like:
var codeGetter = CreateGetter(l[0].GetType(), "Code"); // using the 1st element as an example
l = l.OrderBy(o => codeGetter(o));
Order by property name without type reflection
public static class IQueryableExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string
propertyName)
{
return (IQueryable<T>)OrderBy((IQueryable)source, propertyName);
}
public static IQueryable OrderBy(this IQueryable source, string propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var body = propertyName.Split('.').Aggregate<string, Expression>(x,
Expression.PropertyOrField);
var selector = Expression.Lambda
(Expression.PropertyOrField(x, propertyName), x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderBy", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source,
string propertyName)
{
return (IQueryable<T>)OrderByDescending((IQueryable)source, propertyName);
}
public static IQueryable OrderByDescending(this IQueryable source, string
propertyName)
{
var x = Expression.Parameter(source.ElementType, "x");
var selector = Expression.Lambda(Expression.PropertyOrField(x,
propertyName),x);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] {
source.ElementType, selector.Body.Type },
source.Expression, selector
));
}
}
public static IEnumerable<TSource> ComplexOrderBy<TSource>(this IEnumerable<TSource> source, string orderString)
{
if (string.IsNullOrWhiteSpace(orderString))
{
return source;
}
IOrderedEnumerable<TSource> orderedQuery = null;
var sortingFields = orderString.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
var propertyNames = typeof(TSource).GetProperties().Select(prop => prop.Name.ToLower()).ToImmutableHashSet();
for (var i = 0; i < sortingFields.Length; i++)
{
var sortingSet = sortingFields[i].Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
var sortBy = sortingSet[0].ToLower();
var isDescending = sortingSet.Length > 1 && sortingSet[1].Trim().ToLower() == "desc";
try
{
var propertySelector = sortBy.GetPropertySelector<TSource>();
orderedQuery = isDescending
?
(i == 0 ? source.OrderByDescending(propertySelector.Compile()) : orderedQuery.OrderByDescending(propertySelector.Compile()).ThenByDescending(propertySelector.Compile()))
:
(i == 0 ? source.OrderBy(propertySelector.Compile()) : orderedQuery.ThenBy(propertySelector.Compile()));
}
// Just ignoring illegal properties for simplicity
catch (ArgumentNullException) { }
catch (ArgumentException) { }
}
return orderedQuery ?? source;
}
public static Expression<Func<T, object>> GetPropertySelector<T>(this string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(T), "x");
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = Expression.Property(body, member);
}
return Expression.Lambda<Func<T, object>>(Expression.Convert(body, typeof(object)), parameterExpression);
}
I have created a generic search extension method for IQueryable that enables you to search for a single property to see if a search term is contained within it.
http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable
I now want to enable the user to select multiple properties to search within each, matching if any property contains the text.
The code:
The user enters the following code to perform this search:
string searchTerm = "Essex";
context.Clubs.Search(searchTerm, club => club.Name, club => club.County)
//Note: If possible I would rather something closer to the following syntax...
context.Clubs.Search(club => new[]{ club.Name, club.County}, searchTerm);
// ... or, even better, something similar to this...
context.Clubs.Search(club => new { club.Name, club.County}, searchTerm);
This will return any golf club with 'Essex' in the Name or as the County.
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
//Build parameters
var parameters = stringProperties.SelectMany(prop => prop.Parameters);
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
//Build or expression for each property
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
var methodCallExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters));
return source.Provider.CreateQuery<TSource>(methodCallExpression);
}
The error
If I change the number of parameters supplied to 1:
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters.First()));
I get a new error:
UPDATE
I have written a post on the work discussed in this question. Check it out on GitHub too.
Here we go; you were pretty close - as I noted in comments, the key piece here is to use ExpressionVisitor to re-write the trees in terms of the single parameter you want to keep:
using System;
using System.Linq;
using System.Linq.Expressions;
static class Program
{
static void Main()
{
var data = new[] { new Foo { A = "x1", B = "y1", C = "y1" }, new Foo { A = "y2", B = "y2", C = "y2" },
new Foo { A = "y3", B = "y3", C = "x3" } }.AsQueryable();
var result = data.Search("x", x => x.A, x => x.B, x => x.C);
foreach (var row in result)
{
Console.WriteLine("{0}, {1}, {2}", row.A, row.B, row.C);
}
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
public class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
public static Expression Swap(Expression body, Expression from, Expression to)
{
return new SwapVisitor(from, to).Visit(body);
}
}
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
if (stringProperties.Length == 0) return source.Where(x => false);
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var param = stringProperties[0].Parameters.Single();
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
// re-write the property using the param we want to keep
var body = SwapVisitor.Swap(stringProperty.Body, stringProperty.Parameters.Single(), param);
var checkContainsExpression = Expression.Call(
body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
else
{ // compose
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
}
var lambda = Expression.Lambda<Func<TSource, bool>>(orExpression, param);
return source.Where(lambda);
}
}
I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}