How to aggregate order by's in c#? - c#

So I have a function like below which takes in a list of objects and keeps including them into my query string. This works great.
query = includeExpressions.Aggregate(query, (current, include) => current.Include(include));
But what I am wanting to do is to Aggregate the OrderBys as well. This is where I am running into issues. The issue I have is that one must use OrderBy then use ThenBy on the same line. I've tried using a for loop first item use order by then on use thenby. The issue I am running in is that in order for thenby to be activated the order by needs to proceed it on the same object line.
query.OrderBy(orderBy).ThenBy
I can not do
var usedOrderBy = true;
foreach (var orderBy in orderBys)
{
if (usedOrderBy)
{
query = query.OrderBy(orderBy);
usedOrderBy = true;
}
else
{
query = query.ThenBy(orderBy); // <-- Can not locate thenby
}
}
any ides.
I want to pass in like
orderItems(x => x.Item1, x => x.Item2)

The problem is in the type of query variable. ThenBy is the extension method that can be applied to System.Linq.IOrderedEnumerable<TSource> or System.Linq.IOrderedQueryable<TSource> so you can't do just
IEnumerable<Point> points = GetPoints();
points = points.OrderBy(p => p.X);
points = points.ThenBy(p => p.Y);
You should have an extra variable for preordered result to solve your scenario like in an example below:
IEnumerable<Point> points = GetPoints();
var orderedPoints = points.OrderBy(p => p.X);
orderedPoints = orderedPoints.ThenBy(p => p.Y);

Check this
var orderByList = new List<Expression<Func<TEntity, object>>>();
Expression<Func<TEntity,object>> orderBy1 = x => x.Id;
Expression<Func<TEntity, object>> orderBy2 = x => x.ToString();
Expression<Func<TEntity, object>> orderBy3 = x => x.Id;
orderByList.Add(orderBy1);
orderByList.Add(orderBy2);
orderByList.Add(orderBy3);
var resultOrderedQueryable = orderByList.Aggregate<Expression<Func<TEntity, object>>, IOrderedQueryable<TEntity>>(null, (current, orderBy) => current != null ? current.ThenBy(orderBy) : query.OrderBy(orderBy));

Your query will work if can have initial seed of OrderBy. Then you can chain ThenBy's. As mentioned, ThenBy requires an IOrderedEnumerable (or IOrderedQueryable) first before attaching ThenBy clauses:
Your code will look like below:
IOrderedQueryable<T> orderedQuery = null;
for(int temp = 0; temp < orderBys.Count; temp++)
{
if(temp == 0)
orderedQuery = query.OrderBy(orderBys[temp]);
else orderedQuery = orderedQuery.ThenBy(orderBys[temp]);
}
query = orderedQuery ?? query;
Reference: How to generate a dynamic number of ThenBy clauses in a Specification

Related

C# - How to use Expression<Func> with parametrs in LINQ where

