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);
Related
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
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 our project we have texts for multiple languages stored in our database.
I want to create a helper function that includes a text in a query.
This would be useful because this include happens a lot in the application and I want have the include code in one place.
The include should use the new filtered includes from Entity Framework Core 5.
This is what I want to replace:
.Include(c => c.NameTexts.Where(t => t.LanguageId == langId))
Replace it for:
.IncludeText(e => e.NameTexts, langId)
The function I want to write:
// The helper function:
public static IQueryable<T> IncludeText<T>(this IQueryable<T> originalQuery, Expression<Func<T, IEnumerable<UserText>>> textToInclude, int langId) where T : class
{
var textWhere = textToInclude.Where(e => e.LanguageId == langId);
originalQuery.Include(textWhere);
return originalQuery;
}
// And call it from a query like this:
var result = await _context.SomeEntity
.IncludeText(e => e.NameTexts, langId)
// Instead of
// .Include(c => c.NameTexts.Where(t => t.LanguageId == langId))
.Where(c => c.Id == request.Id)
.SingleOrDefaultAsync(cancellationToken);
I tried doing the following but I get an error because the types don't match.
Expression<Func<UserText, bool>> newPred = t => t.LanguageId == langId;
var textWhere = Expression.Lambda<Func<T, IList<UserText>>>(Expression.AndAlso(textToInclude, newPred), textToInclude.Parameters);
originalQuery.Include(textWhere);
return originalQuery;
Maybe this can work :
public static IQueryable<T> IncludeText<T>(this IQueryable<T> originalQuery, Expression<Func<T, IEnumerable<UserText>>> textToInclude, int langId) where T : class
{
var methodWhere = typeof(Enumerable)
.GetMethods()
.First(m => m.ToString() == "System.Collections.Generic.IEnumerable`1[TSource] Where[TSource](System.Collections.Generic.IEnumerable`1[TSource], System.Func`2[TSource,System.Boolean])")
.MakeGenericMethod(typeof(UserText));
Expression<Func<UserText, bool>> predicate = t => t.LanguageId == langId;
var navigationWhere = Expression.Call(methodWhere, textToInclude.Body, predicate);
var lambda = Expression.Lambda<Func<T, IEnumerable<UserText>>>(navigationWhere, Expression.Parameter(typeof(T)));
return originalQuery.Include(lambda);
}
I dislike how Where MethodInfo is recuperated. You can improve by inspiring from this other question.
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.
I refactored a linq to entities query to speed it up and broke my orderby lambda feature.
Is there any way to get this to working again since the query is now a join and creating an anonymous type?
Refactored code that is broken because of the orderBy:
public List<UserProductRating> GetUserProductRatings<TKey>(int userId, IPager pager, Func<UserProductRating, TKey> orderBy)
{
var result = _userProductRatingRepo.Table.Where(a => a.UserId == userId)
.Join(_productRepo.Table, outer => outer.ProductId, inner => inner.ProductId,
(outer, inner) => new { UserProductRating = outer, Product = inner })
.OrderByDescending(o => orderBy) // won't work because the query creates an anonymous type above that doesn't match the Func<> definition
.Skip(pager.Skip).Take(pager.PageSize)
.Select(a => new
{
a.UserProductRating.UserId,
a.UserProductRating.ProductId,
a.UserProductRating.VoteCount,
a.UserProductRating.TotalViews,
a.UserProductRating.Rating,
a.Product.Name
}).ToList();
}
Old code that works with orderBy:
public List<UserProductRating> GetUserProductRatings<TKey>(int userId, IPager pager, Func<UserProductRating, TKey> orderBy)
{
return _userProductRatingRepo.Table
.Include(a => a.Product)
.Where(a => a.UserId == userId)
.OrderByDescending(orderBy)
.Skip(pager.Skip)
.Take(pager.PageSize)
.ToList();
}
Since your OrderBy parameter takes a UserProductRating and you include it as one of the anonymous type's properties, you should be able to do this:
public List<UserProductRating> GetUserProductRatings<TKey>(int userId, IPager pager, Func<UserProductRating, TKey> orderBy)
{
var result = _userProductRatingRepo.Table.Where(a => a.UserId == userId)
.Join(_productRepo.Table, outer => outer.ProductId, inner => inner.ProductId,
(outer, inner) => new { UserProductRating = outer, Product = inner })
.OrderByDescending(o => orderBy(o.UserProductRating)) // <-- pass the joined property to the order function
.Skip(pager.Skip).Take(pager.PageSize)
.Select(a => new
{
a.UserProductRating.UserId,
a.UserProductRating.ProductId,
a.UserProductRating.VoteCount,
a.UserProductRating.TotalViews,
a.UserProductRating.Rating,
a.Product.Name
}).ToList();
}