How to combine mulitiple LINQ to objects requests into single one - c#

I would like to rewrite GetCurrentAuction into single LINQ request:
private AuctionInfo GetCurrentAuction()
{
var auctions = Auctions.List().ToList();
var liveAuction = auctions
.Where(AuctionIsLive)
.OrderBy(a => a.StartDate)
.FirstOrDefault();
if (liveAuction != null)
{
return liveAuction;
}
var openAuction = auctions
.Where(AuctionIsOpen)
.OrderBy(a => a.StartDate)
.FirstOrDefault();
if (openAuction != null)
{
return openAuction;
}
// next upcoming auction
return auctions
.Where(a => a.StartDate >= DateTime.UtcNow)
.OrderBy(a => a.StartDate)
.FirstOrDefault();
}
private bool AuctionIsLive(AuctionInfo auction)
{
// WorkflowStage is int
return auction.WorkflowStage == LIVE_WORKFLOW_STAGE;
}
private bool AuctionIsOpen(AuctionInfo auction)
{
return auction.WorkflowStage == OPEN_WORKFLOW_STAGE;
}
Could someone suggest how to achieve this? It looks like using auctions.GroupBy(a => a.WorkflowStage) doesn't bring me closer to the solution.

You can indicate preference by ordering them - something like:
return
Auctions.List().ToList() //--> ToList() not needed here?
.Where
( a =>
AuctionIsLive(a) ||
AuctionIsOpen(a) ||
a.StartDate >= DateTime.UtcNow
)
.OrderBy
( a =>
AuctionIsLive( a ) ? 0 :
AuctionIsOpen( a ) ? 1 : 2
)
.ThenBy( a => a.StartDate )
.FirstOrDefaut();

You can use very usefull ?? ( https://msdn.microsoft.com/en-us/library/ms173224.aspx ) operator and achive this:
var result = auctions.Where(AuctionIsLive).OrderBy( x => x.StartDate).FirstOrDefault() ??
auctions.Where(AuctionIsOpen).OrderBy( x => x.StartDate).FirstOrDefault() ??
auctions.Where(a => a.StartDate >= DateTime.UtcNow).OrderBy(a => a.StartDate).FirstOrDefault();
return result;

It depends on the datasource and LINQ provider you're using.
For example, if you use LINQ to SQL the preferent way of doing it would be using Expressions to save your memory and end up with the answer simular to #fankyCatz's:
return Auctions.Where(a => a.WorkflowStage == LIVE_WORKFLOW_STAGE).OrderBy(x => x.StartDate).FirstOrDefault() ??
Auctions.Where(a => a.WorkflowStage == OPEN_WORKFLOW_STAGE).OrderBy(x => x.StartDate).FirstOrDefault() ??
Auctions.Where(a => a.StartDate >= DateTime.UtcNow).OrderBy(a => a.StartDate).FirstOrDefault();
However, using only LINQ to Objects I would end up with the answer simular to #Clay's one, just would improve readability with mapping:
public static Dictionary<int, Func<AuctionInfo, bool>> Presedence =
new Dictionary<int, Func<AuctionInfo, bool>>
{
{ 0, a => a.WorkflowStage == LIVE_WORKFLOW_STAGE },
{ 1, a => a.WorkflowStage == OPEN_WORKFLOW_STAGE },
{ 2, a => a.StartDate >= DateTime.UtcNow },
};
//in your GetCurrentAuction()
return Auctions.Where(a => Presedence.Any(p => p.Value(a)))
.OrderBy(a => Presedence.First(p => p.Value(a)).Key)
.ThenBy(a => a.StartDate)
.FirstOrDefault();

Related

Is there a way to set this entity/linq query to IEnumerable?

I'm trying to return an IEnumerable activities instead of "var"
var activities = ctx.Activities.Where(a => a.SiteID == propID)
.Where(a => a.ActivityTypeName == "Call")
.Select(x => new
{
x.DateTimeEntry,
x.Contact.OwnerContact.ParcelDatas
.FirstOrDefault(a => a.OwnerContactID == x.Contact.OwnerContact.OOwnerID)
.Parcel_LetterTracking.LMailDate,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID)
.FAQ_Library.FaqNum,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID)
.FAQ_Library.Question
});
edit: data type Object compiles but I'm not sure if that's right.
.Select already returns a an IEnumerable<TResult> also combine your ..where() clauses with && instead. https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=netframework-4.8 also one other thing you can do is use .AsEnuemerable()
var activities = ctx.Activities.Where(a => a.SiteID == propID && a.ActivityTypeName == "Call")
.Select(x => new
{
x.DateTimeEntry,
x.Contact.OwnerContact.ParcelDatas.FirstOrDefault(a => a.OwnerContactID == x.Contact.OwnerContact.OOwnerID).Parcel_LetterTracking.LMailDate,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID).FAQ_Library.FaqNum,
x.FAQs.FirstOrDefault(a => a.ActivityID == x.ActivityID).FAQ_Library.Question
}).AsEnumerable();

