How can I reduce this method without using the If condition? - c#

I'm trying to use the entity framework to filter my GET method... Using the if the condition it's working
public async Task<List<TimeEntryViewModel>> Get(TimeEntryFilter filter)
{
var result = await _readRepository
.FindByCondition(x => x.IsApproved == true)
.Include(x => x.Task)
.Include(x => x.Account)
.Select(x => new TimeEntryViewModel
{
AccountName = x.Account.Name,
Date = x.Date,
StartTime = x.StartTime,
FinishTime = x.FinishTime,
InternalId = x.InternalId,
TaskId = x.TaskId,
TaskName = x.Task.Name,
CreatedBy = x.CreatedBy,
CustomerName = x.Task.Project.ServiceOrder.Customer.Name
}).ToListAsync().ConfigureAwait(false);
if (filter.AccountName != null ||
(filter.StartDate.HasValue && filter.FinishDate.HasValue && (filter.StartDate <= filter.FinishDate)) ||
(filter.TaskName != null) ||
(filter.CustomerName != null) ||
(filter.InternalId != null))
result = result.Where(x =>
(x.AccountName.ToString().Contains(filter.AccountName)) &&
(x.Date >= filter.StartDate && x.Date <= filter.FinishDate) &&
(x.CustomerName.ToString().Contains(filter.CustomerName)) &&
(x.TaskName.ToString().Contains(filter.TaskName)) &&
(x.InternalId.ToString().Contains(filter.InternalId.ToString()))).ToList();
return result;
}
But I imagine that there is a way to improve this method without using if
I'd had already tried to do that (Just filter account name to show)
But returned Internal Server Error
var result = await _readRepository
.FindByCondition(x => x.IsApproved == true)
.Where(x => string.IsNullOrEmpty(filter.AccountName) || x.Account.Name.ToString().Contains(filter.AccountName))
.Include(x => x.Task)
.Include(x => x.Account)
.Select(x => new TimeEntryViewModel
{
Id = x.Id,
AccountName = x.Account.Name,
Date = x.Date,
Description = x.Description,
StartTime = x.StartTime,
FinishTime = x.FinishTime,
InternalId = x.InternalId,
OnSite = x.OnSite,
IsApproved = x.IsApproved,
IsBillable = x.IsBillable,
TaskId = x.TaskId,
TaskName = x.Task.Name,
CreatedBy = x.CreatedBy,
CustomerName = x.Task.Project.ServiceOrder.Customer.Name
}).ToListAsync().ConfigureAwait(false);
Okay Guys EDIT1: In my second sample, using InternalId instead of Account.Name
.Where(x => (string.IsNullOrEmpty(filter.InternalId.ToString())) || x.InternalId.ToString().Contains(filter.InternalId.ToString()))
it worked.... I believe that I'm having trouble to work with something that is a foreign key in my table...

There is a very interesting way to mass multiple bool values (contains or not etc) with only one value. It's called bit bitwise operations. Here: Most common C# bitwise operations on enums
you can find different approachs to this "technique".

It returned null because you don't check if Account property is null.
AccountName = x.Account.Name
So the above will throw a Null Reference Exception.
A way to avoid this, it is to use the null conditional operator:
AccountName = x.Account?.Name
You can follow the same approach also for properties like:
Task.Name
Task.Project.ServiceOrder.Customer.Name
And access them safely, by using the same operator, like below:
Task?.Name
Task?.Project?.ServiceOrder?.Customer?.Name
Based on the above you can follow the way you have already followed for AccountName property, but you have also at the same time to use the null conditional operator.

It looks like you need && operator:
.Where(x => string.IsNullOrEmpty(filter?.AccountName)
&& x?.Account?.Name.ToString().Contains(filter.AccountName))
because when you use || operator, then you can get NullReferenceException in the second part of your expression in Where:
x?.Account?.Name.ToString().Contains(filter.AccountName))

Okay, apparently, my problem was .Where(x => string.IsNullOrEmpty(filter.AccountName) || x.Account.Name.ToString().Contains(filter.AccountName)) after x.Account.Name I can't use ToString()... I don't know exactly why, but now works without the if condition
public async Task<List<TimeEntryViewModel>> GetApproved(TimeEntryFilter filter)
{
var result = await _readRepository
.FindByCondition(x => x.IsApproved == true)
.Include(x => x.Task)
.Include(x => x.Account)
.Where(x =>
(string.IsNullOrEmpty(filter.InternalId.ToString()) || x.InternalId.ToString().Contains(filter.InternalId.ToString())) &&
(string.IsNullOrEmpty(filter.AccountName) || x.Account.Name.Contains(filter.AccountName)) &&
(string.IsNullOrEmpty(filter.CustomerName) || x.Task.Project.ServiceOrder.Customer.Name.Contains(filter.CustomerName)) &&
(string.IsNullOrEmpty(filter.TaskName) || x.Task.Name.Contains(filter.TaskName)))
.Select(x => new TimeEntryViewModel
{
Id = x.Id,
AccountName = x.Account.Name,
Date = x.Date,
Description = x.Description,
StartTime = x.StartTime,
FinishTime = x.FinishTime,
InternalId = x.InternalId,
OnSite = x.OnSite,
IsApproved = x.IsApproved,
IsBillable = x.IsBillable,
TaskId = x.TaskId,
TaskName = x.Task.Name,
CreatedBy = x.CreatedBy,
CustomerName = x.Task.Project.ServiceOrder.Customer.Name
}).ToListAsync().ConfigureAwait(false);
return result;
}
If anyone knows why it's working, I'll be thankful

