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.
Related
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 }
);
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.
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"
How do I write the following sql query to a Predicate Expression using Fluent Sytax:
select c.* from customer c
inner join Orders r on r.CustomerId = c.Id
where
c.MemberSince > '01/01/2013' &&
r.OrderDate > '01/01/2014'
order by r.OrderDate
I am expecting something like this:
Expression<Func<Customer, bool>> filters = PredicateBuilder.True<Customer>();
filters = filters.And(x => x.MemberSince > DateTime.Parse('01/01/2013'));
filterr = ....
I am not sure how to add the Orders predicate. And then I call:
var list = db.Customers.AsExpandable().Where(filters).ToList();
You don't need PredicateBuilder at all. You just need to understand that the result of a join is effectively a sequence of pairs:
DateTime memberCutoff = ...;
DateTime orderCutoff = ...;
var query = context.Customers
.Join(context.Orders,
c => c.Id, r => r.CustomerId, (c, r) => new { c, r })
.Where(pair => pair.c.MemberSince > memberCutoff &&
pair.r.OrderDate > orderCutoff)
.OrderBy(pair => pair.r.OrderDate);
Is there anyway this code can be refactored? The only difference is the order by part.
Idealy I'd like to use a delegate/lambda expression so the code is reusable but I don't know how to conditionally add and remove the query operators OrderBy and OrderByDescending
var linq = new NorthwindDataContext();
var query1 = linq.Customers
.Where(c => c.ContactName.StartsWith("a"))
.SelectMany(cus=>cus.Orders)
.OrderBy(ord => ord.OrderDate)
.Select(ord => ord.CustomerID);
var query2 = linq.Customers
.Where(c => c.ContactName.StartsWith("a"))
.SelectMany(cus => cus.Orders)
.OrderByDescending(ord => ord.OrderDate)
.Select(ord => ord.CustomerID);
You can create your own reusable extension method which will do this:
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>
(this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
bool ascending)
{
return ascending ? source.OrderBy(keySelector)
: source.OrderByDescending(keySelector);
}
and similarly for ThenBy:
public static IOrderedQueryable<TSource> ThenBy<TSource, TKey>
(this IOrderedQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
bool ascending)
{
return ascending ? source.ThenBy(keySelector)
: source.ThenByDescending(keySelector);
}
You can split your query up into bits, and use control flow logic. LINQ to SQL will magically construct the correct query as if you had typed it all one line! The reason this works is that the query is not sent to the database until you request the data, but instead is stored as an expression.
var linq = new NorthwindDataContext();
var query = linq.Customers
.Where(c => c.ContactName.StartsWith("a"))
.SelectMany(cus=>cus.Orders);
IOrderedQueryable<Order> query2;
if (useAscending) {
query2 = query.OrderBy(ord => ord.OrderDate);
} else {
query2 = query.OrderByDescending(ord => ord.OrderDate);
}
var query3 = query2.Select(ord => ord.CustomerID);
return from T in bk.anbarsabts
where T.kalaname == str
orderby T.date descending
select new { T.date, T.kalaname, T.model, T.tedad };
With numbers, etc you can normally just negate the 'ordering variable'.
With DateTime, I am not so sure. You could try using a Timespan.
Well, If you have a condition where you decide if the order by is ascending or descending you can use this
var query1 = linq.Customers
.Where(c => c.ContactName.StartsWith("a"))
.SelectMany(cus=>cus.Orders)
if(SortAscending)
query1 = query1.OrderBy(ord => ord.OrderDate);
else
query1 = query1.OrderByDescending(ord => ord.OrderDate);
var query2 = query1.Select(ord => ord.CustomerID);