Collection Selector as Lambda Expression with SelectMany - c#

Combinding an enumerable and a queryable via a Cross Join can be written in Linq like this with SelectMany:
SomeClass[] AnArray = new SomeClass[] {
new SomeClass { ... },
....
};
IQueryable<SomeClass> Outer = AnArray.AsQueryable();
IQueryable<SomeEntity> Inner = <DbConext>.SomeEntity;
var Crossed = Outer.SelectMany(
x => Inner,
(outer, inner) => new { Outer = outer, Inner = inner }
);
Not sure how to create the Collection Selector with Expression Trees?
// Collection Selector
var OuterAsX = Expression.Parameter(
Outer.ElementType,
"x"
);
var CollectionSelector = Expression.Lambda(
? // <- Some sort of Member Access on Inner
OuterAsX
);
....
MethodInfo SelectMethod = (typeof(Queryable))
.GetMethods()
.Where(
method => method.Name == "SelectMany"
&& method.IsGenericMethod
&& method.GetParameters().Length == 3
)
.OrderBy(x => x.ToString().Length)
.First()
.MakeGenericMethod(
Outer.ElementType,
Inner.ElementType,
ResultType
);
IQueryable Query = Inner.Provider.CreateQuery(
Expression.Call(
null,
SelectMethod,
Outer.Expression,
CollectionSelector,
ResultSelector
)
);
Looking at the Expression from Linq (i.e. the Crossed variable) it defines a Constant around class that has two fields, one of which is the Entity (i.e. Inner), making a member access expression:
Operand {x => value(Charting.Tests.Engine.GraphTest+<>c__DisplayClass33_0).Inner}
DebugView ".Constant<Charting.Tests.Engine.GraphTest+<>c__DisplayClass33_0>(Charting.Tests.Engine.GraphTest+<>c__DisplayClass33_0).Inner"

Related

Can Not Convert From Expression<Func<T, decimal>> To Func<T, decimal> In Sum Method in Lambda Expression

In the following code when I want to get sum for sumField
I'm getting an error(Can Not Convert From Expression<Func<T, decimal>> To Func<T, decimal>)
If I remove the Expression from the groupBy and sumField parameters, my problem will be solved, but in this case, all the data will be sent to the application as IEnumrable and then group by
How can I do this operation Iqueryable??
public virtual async Task<Dictionary<TKey, decimal>> SumAsync<TKey>(
Expression<Func<T, TKey>> groupBy,
Expression<Func<T, decimal>> sumField,
Expression<Func<T, bool>> predicate = null,
List<Expression<Func<T, object>>> includes = null,
string includeString = null)
{
IQueryable<T> query = DbSet;
if (includes != null) query = includes.Aggregate(query, (current, include) => current.Include(include));
if (!string.IsNullOrWhiteSpace(includeString))
{
var incluseSplit = includeString.Split(',');
query = incluseSplit.Aggregate(query, (current, include) => current.Include(include));
}
if (predicate != null) query = query.Where(predicate);
var group = query.GroupBy(groupBy)
.Select(g =>
new
{
Key = g.Key,
SumValue = g.Sum(sumField)
}
)
.AsQueryable();
return group.ToDictionary(s => s.Key, s => s.SumValue);
}
Can you not use a different overload of .GroupBy() as follows:
...
var group = query.GroupBy(groupBy, sumField) // sumField is used to select the elements returned in the grouping
.Select(g =>
new
{
Key = g.Key,
SumValue = g.Sum() // Now only .Sum() is required
}
); // and no .AsQueryable() necessary
...
I would also note that your method is marked async, but does not await anything, so will run synchronously. You might want to use .ToDictionaryAsync() at the end instead.
It is difficult, but possible, to do that without LINQKit. So first variant is with LINQKit, other need dynamic Expression Tree composing.
Activate LINKQKit via DbContextOptions:
builder
.UseSqlServer(connectionString)
.WithExpressionExpanding(); // enabling LINQKit extension
Then we can use LINQKit extension Invoke
public virtual Task<Dictionary<TKey, decimal>> SumAsync<TKey>
(
Expression<Func<T, TKey>> groupBy,
Expression<Func<T, decimal>> sumField,
Expression<Func<T, bool>> predicate = null
)
{
IQueryable<T> query = DbSet;
if (predicate != null)
query = query.Where(predicate);
var group = query
.GroupBy(groupBy)
.Select(g =>
new
{
Key = g.Key,
SumValue = g.Sum(e => sumField.Invoke(e))
}
);
return group.ToDictionaryAsync(s => s.Key, s => s.SumValue);
}
Also removed Includes staff, it is completely ignored by EFCore when you use GroupBy or Select

Converting SQL with LEFT JOIN to Linq (Method Syntax)