Task: I need to give some expression with parameters into LINQ's where to get some data from database, but have an error above
This example of working expression:
var shopExp = GetPersonForShop(PersonTypeIds.Director, new Guid("adda423f-8c38-40e0-9f39-6deceb787bc0")); // id
Where(shopExp)
But i need assign id dynamically, but got error above :
_repository.Persons
.Where(GetPersonForShop(PersonTypeIds.Director, person.PersonId)
And got error:
{"Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression2' to type 'System.Linq.Expressions.LambdaExpression'."}
How does function for where(linq) look:
private Expression<Func<Person, bool>> GetPersonForShop(PersonTypeIds personTypeId, Guid personId)
{
return person => person .PeronTypeId== (int) personTypeId && person .PersonId == personId;
}
This is approximate look like out production, just change names of parametrs code
How can I add expression with parameters to Where clause??
Lambda expressions use => notation. Try something like this:
var idToFind = new Guid("adda423f-8c38-40e0-9f39-6deceb787bc0");
var result = _repository.Persons
.Where(p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind);
In this expression, p represents each Person record in the Persons table, compared one-by-one using the boolean expression that follows it.
Depending on your datasource, the comparison for each p will either be done by .NET in memory, or it will happen inside your database using a SQL WHERE clause which is constructed from the boolean expression. The last would be optimal because it would mean that not the entire Persons table has to be transferred into .NET memory before comparison can take place.
Update - To apply the same condition multiple times without repeating it in your code, while still keeping the advantages of LINQ to SQL translation intact, you can put the condition in an Expression<Func<Person, bool>> object and then use that multiple times:
Expression<Func<Person, bool>> expression =
p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind;
var result1 = datasource1.Where(expression);
var result2 = datasource2.Where(expression);
var result3 = datasource3.Where(expression);
Or through a method that produces the Expression object:
var result1 = datasource1.Where(GetExpression(idToFind));
var result2 = datasource2.Where(GetExpression(idToFind));
var result3 = datasource3.Where(GetExpression(idToFind));
public Expression<Func<Person, bool>> GetExpression(Guid idToFind)
{
return p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind;
}
Or alternatively you can use a helper method:
var result1 = FilterByTypeAndId(datasource1, idToFind);
var result2 = FilterByTypeAndId(datasource2, idToFind);
var result3 = FilterByTypeAndId(datasource3, idToFind);
public IQueryable<Person> FilterByTypeAndId(IQueryable<Person> datasource, Guid idToFind)
{
return datasource.Where(p => p.TypeId == PersonTypeIds.Director && p.PersonId == idToFind);
}
based on the previous response, I am going to give you a few alternatives and suggestions.
var idToFind = new Guid("adda423f-8c38-40e0-9f39-6deceb787bc0");
var result = _repository
.Persons
.Where(p => p.TypeId == PersonTypeIds.Director)
.Where(p => p.PersonId == idToFind)
.ToList();
First is doing the where clause in 2 steps and then, adding the ToList(), with the ToList(), you will deal with collections and LINQ that is pretty useful. And by doing the where clause in 2 steps, is more for readable purposes.

Linq query takes too long when using Func & OrderByDescending

Consider this code:
public List<Clients> GetFilteredClients(DateTime? FromDate = null,
DateTime? ToDate = null,
int? fromLocationType = null,
int? toLocationType = null)
{
Func<Clients, bool> fromDateFilter = f => true;
if (FromDate.HasValue)
{
fromDateFilter = z => z.Insert_Date.Value.Date >= FromDate.Value.Date;
}
Func<Clients, bool> toDateFilter = f => true;
if (ToDate.HasValue)
{
toDateFilter = z => z.Insert_Date.Value.Date <= ToDate.Value.Date;
}
Func<Clients, bool> fromLocationTypeFilter = f => true;
if (fromLocationType.HasValue)
{
fromOrgFilter = z => z.LocationTypeId >= fromLocationType.Value;
}
Func<Clients, bool> toLocationTypeFilter = f => true;
if (toLocationType.HasValue)
{
toLocationTypeFilter = z => z.LocationTypeId <= toLocationType.Value;
}
var filtered = DB_Context.Clients
.Where(fromDateFilter)
.Where(toDateFilter)
.Where(fromLocationTypeFilter)
.Where(toLocationTypeFilter)
.OrderByDescending(k => k.Id)
.Take(1000)
.ToList();
return filtered;
}
I have something like 100K records in the DB, I need only the top 1000 that answer to the requirements of:
.Where(fromDateFilter)
.Where(toDateFilter)
.Where(fromLocationTypeFilter)
.Where(toLocationTypeFilter)
However the execution time still takes something like 10 seconds.
Any idea why?
You must use Expression<Func<...>> rather than Func<...>. When you use Func, only the enumerable methods can be used on the queryable, which in this case means you first download everything to memory, and then do the filtering. If you switch over to Expression<...>, the O/RM will do the filtering on the DB server, rather than in your application.
Also, there's better ways to do what you're doing. For example, you can build the conditions like so:
var query = DB_Context.Clients.AsQueryable();
if (FromDate.HasValue) query = query.Where(...);
if (ToDate.HasValue) query = query.Where(...);
...
return query.OrderByDescending(k => k.Id).Take(1000).ToList();
Of course, this means that whatever DB provider you're using must be able to support the kind of filtering you're trying to do - you'll need to consult the documentation.
You are using delegates instead LINQ expressions. That leads to processing a data by your application and not by SQL Server.
LINQ expressions look like lambda expressions thanks for the syntax, but they are not same thing. The compiler takes a decision what to create (delegates or LINQ expressions) depending on the situation.
If an object implements the IQueriable interface, then the compiler uses the Queryable class and generates LINQ expression trees, which later can be translated into a SQL query or other form by the specific IQueryProvider.
Otherwise, the compiler uses extensions from the Enumerable class, which create iterators over source collection (all records from the table in your case).
As an example. The code bellow will be compilled into LINQ expressions.
// Source code
IQueryable<Clients> source = null;
IQueryable<Clients> result = source.Where(c => c.LocationTypeId >= 1);
// Compiller generated code
IQueryable<Clients> source = null;
Expression parameterC = Expression.Parameter(typeof(Clients), "c");
IQueryable<Clients> result = Queryable.Where<Clients>(
source,
Expression.Lambda<Func<Clients, bool>>(
Expression.LessThanOrEqual(
Expression.Property(
parameterC ,
typeof(Clients).GetProperty("LocationTypeId").GetGetMethod()
),
Expression.Constant(1, typeof(int))
),
new ParameterExpression[]
{
parameterC
}
);
And this code uses delegates:
// Source code
IQueryable<Clients> source = null;
Func<Clients, bool> filter = c => c.LocationTypeId >= 1;
IEnumerable<Clients> result = source.Where(filter );
// Compiller generated code
IQueryable<Clients> source = null;
Func<Clients, bool> filter = c => c.LocationTypeId >= 1;
IEnumerable<Clients> result = Enumerable.Where(source, filter);
So, to solve you problem use Expression<Func<Clients, bool>> instead of Func<Clients, bool>:
IQueryable<Clients> result = DB_Context.Clients;
if (someFilter.HasValue)
result = result.Where(c => c.SomeProperty == someFilter.Value);
// other filters
return query
.OrderByDescending(k => k.Id)
.Take(1000)
.ToList();

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities

I have a problem trying to implement a filtering expression to filter a list of entities :
The LINQ expression node type 'Invoke' is not supported in LINQ to
Entities.
This is the code :
public IList<DocumentEntry> GetDocumentEntriesForRateAdjustmentTry2(
string username, Rate rate, List<RatePeriod> ratePeriods)
{
var dimensionLibManager = new DimensionLibManager();
var currentVersionRateGroups = rate.CurrentRateVersion.RateGroups.ToList();
Expression<Func<DocumentEntry, IList<RateGroup>, int, bool>> dimensionMatchesExpression =
(documentEntry, rateGroups, dimensionInfoId) =>
rateGroups.Any(
rg =>
rg.Dimension1.All(character => character == '*')
||
documentEntry.DocumentEntryDimensions.Any(
ded =>
ded.DimensionInfo.Position == dimensionInfoId
&&
dimensionLibManager.GetDimensionSegments(rate.CompanyId, username, dimensionInfoId, ded.Value).Any(
seg => ded.Value.Substring(seg.SegmentStart, seg.SegmentLength) == seg.SegmentValue)));
var dimensionMatches = dimensionMatchesExpression.Compile();
var documentEntries = this.ObjectSet.Where(de => dimensionMatches(de, currentVersionRateGroups, 1));
var result = documentEntries.ToList(); // The error happens here.
return result;
}
I suspect that dimensionMatchesExpression cannot be traduced into SQL because inside it calls another library's method (dimensionLibManager.GetDimensionSegments) to filter documents based on specific parameters.
Is there a way (other than using LinqKit or any additionnal extention library) that I can make this work ?
The reason why I want to use an Expression to act as a filter is because, ultimately, I would like to to this :
var documentEntries = this.ObjectSet.Where(de =>
dimensionMatches(de, currentVersionRateGroups, 1)
&& dimensionMatches(de, currentVersionRateGroups, 2)
&& dimensionMatches(de, currentVersionRateGroups, 3)
&& dimensionMatches(de, currentVersionRateGroups, 4));
Also, how can I actually debug that kind of problem ? The error message is pretty vague. How can I track down the exact node that is causing the error ?
I suspect this is the issue.
var documentEntries = this.ObjectSet.Where(de => dimensionMatches(de, currentVersionRateGroups, 1));
I don't think that row number works with Linq2EF.
public IList<DocumentEntry> GetDocumentEntriesForRateAdjustmentTry2(
string username, Rate rate, List<RatePeriod> ratePeriods)
{
var dimensionLibManager = new DimensionLibManager();
var currentVersionRateGroups = rate.CurrentRateVersion.RateGroups.ToList();
Expression<Func<DocumentEntry, int, bool>> dimensionMatchesExpression =
(documentEntry, rateGroups, dimensionInfoId) =>
currentVersionRateGroups.Any(
rg =>
rg.Dimension1.All(character => character == '*')
||
documentEntry.DocumentEntryDimensions.Any(
ded =>
ded.DimensionInfo.Position == dimensionInfoId
&&
dimensionLibManager.GetDimensionSegments(rate.CompanyId, username, dimensionInfoId, ded.Value).Any(
seg => ded.Value.Substring(seg.SegmentStart, seg.SegmentLength) == seg.SegmentValue)));
var documentEntries = this.ObjectSet.Where(dimensionMatchesExpression);
var result = documentEntries.ToList(); // The error happens here.
return result;
}
Although I don't understand why you want to use an expression to do this. You could just inline it all...
Just realised a few days ago the solution to your problem...bit of a hack...
public Expression<Func<DocumentEntry, int, bool>> CreateWhereClause(stuff);
public IList<DocumentEntry> GetDocumentEntriesForRateAdjustmentTry2(
string username, Rate rate, List<RatePeriod> ratePeriods)
{
using(var db = new Context())
{
IQueryable<DocumentEntry> foo = db.Foos;
foreach(var i =0; i <4; i++)
{
foo = foo.Where(DocumentEntry(i));
}
}
}

Adding a where/order by clause to an IQueryable

I have ths function to query a set of records from the DB:
public IQueryable<PointTransactionViewModel> GetPointTransactions(int UserID)
{
return
(
from PointTransaction p in entities.PointTransaction
join ActivityLog a in entities.ActivityLog
on p.TransactionID equals a.TransactionID
where p.UserID == UserID
select new PointTransactionViewModel
{
ID = p.TransactionID,
Balance = p.Balance,
Points = p.Amount,
RelatedActivityID = a.ID,
When = p.When,
Sender = p.SenderUserInfo.CompleteName
}
);
}
I wish to add an additional cause, like this
var entries = GetPointTransaction(1);
return entries.OrderbyDescending.Where( x => x.When >= start && w.When <= end).
( x => x.When);
However, I seem to need to create a new query from the existing one for this to work. But, I have seem this work before without creating a new query, in the code snippet before:
public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = source.Count();
TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);
this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
}
Does the code above somehow doesn't need a new query to be created for the IQueryable source object? Was a temporary object created?
Edit
It's strange, but to get it to work I have to do the following:
IQueryable<ActivityLogEntry> log = activityRepo.GetPointTransaction(userID).
Where(x => x.PointsEarned == 50);
return log.ToList();
The following will not work:
var log = = activityRepo.GetPointTransaction(userID);
log.Where( x => x.PointsEarned == 50);
return log.ToList();
There is no error message, just that the where clause seems to be ignored (it is also returning all data which PointsEarned is not 50)
Your entries is of IQueryable type, that's enough and you can add any number of clauses before fetching the data, e.g. before calling the ToList() function.
It doesn't execute the SQL code, just an expression tree will be created until you fetch the whole data with one of the existing methods (again, e.g. the ToList() function).
var query = context.Where(x=>x.id == test);
query = query.Where(anotherCondition1);
query = query.Where(anotherCondition2);
...
var result = query.ToList();
it's equal to
var result = context.Where(x=>x.id == test)
.Where(anotherCondition1)
.Where(anotherCondition2)
....
.ToList()
This is called deferred execution, for more details see the MSDN blog post on LINQ and Deferred Execution.
You do need to create a new object. IQueryable is immutable. Don't worry this is how you are supposed to do it. This is how the queries are formed internally. All the extension methods like "Where" don't actually change the object. They just return a new one.
The code that you claim works should not work. The method doesn't even have a type.
i mean you can write this sample :
opportunites = from opp in oppDC.Opportunities
join org in oppDC.Organizations on opp.OrganizationID equals org.OrgnizationID
select new
{
opp.OpportunityID,
opp.Title,
opp.PostedBy,
opp.Address1,
opp.CreatedDate,
org.OrganizationName
};
if(condition)
{
opportunites = opportunites.Where(opp => opp.Title.StartsWith(title));
}
//------Other Condition you need
if(!String.IsNullOrEmpty(title))
{
opportunites = opportunites.Where(.....);
}
if(!String.IsNullOrEmpty(name))
{
opportunites = opportunites.Where(.....);
}
As others have pointed out, you do not need a new object. Your syntax for OrderByDescending is wrong though, you need to specify the key selector.
var entries = GetPointTransaction(1);
return entries.Where(x => x.When >= start && w.When <= end).OrderbyDescending(x => x.When);