How to tell LINQ to skip where if condition is false?

Refer to code below:
Instead of having so many if else statements:
List<T> GetTs(string createBy, bool isAdmin, string departmentCode, ...)
{
if(isAdmin)
{
if(!string.IsNullOrEmpty(createBy))
{
var query = context.table
.Where(x => string.compare(x.createBy, createBy, StringComparison.OrdinalIgnoreCase) == 0)
.ToList();
return query;
}
else
{
var query = context.table.ToList();
return query;
}
}
else
{
if(!string.IsNullOrEmpty(createBy))
{
var query = context.table
.Where(x => string.compare(x.departmentCode, departmentCode, StringComparison.OrdinalIgnoreCase) == 0)
.Where(x => string.compare(x.createBy, createBy, StringComparison.OrdinalIgnoreCase) == 0)
.ToList();
return query;
}
else
{
var query = context.table
.Where(x => string.compare(x.departmentCode, departmentCode, StringComparison.OrdinalIgnoreCase) == 0)
.ToList();
return query;
}
}
}
Can I do this instead where it don't have so many if else statements:
var query = context.table
.Where(x => (!isAdmin)? string.compare(x.departmentCode, departmentCode, StringComparison.OrdinalIgnoreCase) == 0) : SKIP THIS '.Where')
.Where(x => (!string.IsNullOrEmpty(createBy))? string.compare(x.createBy, createBy, StringComparison.OrdinalIgnoreCase) == 0) : SKIP THIS '.Where')
//...
.ToList();
How do I tell LINQ to skip the .Where if condition is false? possible?
Suggestion of Patrick Hofman from comment below works:
var query = context.table
.Where(x => (!isAdmin)? string.compare(x.departmentCode, departmentCode, StringComparison.OrdinalIgnoreCase) == 0) : true)
.Where(x => (!string.IsNullOrEmpty(createBy))? string.compare(x.createBy, createBy, StringComparison.OrdinalIgnoreCase) == 0) : true)
//...
.ToList();
If I understand the intent (to conditionally apply predicates), then basically: do the composition differently:
IQueryable<Whatever> query = context.Table;
if(condition1)
query = query.Where(x => x.column1);
if(condition2)
query = query.Where(x => x.column2);
//...etc
var list = query.ToList();
You could probably wrap that in an extension method like
static IQueryable<T> WhereIf<T>(this IQueryable<T> query, bool condition,
Expression<Func<T,bool>> predicate)
=> condition ? query.Where(predicate) : query;
and:
var list= context.Table.WhereIf(condition1, x => x.column1)
.WhereIf(condition2, x => x.column2)
// ...
.ToList();
however, I probably wouldn't do that, as in many cases it will require constructing an unnecessary additional expression tree.

Linq Combine two statements into one big statement (optimization)

