Filtering data from parameters c# - c#

I'm facing a issue when trying to search my page when trying to filter from 5 parameters.
If the parameters is null then I would want to ignore that search and only return results if there is a value in the parameters.
At the moment i used separate parameters but i need something that caters for all 5 and if it has 2 out of 4 values then it should return based on the filter.:
if (name != null)
{
report = report.Where(asd => asd.name == name).OrderBy(x => x.Id).ToList();
}
else if (Id != null)
{
report = report.Where(x => x.Id == Id).OrderBy(x => x.Id).ToList();
}
So this works with only 1 in mind, how would i join all 4 together so it works together.
This is my full code:
[HttpGet]
public ActionResult Get(string Id = null, string name = null, string status = null, string startDate = null, string endDate = null)
{
List<ReportModel> report = new List<ReportModel>();
report = (from data in _context.ReportData
//where data.Id== Id
//&& data.name== name
//&& data.Status == status
//&& data.Date >= Convert.ToDateTime(startDate) && data.Date < Convert.ToDateTime(endDate)
select new ReportModel
{
Id = data.Id,
Name = data.Plant,
Status = data.Status,
Date = data.Date
}).ToList();
if (name != null)
{
report = report.Where(asd => asd.name == name).OrderBy(x => x.Id).ToList();
}
else if (Id != null)
{
report = report.Where(x => x.Id == Id).OrderBy(x => x.Id).ToList();
}
else if (status != null)
{
report = report.Where(x => x.Status == status).OrderBy(x => x.Id).ToList();
}
return Ok(report);
}
Kindly help.

If you care about performance, do not call ToList() early, because it will load whole table into the memory. IQueryable can be combined to produce desired result.
[HttpGet]
public ActionResult Get(string Id = null, string name = null, string status = null, string startDate = null, string endDate = null)
{
var query = _context.ReportData.AsQueryable();
if (name != null)
{
query = query.Where(asd => asd.name == name);
}
if (Id != null)
{
query = query.Where(x => x.Id == Id);
}
if (status != null)
{
query = query.Where(x => x.Status == status);
}
query = query.OrderBy(x => x.Id);
var report = query
.Select(data => new ReportModel
{
Id = data.Id,
Name = data.Plant,
Status = data.Status,
Date = data.Date
})
.ToList();
return Ok(report);
}

Intead of using if condition, you can add directly the condition in the Where clause
if (name != null)
{
query = query.Where(asd => asd.name == name);
}
// replace by
query = query.Where(asd => asd.name == name || name == null);
when name will be null, the Where condition will always true.

Related

How to efficiently grab results from Linq with or without user input params

