Multiple Linq functions as parameter - c#

I have a method which searchs Items in the Database. Because I use it multiple times I made it generic:
public async Task Search(Expression<Func<T, bool>> criteria, Func<T, object> order)
{
return Task.Run(async () => Searchlist.AddRange((await Service.SearchAsync(criteria, false)).AsEnumerable().OrderBy(order)));
}
Because it is generic, I implement the parameter order, that they are ordered correctly. I call the method like this:
await Search(GetCriteria(), p => p.Description);
But I have some objects which are ordered by multiple (between 2 and 4) properties. So they are ordered like this:
SearchAsync(criteria, false)).AsEnumerable().OrderBy(x => x.Date).ThenBy(y => y.Nr).ThenBy(z => z.Type))
Can I create a parameter where I can put the Methods to call. Like .OrderBy(x => x.Date).ThenBy(y => y.Nr).ThenBy(z => z.Type) or only .OrderBy(x => x.Date).
Thanks

If you are able to break the current interface of your function, go with:
public async Task Search(Expression<Func<T, bool>> criteria, Func<T, object>[] order)
{
var elems = await Service.SearchAsync(criteria, false);
var sorted = elems.AsEnumerable().OrderBy(order.First());
foreach(var subOrder in order.Skip(1)){
sorted = sorted.ThenBy(subOrder);
}
Searchlist.AddRange(sorted)
}
await Search(GetCriteria(), new []{p => p.Date, p => p.Description});
If you dont want to break the current interface, go with:
public async Task Search(
Expression<Func<T, bool>> criteria,
Func<T, object> mainOrder,
params Func<T, object>[] subOrders)
{
var elems = await Service.SearchAsync(criteria, false);
var sorted = elems.AsEnumerable().OrderBy(mainOrder);
foreach(var subOrder in subOrders){
sorted = sorted.ThenBy(subOrder);
}
Searchlist.AddRange(sorted)
}
await Search(GetCriteria(), p => p.Date, p => p.Description);

Related

async/await inside LINQ where clause not working

I'm tring to make a database query inside a LINQ statement asynchronous, but I'm running into an error. The code below runs fine with out async/await
var newEntities = _repositoryMapping.Mapper.Map<List<Entry>>(entries);
newEntities = newEntities.Where(async e => await !_context.Entries.AnyAsync(c => c.Id == e.Id)).ToList();
Severity Code Description Project File Line Suppression State
Error CS4010 Cannot convert async lambda expression to delegate type
'Func<Entry, bool>'. An async lambda expression may return
void, Task or Task, none of which are convertible to
'Func<Entry,
bool>'
Other than breaking this up into a foreach loop, how can I make this work with async/await?
If you care about performance, code should be smarter. You just need to send one query and check what is already present in database.
Prepared extension which can do that in generic way:
newEntities = (await newEntities.FilterExistentAsync(_context.Entries, e => e.Id)).ToList();
Implementation is not so complex
public static class QueryableExtensions
{
public static async Task<IEnumerable<T>> FilterExistentAsync<T, TProp>(this ICollection<T> items,
IQueryable<T> dbQuery, Expression<Func<T, TProp>> prop, CancellationToken cancellationToken = default)
{
var propGetter = prop.Compile();
var ids = items.Select(propGetter).ToList();
var parameter = prop.Parameters[0];
var predicate = Expression.Call(typeof(Enumerable), "Contains", new[] { typeof(TProp) }, Expression.Constant(ids), prop.Body);
var predicateLambda = Expression.Lambda(predicate, parameter);
var filtered = Expression.Call(typeof(Queryable), "Where", new[] {typeof(T)}, dbQuery.Expression,
predicateLambda);
var selectExpr = Expression.Call(typeof(Queryable), "Select", new[] {typeof(T), typeof(TProp)}, filtered, prop);
var selectQuery = dbQuery.Provider.CreateQuery<TProp>(selectExpr);
var existingIds = await selectQuery.ToListAsync(cancellationToken);
return items.Where(i => !existingIds.Contains(propGetter(i)));
}
}
For the Exception, you can add a extension for IEnumerable to support async
public static class MyExtensions
{
public static async Task<IEnumerable<T>> Where<T>(this IEnumerable<T> source,
Func<T, Task<bool>> func)
{
var tasks = new List<Task<bool>>();
foreach (var element in source)
{
tasks.Add(func(element));
}
var results = await Task.WhenAll<bool>(tasks.ToArray());
var trueIndex = results.Select((x, index) => new { x, index })
.Where(x => x.x)
.Select(x => x.index).ToList();
var filterSource = source.Where((x, index) => trueIndex.Contains(index));
return filterSource;
}
}
Then you can use someting like below
var result = await users.Where(async x => await TestAsync(x));
Full code here https://dotnetfiddle.net/lE2swz

