Create lambda expression for OrderBy based on string - c#

The method MyMethod as a string parameter. Based on the value of this parameter, I'd like get back an expression to use with an OrderBy. I don't find the right syntax for Expression<Func<>> to use with the dictionary (as TValue type)
public void MyMethod(string orderBy)
{
var dico = new Dictionary<string, string>
{
{ "property1", x => x.Name},
{ "property2", x => x.Age},
};
dico.TryGetValue("property1", out string myOrder);
myList.OrderBy(myOrder)......
}
Update :
var dico = new Dictionary<string, Expression<Func<Person, xxxxx>>>
{
{ "property1", x => x.Name},
{ "property2", x => x.Age},
};
Thanks,

I think you may get hints from this:
public void MyMethod(string orderBy)
{
// Assuming Product has 'Name' and 'Age' property ?
var dico = new Dictionary<string, Expression<Func<Product,object>>>
{
{ "property1", x => x.Name},
{ "property2", x => x.Age},
};
Expression<Func<Product,object>> myorder;
dico.TryGetValue(orderBy, out myOrder);
_context.Products.OrderBy(myOrder);
}

Related

Pass expression to initializer

I would like to pass an expression that represents a variable to used when instantiating an object.
Instead of:
class MyObject : IMyInterface { ... }
var list = db.MyObjects.Where(x => !x.IsDeleted).ToList();
var anotherList = list.Select(x => new AnotherObject() {
Id = x.Id,
Value = x.Value
});
I would like to make this so that a list of objects of IMyInterface can be transformed into another type of list (AnotherObject as example) using defined expressions as so:
var list = db.MyObjects
.Where(x => !x.IsDeleted)
.ToAnotherObjectList(x => x.Id, x => x.Value);
...
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<IMyInterface> list,
Expression id,
Expression value)
{
return list.Select(x => new AnotherObject() { Id = id, Value = value }).ToList();
}
I'm not sure how to accomplish this. I know I can use reflection to create objects and set properties by a string but I'm not sure how to pass expressions.
UPDATE
Well, I thought I'd have to do some reflection but it's simpler than what I was thinking. Here's my solution that works in IRL.
public static IEnumerable<AnotherObject> ToAnotherObject<T>(this IEnumerable<T> list, Func<T, int> getId, Func<T, string> getValue, Func<T, bool> getSelected = null) where T : IMyInterface
{
return list.Select(x => new AnotherObject {
Display = getValue(x),
Id = getId(x),
Selected = getSelected != null && getSelected(x),
});
}
You could use a Func<TInput,TReturn> for that. For example:
public static List<AnotherObject> ToAnotherObjectList<T>(
this IEnumerable<T> list,
Func<T, int> getId,
Func<T, object> getValue)
{
return list.Select(x => new AnotherObject() { Id = getId(x), Value = getValue(x) }).ToList();
}
Call:
list.ToAnotherObjectList(i => i.Id, i=> i.Value);
In this example I used Funcs with one parameter (of type T) and return type int/object.

orderby in foreach loop loosing scope

having some trouble with something very easy (i hope)
i am receiving an array for sorting. I've created a dictionary for the keyselector.
But i am missing the last piece to fix then ThenBy instead or re-ordering them everytime.
public List<Vehicle> OrderBy(string[] sorting, List<Vehicle> vehicles)
{
return Order(sorting, vehicles, SortingFiltersVehicle);
}
//this is a generic implementation
private List<T> Order<T>(string[] sorting, List<T> vehicles, IDictionary<string, Func<T, object>> filters)
{
if (!sorting.HasAnyValue())
return vehicles;
foreach (var orderby in sorting)
{
var key = orderby.Split("-")[0];
if (filters.ContainsKey(key.Trim()))
{
var direction = orderby.Contains("desc") ? OrderByDirection.Descending : OrderByDirection.Ascending;
vehicles = vehicles.OrderBy(filters[key], direction).ToList(); <== here is the problem
}
}
return vehicles;
}
private static readonly IDictionary<string, Func<Vehicle, object>> SortingFiltersVehicle = new Dictionary<string, Func<Vehicle, object>>
{
{ "price", v => v.DiscountedPrice },
{ "make", v => v.Make },
{ "model", v => v.Model },
{ "trimline", v => v.Trimline },
};
Untested, but this looks like it should work:
private List<T> Order<T>(string[] sorting, List<T> vehicles,
IDictionary<string, Func<T, object>> filters)
{
if (!sorting.Any()) return vehicles;
IOrderedEnumerable<T> sorted = null;
foreach (var orderby in sorting)
{
var key = orderby.Split("-")[0];
if (filters.ContainsKey(key.Trim()))
{
var desc = orderby.Contains("desc");
var filter = filters[key];
if (sorted == null) sorted = desc ? vehicles.OrderByDescending(filter) : vehicles.OrderBy(filter);
else sorted = desc ? sorted.ThenByDescending(filter) : sorted.ThenBy(filter);
}
}
return sorted?.ToList() ?? vehicles;
}

Lambda expression as inline data in xUnit

