How can I merge this below query into a single query? I think multiple if's will add unnecessarily to code complexity.
var query = scenario.Where(x => x.ScenarioNumber == ScenarioNumber).Select(x => x).Distinct().ToList();
// Match exception number else return wild card entries.
if(query.Any(x=>x.ExcepNumber == ExcepNumber))
{
query = query.Where(x => x.ExcepNumber == ExcepNumber).ToList();
}
else
{
query = query.Where(x => x.ExcepNumber == "**").ToList();
}
// Match regime else return wild card entries.
if (query.Any(x => x.Regime == Regime))
{
query = query.Where(x => x.Regime == Regime).ToList();
}
else
{
query = query.Where(x => x.Regime == "**").ToList();
}
finalResponse = query.Select(x => x.TestColumn).Distinct().ToList();
I have a suggestion. You could extract it into a generic method, in which you would pass a selector function which specifies the property to be used for the filtering and the 2 choices that you would like to be compared (and the collection of course).
private List<T> EitherFilterOrWildCard<T>(Func<T, string> selector, string compare, string elseCompare, List<T> query)
{
var temp = query.Where(x => selector(x) == compare);
return temp.Any() ? temp.ToList() : query.Where(x => selector(x) == elseCompare).ToList();
}
Your if statements would then shrink to these 2 lines:
query = EitherFilterOrWildCard<MyClass>(x => x.ExcepNumber, ExcepNumber, "**", query);
query = EitherFilterOrWildCard<MyClass>(x => x.Regime, Regime, "**", query);
Related
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.
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();
I have an operation that takes a serializable QueryModel and converts it to an Expression to be passed to Entity Framework. My query against the database looks like:
public IEnumerable<PhotoVerifySessionOverview> FindSessions(Expression<Func<vwPhotoVerifySession, bool>> predicate, PaginationModel model)
{
var sessions = Context.vwPhotoVerifySessions
.AsQueryable()
.Where(predicate)
.OrderBy(string.Format("{0} {1}", model.OrderByColumn, model.OrderByDirection))
.Skip(model.Offset)
.Take(model.PageSize);
return Mapper.Map<IEnumerable<PhotoVerifySessionOverview>>(sessions);
}
and my predicate builder looks like:
var predicate = PredicateBuilder.True<vwPhotoVerifySession>();
//Add the tenant to the where clause
if (model.TenantId.HasValue)
predicate.And(p => p.TenantId == model.TenantId.Value);
else
predicate.And(p => p.TenantReferenceId == model.TenantReferenceId);
//Add a date range if one is present
if (model.CreatedOnRange != default(DateRange))
{
var endDate = model.CreatedOnRange.End == default(DateTime) ? DateTime.Now : model.CreatedOnRange.End;
predicate.And(p => p.CreatedOn >= model.CreatedOnRange.Start && p.CreatedOn <= endDate);
}
//Include status filtering if any filters are present
if (model.StatusFilter != null && model.StatusFilter.Any())
{
//use Id and name to search for status
predicate.And(p => model.StatusFilter.Any(f => f.StatusId == p.StatusId || p.Status == f.Name));
}
var pagination = model as PaginationModel;
var sessions = Manager.FindSessions(predicate, pagination);
return sessions;
The problem is, my Where clause is not being evaluated and all results are being returned. Is there something else I should be doing to get the Where statement to work correctly?
You need to assign predicate back to itself.
predicate = predicate.And(p => p.TenantId == model.TenantId.Value);
I have function
public async Task<IQueryable<Document>> GetDocuments(...)
in which I search for documents under some given conditions. Some conditions can be skipped. At the end I perform union of these queries.
var documents = await documentService.GetDocuments(this, userId,
roleShowFullNumber, param.OrderColName(), param.SearchValue, filter);
var usersGroupsId = filter.UsersGroupsId;
if (usersGroupsId != null)
{
if (!usersGroupsId.Contains("All"))
{
IQueryable<Document> myDocs = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Contains("myOrders"))
{
myDocs = documents.Where(x => x.OwnerId == userId || x.UserId == userId);
usersGroupsId = usersGroupsId.Where(x => x != "myOrders").ToArray();
}
IQueryable<Document> wards = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Contains("wards"))
{
var relatedUserId = _db.Users.Where(x => x.Id == userId).Select(x => x.RelatedUserId).FirstOrDefault();
if (relatedUserId != null)
{
var myWards = _db.kh__Kontrahent.Where(x => x.kh_IdOpiekun == relatedUserId);
var myWardsUsers = _db.Users.Where(x => myWards.Any(w => w.kh_Id == (x.RelatedCustomerId == null ? -1 : x.RelatedCustomerId)));
wards = documents.Where(x => (myWardsUsers.Any(w => x.UserId == w.Id) || myWardsUsers.Any(w => x.OwnerId == w.Id)));
usersGroupsId = usersGroupsId.Where(x => x != "wards").ToArray();
}
}
IQueryable<Document> groups = Enumerable.Empty<Document>().AsQueryable();
if (usersGroupsId.Length > 0)
{
var usersGroups = _db.Groups.Where(x => usersGroupsId.Contains(x.Id.ToString()));
var usersList = usersGroups.Select(x => x.Users);
var users = usersList.SelectMany(x => x);
var usersId = users.Select(x => x.Id);
groups = _db.Documents.Where(x => (usersId.Any(u => u == x.OwnerId) || usersId.Any(u => u == x.UserId)));
}
documents = myDocs.Union(wards).Union(groups);
}
}
But if one of these partial queries is empty (was skipped) when I try obtain these documents in way shown below I got error.
var documentsPaginated = await documents.Skip(param.Start)
.Take(param.Length)
.ToListAsync();
Error: The source IQueryable doesn't implement IDbAsyncEnumerable.
How can I make this function to be able to skip some sub queries and then union all. I would prefer not to change function return value.
Try this, althought your code seems to have a massive amount of code smell...
public async Task<IQueryable<Document>> GetDocuments(...)
var documents = await documentService.GetDocuments(this, userId,
roleShowFullNumber, param.OrderColName(), param.SearchValue, filter);
var usersGroupsId = filter.UsersGroupsId;
if (usersGroupsId != null)
{
if (!usersGroupsId.Contains("All"))
{
IQueryable<Document> myDocs = null;
if (usersGroupsId.Contains("myOrders"))
{
myDocs = documents.Where(x => x.OwnerId == userId || x.UserId == userId);
usersGroupsId = usersGroupsId.Where(x => x != "myOrders").ToArray();
}
IQueryable<Document> wards = null;
if (usersGroupsId.Contains("wards"))
{
var relatedUserId = _db.Users.Where(x => x.Id == userId).Select(x => x.RelatedUserId).FirstOrDefault();
if (relatedUserId != null)
{
var myWards = _db.kh__Kontrahent.Where(x => x.kh_IdOpiekun == relatedUserId);
var myWardsUsers = _db.Users.Where(x => myWards.Any(w => w.kh_Id == (x.RelatedCustomerId == null ? -1 : x.RelatedCustomerId)));
wards = documents.Where(x => (myWardsUsers.Any(w => x.UserId == w.Id) || myWardsUsers.Any(w => x.OwnerId == w.Id)));
usersGroupsId = usersGroupsId.Where(x => x != "wards").ToArray();
}
}
IQueryable<Document> groups = null;
if (usersGroupsId.Length > 0)
{
var usersGroups = _db.Groups.Where(x => usersGroupsId.Contains(x.Id.ToString()));
var usersList = usersGroups.Select(x => x.Users);
var users = usersList.SelectMany(x => x);
var usersId = users.Select(x => x.Id);
groups = _db.Documents.Where(x => (usersId.Any(u => u == x.OwnerId) || usersId.Any(u => u == x.UserId)));
}
if(myDocs != null)
documents = documents.Union(myDocs);
if(wards != null)
documents = documents.Union(wards);
if(groups != null)
documents = documents.Union(groups);
}
}
It appears that ToListAsync can't be used interchangeably with linq, but only in an EF query.
Quote from MSDN
Entity Framework 6 introduced a set of extension methods that can be used to asynchronously execute a query. Examples of these methods include ToListAsync, FirstAsync, ForEachAsync, etc.
Because Entity Framework queries make use of LINQ, the extension methods are defined on IQueryable and IEnumerable. However, because they are only designed to be used with Entity Framework you may receive the following error if you try to use them on a LINQ query that isn’t an Entity Framework query.
The source IQueryable doesn't implement IDbAsyncEnumerable{0}. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
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();