Related

How to get the 1 month before records with age

My FilterByAgeRequest Class structure
public class FilterByAgeRequest
{
[Required(ErrorMessage = "Username required")]
public string userName { get; set; }
public DateTime? from { get; set; }
public DateTime? fromTo { get; set; }
}
I want to calculate Distinct age with count.
var query = await _dataContext.Users
.Where(p => p.UserDob != null && p.CreatedAt >= request.from.Date && p.CreatedAt <= request.fromTo.Date) // exclude null DOB's from stats
.GroupBy(p => p.UserDob.Year)
.Select(g => new FilterByAgeResponseCls{ Age = DateTime.Now.Year - g.Key, Count = g.Count() })
.OrderBy(g => g.Age).ToListAsync().ConfigureAwait(true);
Above query executes perfectly but now i have new condition. I want to retrieve one month before user age data from today date if the from and fromTo is NULL. How to check even from and fromTo in linq.
Thanks in advance.
You need to add condition (request.from != null && request.fromTo != null) in Where clause as follow:
var query = await _dataContext.Users
.Where(p => p.UserDob != null &&
((request.from != null && request.fromTo != null)
? //It means from and fromTo is not null
(p.CreatedAt >= request.from.Date && p.CreatedAt <= request.fromTo.Date)
: // else means if from or fromTo is null then retrieve data from last one month
p.CreatedAt > DateTime.Now.AddMonths(-1) ))
.GroupBy(p => p.UserDob.Year)
.Select(g => new FilterByAgeResponseCls{ Age = DateTime.Now.Year - g.Key, Count = g.Count() })
.OrderBy(g => g.Age).ToListAsync().ConfigureAwait(true);
You could try this code, which doesn't require much changes and gives more control over date boundaries:
DateTime from;
DateTime to;
if( request.from == null || request.fromTo == null )
{
from = DateTime.Now.Date.AddMonths(-1);
to = DateTime.Now.Date.AddMonths(-1).AddDays(1);
}
else
{
from = request.from.Date;
to = request.fromTo.Date;
}
var query = await _dataContext.Users
.Where(p => p.UserDob != null && p.CreatedAt >= from && p.CreatedAt <= to) // exclude null DOB's from stats
.GroupBy(p => p.UserDob.Year)
.Select(g => new FilterByAgeResponseCls{ Age = DateTime.Now.Year - g.Key, Count = g.Count() })
.OrderBy(g => g.Age).ToListAsync().ConfigureAwait(true);

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();
}
}

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.

My Entity Framework query always returns "1"

This is my full code
var SearchValue = dbContext.TimeSheetDetails
.Where(t => t.StartDate == date &&
t.CreatedBy == userId &&
t.ManageTask.ProjectId == projectId)
.ToList()
.Select(x =>
new TimeSheetModel()
{
TaskId = Convert.ToInt32(x.TaskId),
EndDate = x.EndDate,
EndTime = x.EndTime,
Remarks = x.Remarks,
StartDate = x.StartDate,
StartTime = x.StartTime,
Status = Convert.ToInt32(x.StatusType.StatusTypeId),
StatusName = Convert.ToString(x.StatusType.StatusTypeName),
TimeSheetId = x.TimeSheetId,
TotalHours = x.TotalHours,
CreatedBy = Convert.ToInt32(x.CreatedBy),
Description = x.Description,
TaskName = dbContext.TaskDetails.ToList().Where(t => t.TaskId == Convert.ToInt32(x.TaskId)).FirstOrDefault().TaskName
}).ToList();
Everything get correct data, but TaskName is always set to "1". Does not get the correct name.
See this line
TaskName = dbContext.TaskDetails
.ToList()
.Where(t => t.TaskId == Convert.ToInt32(x.TaskId))
.FirstOrDefault().TaskName
This above query always returns a "1", I don't know what the issue is.
You can see the result in quick watch. See the below image
Update
Also i have tried this query instead of my above query
TaskName = x.ManageTask.TaskDetails.FirstOrDefault(t => t.TaskId == Convert.ToInt32(x.TaskId)).TaskName
But no luck, it's does not working, same as return "1"

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