I have a linq query that I would like to return results with user input data. However, if this function gets called and there is zero data from user, OR user just wants to search via data, OR just one of the other parameters, how can I efficiently write the linq to accommodate for this? Here is the Linq and function:
public static List<Objects.Logs.GenericLog> GetLogs(int entityId, int logLevelId,
DateTime startDate, DateTime endDate)
{
var logsList = new List<Objects.Logs.GenericLog>();
using(var db = CORAContext.GetCORAContext())
{
logsList = (from i in db.GenericLog select new Objects.Logs.GenericLog()
{
EntityId = i.FkEntityId,
LogSourceCode = i.FkLogSourceCode,
LogLevelId = i.FkLogLevelId,
LogDateTime = i.LogDateTime,
LogId = i.PkLogId,
Message = i.Message
})
.Where(i => i.LogDateTime >= startDate && i.LogDateTime <= endDate)
.Where(i => i.EntityId == entityId || i.EntityId == null)
.Where(i => i.LogLevelId == logLevelId || i.EntityId == null)
.ToList();
}
return logsList;
}
For example, in the second and third Where(), I have || i.EntityId == null... thinking this would accomodate for is user input for Entity is null?
Will this work?
Also, how can I do this for date ranges? Can I also do the same?
Finally, is there a BETTER way to do this?
Split creating a query and generating a final result by .ToList()
When you generate a query, you can add where statements on demand, like this:
public static List<Objects.Logs.GenericLog> GetLogs(int entityId, int logLevelId, DateTime startDate, DateTime endDate)
{
var logsList = new List<Objects.Logs.GenericLog>();
using(var db = CORAContext.GetCORAContext())
{
var query = (from i in db.GenericLog select new Objects.Logs.GenericLog()
{
EntityId = i.FkEntityId,
LogSourceCode = i.FkLogSourceCode,
LogLevelId = i.FkLogLevelId,
LogDateTime = i.LogDateTime,
LogId = i.PkLogId,
Message = i.Message
});
if(someCondition) {
query = query.Where(i => i.LogDateTime >= startDate && i.LogDateTime <= endDate)
}
query = query.Where(i => i.EntityId == entityId || i.EntityId == null)
query = query.Where(i => i.LogLevelId == logLevelId || i.EntityId == null)
logsList = query.ToList();
}
return logsList;
}
If I understand you correctly, you have a method that gets a filtered set of data based on the values of the parameters passed in. But you want to make the parameters optional, so if the user wants data for all entities, they wouldn't pass in an entityId.
If that's the case, then you can make the arguments optional by providing a default value for them in the method signature. We can then check if the argument has the default value, and if it does, don't apply that filter; otherwise apply it.
We can do this by doing .Where(x => argHasDefaultValue || someFilter). This works because if the argument has the default value, then the second part of the || is ignored.
For example:
public static List<Objects.Logs.GenericLog> GetLogs(int entityId = int.MinValue,
int logLevelId = int.MinValue, DateTime startDate = default(DateTime),
DateTime endDate = default(DateTime))
{
using(var db = CORAContext.GetCORAContext())
{
return db.GenericLog
.Where(i => startDate == default(DateTime) || i.LogDateTime >= startDate)
.Where(i => endDate == default(DateTime) || i.LogDateTime <= endDate)
.Where(i => entityId == int.MinValue || i.EntityId == entityId)
.Where(i => logLevelId == int.MinValue || i.LogLevelId == logLevelId)
.Select(i => new Objects.Logs.GenericLog
{
EntityId = i.FkEntityId,
LogSourceCode = i.FkLogSourceCode,
LogLevelId = i.FkLogLevelId,
LogDateTime = i.LogDateTime,
LogId = i.PkLogId,
Message = i.Message
}).ToList();
}
}

Dynamic Where clause in Lambda expression