I'm pretty new to xUnit and here's what I'd like to achieve:
[Theory]
[InlineData((Config y) => y.Param1)]
[InlineData((Config y) => y.Param2)]
public void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
Basically, I have a GenericConfig object which contains Config and other kind of configurations, but I need to check that every single parameter is valid. Since they're all string, I wanted to simplify using [InlineData] attribute instead of writing N equals tests.
Unfortunately the error I'm getting is "Cannot convert lambda expression to type 'object[]' because it's not a delegate type", which is pretty much clear.
Do you have any idea on how to overcome this?
In addition to the already posted answers. The test cases can be simplified by directly yielding the lambdas.
public class ConfigTestDataProvider
{
public static IEnumerable<object[]> TestCases
{
get
{
yield return new object [] { (Func<Config, object>)((x) => x.Param1) };
yield return new object [] { (Func<Config, object>)((x) => x.Param2) };
}
}
}
This test ConfigTestDataProvider can then directly inject the lambdas.
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(Func<Config, object> func)
{
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
Actually, I was able to find a solution which is a bit better than the one provided by Iqon (thank you!).
Apparently, the InlineData attribute only supports primitive data types. If you need more complex types, you can use the MemberData attribute to feed the unit test with data from a custom data provider.
Here's how I solved the problem:
public class ConfigTestCase
{
public static readonly IReadOnlyDictionary<string, Func<Config, string>> testCases = new Dictionary<string, Func<Config, string>>
{
{ nameof(Config.Param1), (Config x) => x.Param1 },
{ nameof(Config.Param2), (Config x) => x.Param2 }
}
.ToImmutableDictionary();
public static IEnumerable<object[]> TestCases
{
get
{
var items = new List<object[]>();
foreach (var item in testCases)
items.Add(new object[] { item.Key });
return items;
}
}
}
And here's the test method:
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(string currentField)
{
var func = ConfigTestCase.testCases.FirstOrDefault(x => x.Key == currentField).Value;
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
I could maybe come up with something a bit better or cleaner, but for now it works and the code is not duplicated.
I have the problem the same to you, and I found the solution that using TheoryData class and MemberData attribute. Here is the example and I hope the code usefully:
public class FooServiceTest
{
private IFooService _fooService;
private Mock<IFooRepository> _fooRepository;
//dummy data expression
//first parameter is expression
//second parameter is expected
public static TheoryData<Expression<Func<Foo, bool>>, object> dataExpression = new TheoryData<Expression<Func<Foo, bool>>, object>()
{
{ (p) => p.FooName == "Helios", "Helios" },
{ (p) => p.FooDescription == "Helios" && p.FooId == 1, "Helios" },
{ (p) => p.FooId == 2, "Poseidon" },
};
//dummy data source
public static List<Foo> DataTest = new List<Foo>
{
new Foo() { FooId = 1, FooName = "Helios", FooDescription = "Helios Description" },
new Foo() { FooId = 2, FooName = "Poseidon", FooDescription = "Poseidon Description" },
};
//constructor
public FooServiceTest()
{
this._fooRepository = new Mock<IFooRepository>();
this._fooService = new FooService(this._fooRepository.Object);
}
[Theory]
[MemberData(nameof(dataExpression))]
public void Find_Test(Expression<Func<Foo, bool>> expression, object expected)
{
this._fooRepository.Setup(setup => setup.FindAsync(It.IsAny<Expression<Func<Foo, bool>>>()))
.ReturnsAsync(DataTest.Where(expression.Compile()));
var actual = this._fooService.FindAsync(expression).Result;
Assert.Equal(expected, actual.FooName);
}
}
Oddly delegates are not objects, but Actions or Funcs are. To do this, you have to cast the lambda to one of these types.
object o = (Func<Config, string>)((Config y) => y.Param1)
But doing this, your expression is not constant anymore. So this will prevent usage in an Attribute.
There is no way of passing lambdas as attributes.
One possible solution would be to use function calls, instead of attributes. Not as pretty, but could solve your problem without duplicate code:
private void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
[Theory]
public Test1()
{
HasConfiguration((Config y) => y.Param1);
}
[Theory]
public Test2()
{
HasConfiguration((Config y) => y.Param2);
}
public class HrcpDbTests
{
[Theory]
[MemberData(nameof(TestData))]
public void Test(Expression<Func<bool>> exp)
{
// Arrange
// Act
// Assert
}
public static IEnumerable<object[]> TestData
{
get
{
Expression<Func<bool>> mockExp1 = () => 1 == 0;
Expression<Func<bool>> mockExp2 = () => 1 != 2;
return new List<object[]>
{
new object[]
{
mockExp1
},
new object[]
{
mockExp2
}
}
}
}
}

Expression abstraction

Is it possible to avoid duplication of this method for each string field in the model I want to check for a match? If MyModel is abstracted then obviously the MyModelField in the lambda expression is not recognized anymore, so I'm thinking maybe some kind of reflection to access the field by name?
private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem)
{
var searchItemKey = searchItem.Value.ToLower();
Expression<Func<MyModel, bool>> defaultExp = s => s.MyModelField.ToLower().Contains(searchItemKey);
switch (searchItem.SearchStrategy)
{
case StrStrategy.Contains:
return defaultExp;
case StrStrategy.StartsWith:
return s => s.MyModelField.ToLower().StartsWith(searchItemKey);
case StrStrategy.EndsWith:
return s => s.MyModelField.ToLower().EndsWith(searchItemKey);
case StrStrategy.Equals:
return s => s.MyModelField.ToLower().Equals(searchItemKey);
}
return defaultStrat;
}
EDIT
I need to call the method for dynamically build predicates to use with Entity Framework queries.
If you plan to use result of MatchMyModelFieldByStrategy with Entity Framework or LINQ2SQL, the selector must be an expression instead of delegate, because underlying LINQ providers won't recognize delegate during building entity command text.
Hence, you have to build expression yourself, something like this:
(assuming, that you have similar types:)
enum SearchStrategy
{
Contains,
StartsWith,
EndsWith,
Equals
}
class SearchItem
{
public SearchStrategy SearchStrategy { get; set; }
public string Value { get; set; }
}
Here's the code, which builds the filtering expression:
static class QueryBuilder
{
private static readonly Lazy<MethodInfo> toLowerMethodInfo;
private static readonly Dictionary<SearchStrategy, Lazy<MethodInfo>> searchStrategyToMethodInfoMap;
static QueryBuilder()
{
toLowerMethodInfo = new Lazy<MethodInfo>(() => typeof(string).GetMethod("ToLower", new Type[0]));
searchStrategyToMethodInfoMap = new Dictionary<SearchStrategy, Lazy<MethodInfo>>
{
{
SearchStrategy.Contains,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("Contains", new[] { typeof(string) }))
},
{
SearchStrategy.StartsWith,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("StartsWith", new[] { typeof(string) }))
},
{
SearchStrategy.EndsWith,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("EndsWith", new[] { typeof(string) }))
},
{
SearchStrategy.Equals,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("Equals", new[] { typeof(string) }))
},
};
}
public static Expression<Func<T, bool>> MatchMyModelFieldByStrategy<T>(SearchItem searchItem, Expression<Func<T, string>> selector)
{
// "doe"
var searchItemKey = searchItem.Value.ToLower();
// _.Name.ToLower()
var toLowerCallExpr = Expression.Call(selector.Body, toLowerMethodInfo.Value);
// a method we shall use for searching
var searchMethodInfo = searchStrategyToMethodInfoMap[searchItem.SearchStrategy].Value;
// _ => _.Name.ToLower().SomeSearchMethod("doe")
return Expression.Lambda<Func<T, bool>>(
Expression.Call(toLowerCallExpr, searchMethodInfo, Expression.Constant(searchItemKey)),
selector.Parameters);
}
}
I've added a little laziness to cache reflection results, because for every MatchMyModelFieldByStrategy call they'll be the same.
Now the test entity type:
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
... and the sample code:
static void Main(string[] args)
{
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.Contains, Value = "doe" }, _ => _.Name));
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.StartsWith, Value = "doe" }, _ => _.Name));
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.EndsWith, Value = "doe" }, _ => _.Name));
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.Equals, Value = "doe" }, _ => _.Name));
Console.ReadLine();
}
You could provide a property selector function as a parameter. For example:
private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem, Func<MyModel, string> propertySelector)
{
var searchItemKey = searchItem.Value.ToLower();
Expression<Func<MyModel, bool>> defaultExp = s => propertySelector.Invoke(s).ToLower().Contains(searchItemKey);
switch (searchItem.SearchStrategy)
{
case StrStrategy.Contains:
return defaultExp;
case StrStrategy.StartsWith:
return s => propertySelector.Invoke(s).ToLower().StartsWith(searchItemKey);
case StrStrategy.EndsWith:
return s => propertySelector.Invoke(s).ToLower().EndsWith(searchItemKey);
case StrStrategy.Equals:
return s => propertySelector.Invoke(s).ToLower().Equals(searchItemKey);
}
return defaultStrat;
}
Which can be used like this:
var matchExpression = MatchMyModelFieldByStrategy(someSearchItem, model => model.MyModelField);
You can define selector for target field:
private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem, Func<MyModel, String> selector)
{
var searchItemKey = searchItem.Value.ToLower();
Expression<Func<MyModel, bool>> defaultExp = s => selector(s).ToLower().Contains(searchItemKey);
switch (searchItem.SearchStrategy)
{
case StrStrategy.Contains:
return defaultExp;
case StrStrategy.StartsWith:
return s => selector(s).ToLower().StartsWith(searchItemKey);
case StrStrategy.EndsWith:
return s => selector(s).ToLower().EndsWith(searchItemKey);
case StrStrategy.Equals:
return s => selector(s).ToLower().Equals(searchItemKey);
}
return defaultStrat;
}
And use it this way:
MatchMyModelFieldByStrategy(searchItem, x=>x.MyModelField);

Using given properties as strings

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 });
}

Categories