It's my first post here, so if I get anything wrong let me know and I'll fix it.
I'm struggling to convert a simple SQL statement with a left join, to a LINQ statement (Method syntax). I cannot use Linquer since this is a .Net Core 5.0 MVC project.
Consider that I have two tables:
dbo.OrganisationChannel (Id, OrganisationId, ChannelId)
dbo.Channel (Id, ChannelName, ChannelUrl)
I want to show all channels that an organisation DOESN'T currently have.
Here is the correct SQL query
SELECT c.Id, c.ChannelName, c.ChannelUrl
FROM dbo.Channel c
LEFT JOIN dbo.OrganisationChannel oc ON c.Id = oc.ChannelId
WHERE oc.ChannelId IS NULL OR oc.OrganisationId <> 1
However, the corresponding .GroupJoin and .SelectMany is perplexing me.. I can't find the right place to add the WHERE clauses:
var groupItems = db.Channel
.GroupJoin(
db.OrganisationChannel,
c => c.Id,
oc => oc.ChannelId,
(c, oc) => new { c, oc })
.SelectMany(
x => x.oc.DefaultIfEmpty(),
(chan, orgChan) => new
{
Id = chan.c.Id,
ChannelName = chan.c.ChannelName,
ChannelUrl = chan.c.ChannelUrl,
IsActive = chan.c.IsActive,
}
);
I'd be grateful for any help here, thanks!
Si
Method syntax with LEFT JOIN is a nightmare. If you really want method syntax install Reshaper and click "convert to method chain". But I do not recommend to do that - query become unmaintainable.
Your query is simple with query syntax
var query =
from c in db.Channel
join oc in db.OrganisationChannel on c.Id equals oc.ChannelId into gj
from oc in gj.DefaultIfempty()
where (int?)oc.ChannelId == null || oc.OrganisationId != 1
select new
{
c.Id,
c.ChannelName,
c.ChannelUrl
};
You can use the LeftJoin extension method :
public static IQueryable<TResult> LeftJoin<TResult, Ta, Tb, TKey>(this IQueryable<Ta> TableA, IEnumerable<Tb> TableB, Expression<Func<Ta, TKey>> outerKeySelector, Expression<Func<Tb, TKey>> innerKeySelector, Expression<Func<JoinIntermediate<Ta, Tb>, Tb, TResult>> resultSelector)
{
return TableA.GroupJoin(TableB, outerKeySelector, innerKeySelector, (a, b) => new JoinIntermediate<Ta, Tb> { Value = a, ManyB = b }).SelectMany(intermediate => intermediate.ManyB.DefaultIfEmpty(), resultSelector);
}
public class JoinIntermediate<Ta, Tb>
{
public Ta Value { get; set; }
internal IEnumerable<Tb> ManyB { get; set; }
}
It's usage is similar to the Join extension method but will perform a left join instead of a regular join. Then you can add your call to the Where method right after the call to LeftJoin.
Use the following query instead of lambda expressions
from left in lefts
join right in rights on left equals right.Left into leftRights
from leftRight in leftRights.DefaultIfEmpty()
select new { }
check this url https://dotnettutorials.net/lesson/left-outer-join-in-linq/
also my working code example:
UserApiKeys
.Where(w => w.AppID == AppID && w.IsActive)
.Join(
UserApiApplications,
keys => keys.AppID,
apps => apps.AppID,
(keys, apps) => new { UserApiKeys = keys, UserApiApplications = apps}
)
.OrderByDescending(d => (d.UserApiKeys.ExpirationDate ?? DateTime.MaxValue))
.Select(s => new {
ApiKey = s.UserApiKeys.ApiKey,
IsActive = s.UserApiKeys.IsActive,
SystemName = s.UserApiKeys.SystemName,
ExpirationDate = (s.UserApiKeys.ExpirationDate == null)
? "Newer Expires"
: s.UserApiKeys.ExpirationDate.ToString(),
s.UserApiApplications
})
.ToList()
in addition, to refer #nalka post about extension method usage:
NotificationEvents
.Where(w => w.ID == 123)
.LeftJoin(
Events,
events => events.EventID, ev => ev.EventID,
(events, ev) => new { NotificationEvents = events, Events = ev }
);

Build dynamic Lambda Expression for comparing undefined number of values