I have some filter parameters in the Controller of an ASP.NET MVC project and I need to create Where clause dynamically according to these parameters. If isActive parameter is true, it will get the records having the StatusId = 1. There are also userName and labId parameters in the method that should be matched in the Where clause.
public ActionResult GetStudents(int labId, string userName, bool isAll)
{
var allRecords = repository.Students;
//If isAll, get all the records having StatusId = 1
var result = allRecords.Where(m => (isAll) || m.StatusId == 1);
//???
}
I use the filter above, but I have no idea what is the most suitable way (conventions) for multiple parameters in order to fetch the result fast. Any idea?
Note: I want to filter for all of three parameters and Where clause should contain all of the combinations according to the parameter's values (also is null or empty).
var predicate = PredicateBuilder.False<Record>();
if(isAll)
predicate = predicate.AND(d => d.StatusId ==1);
predicate = predicate.AND(d => d.labID == labid && d.username = username);
return allRecords.Where(predicate);`
You can use a predicate builder
You can concatenate linq-methods as they all return an IEnumerable<T> and are combined using something like an SQL-And (dependinng on what LINQ-provider you use):
IEnumerable<Student> result = allRecords;
if(labelId.HasValue)
result = result.Where(x => x.LabelId == labelId);
else
result = result.Where(x => x.LabelId == 0); // or whatever your default-behaviour is
if(isAll)
result = result.Where(x => x.StatusId == 1);
else
result = result.Where(x => x.StatusId == 0); // or whateever your default-behaviour is when isAll is false
if(!String.IsNullOrEmpty(userName))
result = result.Where(x => x.Name == userName);
else
result = result.Where(x => x.Name == "Claus"); // or whatever the default-behaviour is when the param isn´t set
Do like this
public ActionResult GetStudents(int labId, string userName, bool isAll)
{
var allRecords = repository.Students;
//If isAll, get all the records having StatusId = 1
if (isAll)
{
var result = allRecords.Where(m => m.StatusId == 1 && m.UserName == userName && m.LabId == labId);
}
else
{
// do else things
}
}
you need something like below
public ActionResult GetStudents(int labId, string userName, bool isAll)
{
var allRecords = repository.Students;
//If isAll, get all the records having StatusId = 1
if (isAll)
{
var result = allRecords.Where(m => m.StatusId == 1
&& m.LabId == labId
&& m.UserName == username);
//or
var result = from record in allRecords
where record != null &&
record.StatusId == 1
&& !string.IsNullOrWhiteSpace(record.UserName)
&& record.UserName.Equals(username)
&& record.Labid = labId
select record;
}
else
{
// do else things
}
}

Union on empty Enumerable

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.

LINQ - can't create with WHERE condition

I have the following method:
public DataTable.DataTablesPage<DataTable.UserModel> DataTableUserListByAttendees(int geoArea, int CurrentUserID)
{
var result = from i in _dbContext.Users
where i.GeographicalAreas.Any(p => p.GeoAreaID == geoArea)
select new DataTable.UserModel()
{
ID = i.ID,
Company = i.Company,
DCMember = (i.UserId != null),
FirstName = i.FirstName,
LastName = i.LastName
};
}
it works fine, but it returns entities that dont have the geoArea proeprty set.
public DataTable.DataTablesPage<DataTable.UserModel> DataTableUserListByAttendees(int? geoArea, int CurrentUserID)
{
var result = from i in _dbContext.Users
where i.GeographicalAreas.Any(p => p.GeoAreaID == geoArea)
select new DataTable.UserModel()
{
ID = i.ID,
Company = i.Company,
DCMember = (i.UserId != null),
FirstName = i.FirstName,
LastName = i.LastName
};
}
How can I change the query so that it returns only entities that have their geoArea property set (aka is not null).
where (geoArea == null) || i.GeographicalAreas.Any(p => p.GeoAreaID == geoArea)
or conditionally add the where to the query
That should be
where geoArea == null || i.GeographicalAreas.Any(p => p.GeoAreaID == (int)geoArea)

How to write Linq depending if a value is provided or not

I am trying to write a LINQ statement with some optional where clauses. This is for a search. The user can select a specific site to search or search against all sites:
var query =
_db.STEWARDSHIP
.OrderBy(r => r.SITE.SITE_NAME)
.Where(r => r.SITE_ID == SiteId)
.Where(r => r.VISIT_TYPE_VAL.VISIT_TYPE_ID == VisitTypeId)
.Select(r => new
{
id = r.STEWARDSHIP_ID,
name = r.SITE.SITE_NAME,
visit_type = r.VISIT_TYPE_VAL.VISIT_TYPE_DESC,
visit_date = r.VISIT_DATE
});
return query;
So when the method gets SiteId = 14, for instance, no problem. However, when it gets SiteId = null, then that where clause should not be considered.
Thanks
Eric
That's easy:
var query = _db.STEWARDSHIP.OrderBy(r => r.SITE.SITE_NAME);
if (SiteId != null)
{
query = query.Where(r => r.SITE_ID == SiteId);
}
query = query.Where(r => r.SITE.SITE_TYPE_VAL.SITE_TYPE_ID == SiteTypeId)
.Select(r => new
{
id = r.STEWARDSHIP_ID,
name = r.SITE.SITE_NAME,
visit_type = r.VISIT_TYPE_VAL.VISIT_TYPE_DESC,
visit_date = r.VISIT_DATE
});
return query;
This works because queries compose nicely - and they really only represent queries; it's only when you try to fetch data from them that the query is actually executed.
Can't you just edit the where clause to something like
.Where(r=>SiteId == null || r.SiteId == SiteId)
you can use where clause in one statement ..like this ..
.Where(r => SiteID == null || r.SITE_ID == SiteID)
I'm stealing a trick from TSQL. Just check for the null value as well.
...
.Where(r => SiteID == null || r.SITE_ID == SiteID)
...
The SQL example is this:
WHERE (SITE_ID = #given OR #given IS NULL) --return matches or all
Though if that value is mutable and you want the value at the time the query was built, try this instead:
var localSiteID = SiteID;
...
.Where(r => localSiteID == null || r.SITE_ID == SiteID)
...

Categories