Moq: Get parameter value of expression predicate

I'm new to using Moq and I'm trying to get the value passed into a Moq'd method to use in the Returns method.
I was doing the following with success.
_repositoryMock.Setup(x => x.GetByOrderId(It.IsAny<string>()))
.Returns((string id) => Task.FromResult(
new Order
{
Id = id
}));
Usage in code:
var order = _repository.GetByOrderId("123");
The above worked fine and the id passed into the Returns method is the same ID I passed into the GetByOrderId method in my code.
However, I would like to make my repository more generic so I want to change my GetByOrderId to FindFirstOrDefault that takes an expression predicate instead of an ID.
Usage like this:
var order = _repository.FindFirstOrDefault( o => x.Id == "123");
With unit test changed to this:
_repositoryMock.Setup(moq => moq.FindFirst(It.IsAny<Expression<Func<Order, bool>>>()))
.Returns((Expression<Func<Order, bool>> expression) => Task.FromResult(
new Order
{
Id = // ... HOW TO GET ID LIKE THE FIRST SAMPLE?
}));
So how can I get to that ID? The "123". Is there any way?
I found a work around.
I just set up a list of Order that had all the values I knew would be in my expected result an then I applied the expression to that list to get the item I wanted.
var expected = // INIT MY EXPECTED
List<Order> orders = new List<Order>();
foreach (var expectedItem in expected.Items)
{
orders.Add(new Order
{
Id = expectedItem.Id,
});
}
Then I setup my Mock like this.
_finicityRepositoryMock.Setup(moq => moq.FindFirst(It.IsAny<Expression<Func<Order, bool>>>()))
.Returns((Expression<Func<Order, bool>> expression) =>
{
var order = orders.AsQueryable().Where(expression).FirstOrDefault();
if (order == null)
{
return Task.FromResult((Order)null);
}
return Task.FromResult(
new Order
{
BorrowerID = order.Id
});
});
You could analyze the Expression.
If you only do x => x.Id == "123" in the predicate expression, the solution could be as simple as:
mock.Setup(x => x.FindFirstOrDefault(It.IsAny<Expression<Func<Order, bool>>>()))
.Returns<Expression<Func<Order, bool>>>(
predicate =>
{
var id = (string)((ConstantExpression)(((BinaryExpression)predicate.Body).Right)).Value;
return new Order { Id = id };
});
If you also use other properties, then you need an ExpressionVisitor which helps you extract values for each property:
class PredicatePropertyValueExpressionVisitor : ExpressionVisitor
{
public Dictionary<string, object> PropertyValues { get; } = new Dictionary<string, object>();
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.Left is MemberExpression pe && pe.Member is PropertyInfo pi)
{
PropertyValues[pi.Name] = (node.Right as ConstantExpression).Value;
}
return base.VisitBinary(node);
}
}
Then mock will be:
mock.Setup(x => x.FindFirstOrDefault(It.IsAny<Expression<Func<Order, bool>>>()))
.Returns<Expression<Func<Order, bool>>>(
predicate =>
{
var visitor = new PredicatePropertyValueExpressionVisitor();
visitor.Visit(predicate);
return new Order { Id = visitor.PropertyValues["Id"].ToString() };
});

How to GroupBy by result of async Task<string> method and get a grouping by string in C#?