How to refactor multiple similar Linq-To-Sql queries?

Suppose I have the two following Linq-To-SQL queries I want to refactor:
var someValue1 = 0;
var someValue2= 0;
var query1 = db.TableAs.Where( a => a.TableBs.Count() > someValue1 )
.Take( 10 );
var query2 = db.TableAs.Where( a => a.TableBs.First().item1 == someValue2)
.Take( 10 );
Note that only the Where parameter changes. There is any way to put the query inside a method and pass the Where parameter as an argument?
All the solutions posted in the previous question have been tried and failed in runtime when I try to enumerate the result.
The exception thrown was: "Unsupported overload used for query operator 'Where'"
Absolutely. You'd write:
public IQueryable<A> First10(Expression<Func<A,bool>> predicate)
{
return db.TableAs.Where(predicate).Take(10);
}
(That's assuming that TableA is IQueryable<A>.)
Call it with:
var someValue1 = 0;
var someValue2= 0;
var query1 = First10(a => a.TableBs.Count() > someValue1);
var query2 = First10(a => a.TableBs.First().item1 == someValue2);
I believe that will work...
The difference between this and the answers to your previous question is basically that this method takes Expression<Func<T,bool>> instead of just Func<T,bool> so it ends up using Queryable.Where instead of Enumerable.Where.
If you really want reusability you can try to write your own operators. E.g. instead of repeatedly writing:
var query =
Products
.Where(p => p.Description.Contains(description))
.Where(p => p.Discontinued == discontinued);
you can write simple methods:
public static IEnumerable<Product> ByName(this IEnumerable<Product> products, string description)
{
return products.Where(p => p.Description.Contains(description));
}
public static IEnumerable<Product> AreDiscontinued(IEnumerable<Product> products, bool isDiscontinued)
{
return products.Where(p => p.Discontinued == discontinued);
}
and then use it like this:
var query = Products.ByName("widget").AreDiscontinued(false);

Categories