I have a method which is using a lot of LINQ to set and match some values in a list of Tuple<string, int>.
Right now i'm still stuck with two foreach loops nested into eachother and i think it'd be possible to combine them into one gigantic LINQ query. I'm wondering what would be the best way to do this with optimization as a big condition.
This is the function i'm talking about:
private async void AddLocalChangesFromPendingOperations()
{
var pendingOperations = await this.operationsStorage.GetOperationsAsync();
var list = pendingOperations.
SelectMany(pendingOperation =>
pendingOperation.Settings, (pendingOperation, setting) =>
new { pendingOperation, setting })
.Where(a => a.setting.Key == "selection")
.Select(a => new Tuple<string, int>(
a.pendingOperation.DefinitionId,
Convert.ToInt32(a.setting.Value.ValueObject)))
.ToList();
foreach (var pendingChange in list)
{
var selection = await this.selectionsStorage.GetSelectionByIdAsync(pendingChange.Item2);
foreach (var selectionsViewModel in this.SelectionsList.Where(a => a.Name == selection.Name))
{
if (pendingChange.Item1 == "selection-add-animals")
{
selectionsViewModel.IsActive = true;
}
else if (pendingChange.Item1 == "selection-remove-animals")
{
selectionsViewModel.IsActive = false;
}
}
}
}
If possible i'd like to optimize the last two foreaches while using linq. I've tried something but i'm stuck on setting values in the current list...
I was doing this:
this.SelectionsList = this
.SelectionsList
.Where(a => a.Name == selection.Name)
.SingleOrDefault(
a => pendingChange.Item1 == "selection-add-animals" ? a.IsActive = true : a.IsActive = false
);
In general, LINQ is for querying items (Language Integrated Query). You could however do a query and then do a foreach at the end:
private async void AddLocalChangesFromPendingOperations()
{
var pendingOperations = await this.operationsStorage.GetOperationsAsync();
(await Task.WhenAll(pendingOperations
.SelectMany(pendingOperation =>
pendingOperation.Settings, (pendingOperation, setting) =>
new { pendingOperation, setting })
.Where(a => a.setting.Key == "selection")
.Select(a => Tuple.Create(a.pendingOperation.DefinitionId, Convert.ToInt32(a.setting.Value.ValueObject)))
.Select(async pendingChange => Tuple.Create(await this.selectionsStorage.GetSelectionByIdAsync(pendingChange.Item2)), pendingChange))
.SelectMany(tuple => this.SelectionsList.Where(a => a.Name == tuple.Item1.Name)
.Select(selectionsViewModel => Tuple.Create(selectionsViewModel, tuple.Item2))
.Select(tuple => Tuple.Create(tuple.Item1, tuple.Item2.Item1 == "selection-add-animals"))
.ToList()
.ForEach(tuple => tuple.Item1.IsActive = tuple.Item2);
}
Whether this is clearer than your original implementation is up for discussion (I don't think it is), but it would be one way of doing it.
NOTE: This was typed into the editor directly, there might be some minor syntax errors.
You can do something like:
this.SelectionsList = this.SelectionsList
.Where(a => a.Name == selection.Name)
.Select(a =>
{
a.IsActive = a.Name == selection.Name ? true:false;
return a;
}).ToList();

How to combine the multiple part linq into one query?

Operator should be ‘AND’ and not a ‘OR’.
I am trying to refactor the following code and i understood the following way of writing linq query may not be the correct way. Can somone advice me how to combine the following into one query.
AllCompany.Where(itm => itm != null).Distinct().ToList();
if (AllCompany.Count > 0)
{
//COMPANY NAME
if (isfldCompanyName)
{
AllCompany = AllCompany.Where(company => company["Company Name"].StartsWith(fldCompanyName)).ToList();
}
//SECTOR
if (isfldSector)
{
AllCompany = AllCompany.Where(company => fldSector.Intersect(company["Sectors"].Split('|')).Any()).ToList();
}
//LOCATION
if (isfldLocation)
{
AllCompany = AllCompany.Where(company => fldLocation.Intersect(company["Location"].Split('|')).Any()).ToList();
}
//CREATED DATE
if (isfldcreatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Created >= createdDate).ToList();
}
//LAST UPDATED DATE
if (isfldUpdatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Updated >= updatedDate).ToList();
}
//Allow Placements
if (isfldEmployerLevel)
{
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
AllCompany = AllCompany.Where(company => company["Allow Placements"].ToString() == fldEmployerLevel).ToList();
}
Firstly, unless AllCompany is of some magic custom type, the first line gives you nothing.
Also I have a doubt that Distinctworks the way You want it to. I don't know the type of AllCompany but I would guess it gives you only reference distinction.
Either way here'w what I think You want:
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
var result = AllCompany.Where(itm => itm != null)
.Where(company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName))
.Where(company => !isfldSector|| fldSector.Intersect(company["Sectors"].Split('|')).Any())
.Where(company => !isfldLocation|| fldLocation.Intersect(company["Location"].Split('|')).Any())
.Where(company => !isfldcreatedDate|| company.Statistics.Created >= createdDate)
.Where(company => !isfldUpdatedDate|| company.Statistics.Updated >= updatedDate)
.Where(company => !isfldEmployerLevel|| company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();
Edit:
I moved Distinct to the end of the query to optimize the processing.
How about trying like this;
AllCompany = AllCompany .Where(company => (company => company.Statistics.Created >= createdDate)) && (company.Statistics.Updated >= updatedDate));
If every part of query is optional (like created date, last update date..) then you can build linq query string.
Here's a sneaky trick. If you define the following extension method in its own static class:
public virtual IEnumerable<T> WhereAll(params Expression<Predicate<T> filters)
{
return filters.Aggregate(dbSet, (acc, element) => acc.Where(element));
}
then you can write
var result = AllCompany.WhereAll(itm => itm != null,
company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName),
company => !isfldSectorn || fldSector.Intersect(company["Sectors"].Split('|')).Any(),
company => !isfldLocation || fldLocation.Intersect(company["Location"].Split('|')).Any(),
company => !isfldcreatedDate || company.Statistics.Created >= createdDate,
company => !isfldUpdatedDate || company.Statistics.Updated >= updatedDate,
company => !isfldEmployerLevel || company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();

Datetime check in Linq where statement

Please see the code below:
var pcPageList = db.PcPages
.Where(m =>
m.Quarter == exactQuarter &&
m.Url == pageUrl &&
m.UpdatedOn.ToDateTime().Date.ToString("dd/MMM").ToLower() == "02/nov")
.OrderBy(m => m.UpdatedOn)
.FirstOrDefault();
When I run this above, the application throws error says: "ToDateTime" is not implemented yet. Anyone please can advice ?
How about:
var updateStart = DateTime.ParseExact("02/nov", "dd/MMM", CultureInfo.InvariantCulture);
var updateEnd = updateStart.AddDays(1.0);
var pcPageList = db.PcPages
.Where(m =>
m.Quarter == exactQuarter &&
m.Url == pageUrl &&
m.UpdatedOn >= updateStart &&
m.UpdatedOn < updateEnd)
.OrderBy(m => m.UpdatedOn)
.FirstOrDefault();
I think, you should be calling ToDateTime using Convert class as:
Convert.ToDateTime(m.UpdatedOn).Date...
And remove the Date in between as:
Convert.ToDateTime(m.UpdatedOn).ToString("dd/MMM").ToLower() == "02/nov"
Rather than doing a string comparison, it would be more efficient to compare the date components directly. I haven't tested this, but something like the following may work:
var pcPageList = db.PcPages
.Where(m => m.Quarter == exactQuarter && m.Url == pageUrl)
// You may need to materialize the results of the query at this point
// or use Convert.ToDateTime(...) instead of ToDateTime()
.Select(m => new { Row = m, UpdatedOn = m.UpdatedOn.ToDateTime() })
.Where(a => a.UpdatedOn.Month == 11 && a.UpdatedOn.Day == 2)
.Select(a => a.Row)
.OrderBy(m => m.UpdatedOn)
.FirstOrDefault();

Categories