I have the below method (it's an extension method but not relevant to this question) and I would like to use GroupBy on the results of the method.
class MyClass
{
public async Task<string> GetRank()
{
return "X";
}
public async static Task Test()
{
List<MyClass> items = new List<MyClass>() { new MyClass() };
var grouped = items.GroupBy(async _ => (await _.GetRank()));
}
}
The type of grouped is IGrouping<Task<string>, MyClass>, however I need to group by the actual awaited result of the async method (string). Despite using await and making the lambda async, I still get IGrouping<Task<string>, ..> instead of IGrouping<string, ...>
How to use GroupBy and group by a result of async Task<string> method and get a grouping by string?
You probably are looking to await all your tasks first, then group
// projection to task
var tasks = items.Select(y => AsyncMethod(y);
// Await them all
var results = await Task.WhenAll(tasks)
// group stuff
var groups = results.GroupBy(x => ...);
Full Demo here
Note : You didnt really have any testable code so i just plumbed up something similar
Update
the reason why you example isn't working
items.GroupBy(async _ => (await _.GetRank()))
is because and async lambda is really just a method that returns a task, this is why you are getting IGrouping<Task<string>, MyClass>
You need to wait for all you tasks to finish first before you can think about doing anything with the results from the task
To further explain what is happening take a look at this SharpLab example
Your async lambda basically resolves to this
new Func<int, Task<string>>(<>c__DisplayClass1_.<M>b__0)
Here is an asynchronous version of GroupBy. It expects a task as the result of keySelector, and returns a task that can be awaited:
public static async Task<IEnumerable<IGrouping<TKey, TSource>>>
GroupByAsync<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, Task<TKey>> keySelector)
{
var tasks = source.Select(async item => (Key: await keySelector(item), Item: item));
var entries = await Task.WhenAll(tasks);
return entries.GroupBy(entry => entry.Key, entry => entry.Item);
}
It can be used like this:
class MyClass
{
public async Task<string> GetRank()
{
await Task.Delay(100);
return "X";
}
public async static Task Test()
{
var items = new List<MyClass>() { new MyClass(), new MyClass() };
var grouped = items.GroupByAsync(async _ => (await _.GetRank()));
foreach (var grouping in await grouped)
{
Console.WriteLine($"Key: {grouping.Key}, Count: {grouping.Count()}");
}
}
}
Output:
Key: X, Count: 2

Pass property getter to linq expression function

I'm trying to take a method and make it generic, and I'm a little stuck because the method uses Linq to look at elements. Here's the example method:
private List<int> GetListFromIDS(string ids, IEnumerable<SubSpace_Function> data)
{
if (string.IsNullOrEmpty(ids))
return null;
var list = ids
.Split(new char[] { ',' })
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => int.Parse(x.Trim()));
return data
.Where(x => list.Contains(x.Function_Id)))
.Select(x => x.Function_Id)
.ToList();
}
The parts that change are the type (SubSpace_Function) and the property to lookup Function_ID.
I know I can just change the SubSpace_Function part to T in the generic method signature, but since each type will have it's own property to lookup, I'm not sure how to 'pass' in something like Function_Id.
It's pretty easy to do with Func:
private List<int> GetListFromIDS<T>(string ids, IEnumerable<T> data, Func<T, IEnumerable<int>, bool> filterExpression, Func<T, int> selectExpression)
{
if (string.IsNullOrEmpty(ids))
return null;
var list = ids
.Split(',') // simplify
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => int.Parse(x.Trim()));
return data
.Where(x => filterExpression(x, list))
.Select(selectExpression)
.ToList();
}
And call using:
var data = GetListFromIDS<SubSpace_Function>(
"123,123,123",
someList,
(x, list) => list.Contains(x.Function_Id),
x => x.Function_Id);
Another way is to call the select Func inline:
private List<int> GetListFromIDS<T>(string ids, IEnumerable<T> data, Func<T, int> selectExpression)
{
if (string.IsNullOrEmpty(ids))
return null;
var list = ids
.Split(',') // simplify
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => int.Parse(x.Trim()));
return data
.Where(x => list.Contains(selectExpression(x)))
.Select(selectExpression)
.ToList();
}
And call using:
var data = GetListFromIDS<SubSpace_Function>(
"123,123,123",
someList,
x => x.Function_Id);
I know this focused on generics, but I took the approach of using an interface instead:
interface ISubSpaceFunction
{
int FunctionId { get; }
}
class Foo : ISubSpaceFunction
{
public int FunctionId => FooMethodForFunctionId();
private int FooMethodForFunctionId()
{
//do foo function id stuff
throw new NotImplementedException();//so it compiles
}
}
class Bar : ISubSpaceFunction
{
public int FunctionId => BarMethodForFunctionId();
private int BarMethodForFunctionId()
{
//do bar function id stuff
throw new NotImplementedException();//so it compiles
}
}
static class MyClass
{
private static List<int> GetListFromIds(string idsString, IEnumerable<ISubSpaceFunction> subSpaceFunctions)
{
var ids = string.IsNullOrWhiteSpace(idsString) ?
Enumerable.Empty<int>() :
idsString.Split(new[] { ',' })
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x.Trim())
.Select(int.Parse);
var idSet = new HashSet<int>(ids);
return subSpaceFunctions.Select(ssf => ssf.FunctionId)
.Where(ids.Contains)
.ToList();
}
}
class Example
{
public void Test()
{
string ids = "1, 2, 3, 4, 5";
var subSpaceFunctions = new ISubSpaceFunction[] { new Foo(), new Bar() };
var results = MyClass.GetListFromIds(ids, subSpaceFunctions);
}
}
My attitude on this and related matters is that the code to get the Property value for each particular type has to go somewhere, so it might as well go in the Type's class. This ensures that if the Property's value is needed elsewhere, there is no duplication. This also allows for mixing multiple types that satisfy ISubSpaceFunction, as is done in the example, and you could easily have the interface also specify some common method to be used elsewhere.
I also prefer returning empty collections over null when writing these kinds of LINQ based transformation methods in order to minimize null checking "down the pipeline," but a "fail fast" use case may call for a null return value.