In short what I want do accomplish is to load Tasks from a project in SharePoint Project Server using CSOM.
var projects = ctx.LoadQuery(ctx.Projects
.Where(p => p.Id == projGuid)
.Include(
p => p.Id, p => p.Name,
p => p.Tasks
.Where(t => t.Id == taskGuid)
.Include(t => t.Name))
);
ctx.ExecuteQuery();
My Problem is with this part .Where(t => t.Id == taskGuid). It works how it should if I only want to load 1 Task but would not work if I want to load more then one. Sure I could write it like that .Where(t => t.Id == taskGuid1 || t.Id == taskGuid2 || ... )
But that wouldn't be dynamic.
What I tried was to use an array and the look if the array GuidArray.Contains(p.Id)
But I get an error if I try to use .Contains() inside the Where() expression.
ClientRequestException: The 'Contains' member cannot be used in the expression.
So I was thinking if it is possible to somehow create the lambda expression based on the number of tasks I want to load.
I regards to creation of lambda, you create the dynamic or condition you are looking for like so
public static class ExpressionExt
{
public static IQueryable<T> Where<T,TKey>(this IQueryable<T> data, string prop,params TKey[] guids)
{
var param = Expression.Parameter(typeof(T));
var exp = guids.Select(g => Expression.Equal(Expression.Property(param, prop), Expression.Constant(g))).Aggregate((a, e) => a != null ? Expression.Or(e, a) : e);
var lambda = Expression.Lambda<Func<T, bool>>(exp, param);
return data.Where(lambda);
}
}
And use it like Where(nameof(A.Id), guids) this is what I usually do when the IQueryable only supports or and not contains. There might a contains implementation there so you might want to check the documentation.

LINQ select from Queryable joining a List

I am using EF to join on a table using a list.
I have the attendance table :
Attendance
----------
UserBaseId
ClassroomID
Attendance Status ...etc
Also,
I have an attendance IEnumerable in memory, of the same structure, let's call it newAttendance.
I need to find all records from the attendance table which matches the UserBaseId and ClassroomId in the newAttendance List.
so far I have tried this,
var entriesInAttendanceTable = context.Attendance.Where(
x => (newAttendance .Select(i => i.UserBaseId).Contains(x.UserBaseId))
&& newAttendance .Select(i => i.ClassRoomId).Contains(x.ClassRoomId)
).ToList();
this results in the following SQL query:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[ClassRoomId] AS [ClassRoomId],
[Extent1].[UserBaseId] AS [UserBaseId],
[Extent1].[CreatedOn] AS [CreatedOn],
[Extent1].[UpdatedOn] AS [UpdatedOn],
[Extent1].[UpdatedByUser_Id] AS [UpdatedByUser_Id],
[Extent1].[CreatedByUser_Id] AS [CreatedByUser_Id]
FROM [dbo].[Attendance] AS [Extent1]
WHERE ( EXISTS (SELECT
1 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
WHERE 1 = 0
)) AND ( EXISTS (SELECT
1 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable2]
WHERE 1 = 0
))
Also tried join but it didnt work.
TIA
I believe this should do what you want:
var entriesInAttendanceTable = context.Attendance.Where(x => (newAttendance.Any(
y => y.UserBaseId == x.UserBaseId && y.ClassRoomId == x.ClassRoomId)));
In general this is not supported, so you either need to
(A) build and execute UNION query like this:
var entriesInAttendanceTable = newAttendance
.Select(y => context.Attendance.Where(x => y.FirstName == x.FirstName && y.LastName == x.LastName))
.Aggregate(Queryable.Union)
.ToList();
(B) build and execute OR based query like this:
Helpers:
public static class QueryableExtensions
{
public static IQueryable<T> Match<T>(this IQueryable<T> source, IEnumerable<T> target, Expression<Func<T, T, bool>> by)
{
var parameter = by.Parameters[0];
var condition = target
.Select(item => by.Body.ReplaceParameter(by.Parameters[1], Expression.Constant(item)))
.DefaultIfEmpty()
.Aggregate(Expression.OrElse) ?? Expression.Constant(false);
var predicate = Expression.Lambda<Func<T, bool>>(condition, parameter);
return source.Where(predicate);
}
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
Usage:
var entriesInAttendanceTable = context.Attendance
.Match(newAttendance, (x, y) => y.FirstName == x.FirstName && y.LastName == x.LastName)
.ToList();
Please note that both solutions might be problematic if the newAttendance list is big.

How to modify the argument of the GroupBy() method?

This is my a linq query that filter tasks by range name then group them by language.
var transTasks = taskData
.Where(t => t.RangeName == rName)
.GroupBy(t => t.CultureID)
.Select(g => new { language = g.Key, tasks = g });
Now I'd like to use the same query for a different grouping. Instead of grouping by CultureID, I'd like to group by TaskOrder.
So I've create variables like this
Func<QuoteTaskInfo, bool> predicate = null
if(tasktype == "translation")
{
groupPredicate = t => t.CultureID;
{
else
{
groupPredicate = t => t.TaskOrder;
}
I'm getting the following error: "Cannot convert expression ... because some return type are not convertible to the delegate return type".
Any help on how to write a delegate that would return a bool?
The same goes with Select(). If the criteria is task order, then the key for the select should be TaskOrder instead of language.
Thanks for helping

Categories