LINQ - Writing an extension method to get the row with maximum value for each group

My application frequently needs to group a table, then return the row with the maximum value for that group. This is pretty easy to do in LINQ:
myTable.GroupBy(r => r.FieldToGroupBy)
.Select(r => r.Max(s => s.FieldToMaximize))
.Join(
myTable,
r => r,
r => r.FieldToMaximize,
(o, i) => i)
Now suppose I want to abstract this out into its own method. I tried writing this:
public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TGroupKey>> groupKeySelector,
Expression<Func<TSource, TMaxKey>> maxKeySelector)
where TMaxKey : IComparable
{
return source
.GroupBy(groupKeySelector)
.Join(
source,
g => g.Max(maxKeySelector),
r => maxKeySelector(r),
(o, i) => i);
}
Unfortunately this doesn't compile: maxKeySelector is an expression (so you can't call it on r, and you can't even pass it to Max. So I tried rewriting, making maxKeySelector a function rather than an expression:
public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TGroupKey>> groupKeySelector,
Func<TSource, TMaxKey> maxKeySelector)
where TMaxKey : IComparable
{
return source
.GroupBy(groupKeySelector)
.Join(
source,
g => g.Max(maxKeySelector),
r => maxKeySelector(r),
(o, i) => i);
}
Now this compiles. But it fails at runtime: "Unsupported overload used for query operator 'Max'." This is what I'm stuck on: I need to find the right way to pass maxKeySelector into Max().
Any suggestions? I'm using LINQ to SQL, which seems to make a difference.
First of all, I'd like to point out that what you're trying to do is even easier than you think in LINQ:
myTable.GroupBy(r => r.FieldToGroupBy)
.Select(g => g.OrderByDescending(r => r.FieldToMaximize).FirstOrDefault())
... which should make our lives a little easier for the second part:
public static IQueryable<TSource>
SelectMax<TSource, TGroupKey, TMaxKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TGroupKey>> groupKeySelector,
Expression<Func<TSource, TMaxKey>> maxKeySelector)
where TMaxKey : IComparable
{
return source
.GroupBy(groupKeySelector)
.Select(g => g.AsQueryable().OrderBy(maxKeySelector).FirstOrDefault());
}
The key is that by making your group an IQueryable, you open up a new set of LINQ methods that can take actual expressions rather than taking Funcs. This should be compatible with most standard LINQ providers.
Very interesting. Sometimes "dynamic" can cost you more in sheer development and runtime execution than it is worth (IMHO). Nonetheless, here's the easiest:
public static IQueryable<Item> _GroupMaxs(this IQueryable<Item> list)
{
return list.GroupBy(x => x.Family)
.Select(g => g.OrderByDescending(x => x.Value).First());
}
And, here's the most dynamic approach:
public static IQueryable<T> _GroupMaxs<T, TGroupCol, TValueCol>
(this IQueryable<T> list, string groupColName, string valueColName)
{
// (x => x.groupColName)
var _GroupByPropInfo = typeof(T).GetProperty(groupColName);
var _GroupByParameter = Expression.Parameter(typeof(T), "x");
var _GroupByProperty = Expression
.Property(_GroupByParameter, _GroupByPropInfo);
var _GroupByLambda = Expression.Lambda<Func<T, TGroupCol>>
(_GroupByProperty, new ParameterExpression[] { _GroupByParameter });
// (x => x.valueColName)
var _SelectParameter = Expression.Parameter(typeof(T), "x");
var _SelectProperty = Expression
.Property(_SelectParameter, valueColName);
var _SelectLambda = Expression.Lambda<Func<T, TValueCol>>
(_SelectProperty, new ParameterExpression[] { _SelectParameter });
// return list.GroupBy(x => x.groupColName)
// .Select(g => g.OrderByDescending(x => x.valueColName).First());
return list.GroupBy(_GroupByLambda)
.Select(g => g.OrderByDescending(_SelectLambda.Compile()).First());
}
As you can see, I precede my Extension Methods with an underscore. You do not need to do this, of course. Just take the general idea and run with it.
You would call it like this:
public class Item
{
public string Family { get; set; }
public int Value { get; set; }
}
foreach (Item item in _List
.AsQueryable()._GroupMaxs<Item, String, int>("Family", "Value"))
Console.WriteLine("{0}:{1}", item.Family, item.Value);
Best of luck!

Categories

Resources