Querying with methods in EF Core? - c#

I'm trying to figure out a way to use methods in a SELECT query in EF Core to reduce the amount of code that is rewritten over and over again. Instead a simple method could be used to query that piece of data. The query looks like so...
var users = await DbContext.Users.Where(user => user.Id == 1)
.Select(user => new UserDTO
{
Id = user.Id,
Name = user.Name,
Unavailable = user.Tasks
.Any(task => task.HasTasksToday()),
}).ToListAsync();
The HasTasksToday() code looks like so...
public bool HasTasksToday()
{
return Tasks.Any(task => task.StartedAt.Date == DateTime.Now.Date);
}
The problem is I get a...
...could not be translated. Either rewrite the query in a form that can be translated...
...error.
I know that using IQueryable may work for this but I am unsure how that would even happen in the select statement as I know only how to use IQueryable on the Users entity.

Related

Extracting LINQ select expression out

I have a LINQ chained query which looks like this
return incidents
.Include(x => x.Submission)
.ThenInclude(x => x.Answer)
.Select(incident => new Incident
{
Id = incident.Id,
Submission = incident.Submission.Select(submission => new Submission
{
Id = submission.Id,
Answer = context.SubFields.ContainsKey("answer") ? submission.Answer : null
}).ToList()
incidents is of type IQueryable<Incident> and I am trying to select particular fields so the SQL query generated when we hit the database doesn't select every single property, some of which we don't need and are making the query slow. The ternary expression determines if we actually need that particular property selected from the DB (this comes from a GraphQL query so we don't know ahead of time). This code all works at the moment and generates the most optimal SQL query.
What I want to do is extract the lambda expression from within the Submission's select method out into a separate method. As we will be using Submission beyond this Incident query, I want to extract it to a method which I can reuse.
In an ideal world, I am trying to get my code to look like the following, but I don't know how to do the expression generation. How do I do this?
Submission = incident.Submission.Select(GENERATE_AN_EXPRESSION(context.SubFields["submission"])).ToList()
GENERATE_AN_EXPRESSION(Field field)
{
return submission => new ApplicationCore.Entities.Submission
{
Id = submission.Id,
Answer = submission.Answer.SelectAnswer(field.SelectionSet.GetField("answer"))
};
}

LINQ include a subset of a query

I have an Entity in EF Core that is using the structure like:
Course has an entity CourseUserRoles which has the CourseId, UserId and RoleId.
CourseViewModel has the same structure as Course, except CourseUserRoles, instead it has two booleans IsAdministrator and IsContributor, that are related to the RoleId.
I am trying to make a query that won't bring all CourseUserRoles for every course queried, but only the ones specific for that user.
I saw that syntactically the below is correct:
query = query.Include(x => x.CourseUserRoles.Where(y => y.UserId == userId));
where the query is trying to return a list of courses, I just want to include the ones that have the same Id as the user.
The problem is that the above is throwing an exception.
Is it possible to only include the CourseUserRoles when the course has the UserId? If it doesn't have it would return null or empty list.
I typically do this by creating separate queries and concatenating as follows:
query = query.Where(x => x.CourseUserRoles.UserId != userId)
var queryWithInclude = query.Where(x => x.CourseUserRoles.UserId == userId)
.Include(x => x.CourseUserRoles)
var fullDataset = query.Concat(queryWithInclude);
This keeps both query objects as type IQueryable and not IEnumerable, allowing execution to happen in SQL/server-side rather than in memory.

Reuse Linq to SQL code with entityframework

I am currently doing some refactor on code that makes my application very slow. I am pretty far but i am still missing some pieces of the puzzle, i hope you can help me.
I like to reuse some Linq to SQL code inside of my project.
This is my way of doing it at this moment:
public DomainAccount GetStandardUserAccount()
{
return this.DomainAccounts.Where(da => da.DomainAccountType == DomainAccountType.Standarduser).First() as DomainAccount;
}
var CurrentSituation = _context.Employees.ToList().Where(e => e.GetStandardUserAccount().Username.Contains("test")).ToList();
A small clarification: Every employee has multiple domain accounts where one always is a standarduser(DomainAccountType) domainaccount.
Because Linq can not convert an C# methode to an sqlstatement (Eventho its linq to sql code only) I have to convert the dbset to a list first so i can use the GetStandardUserAccount(). This code is is slow because of this whole dbset conversion. Is there a way i can reuse linq to sql code without turning it in an methode? I have read some threads and this is what I got untill now:
Func<Employee, DomainAccount> GetStandardDomainAccount = x => x.DomainAccounts.FirstOrDefault(d => d.DomainAccountType == DomainAccountType.Standarduser);
var TheGoal = _context.Employees.Where(e => e.GetStandardDomainAccount().Username.Contains("Something")).ToList();
The answer to this question is a bit more complicated than it looks. In order to let linq execute C# code you need to make the function an expression so the in and output will be interperted not as code but as some sort of a meaning. The solution looks like this:
private Expression<Func<TPeople, bool>> GetDefaultDomainAccount<TPeople>(Func<DomainAccount, bool> f) where TPeople : Person
{
return (a) => f(a.DomainAccounts.FirstOrDefault(d => d.DomainAccountType == DomainAccountType.Standarduser));
}
Now the code can be called uppon like this:
public IQueryable<TPeople> GetPeopleByUsername<TPeople>(string username) where TPeople : Person
{
GetPeople<TPeople>().Where(GetDefaultDomainAccount<TPeople>(d => d.Username == username));
return people;
}
instead of this:
public IQueryable<TPeople> GetPeopleByUsername<TPeople>(string username) where TPeople : Person
{
username = username.ToUpper();
var people = GetPeople<TPeople>()
.Where(a => a.DomainAccounts.FirstOrDefault(d => d.DomainAccountType == DomainAccountType.Standarduser).Username.ToUpper().Contains(username));
return people;
}

Strange behavior of INCLUDE in Entity Framework

This works:
using (var dbContext = new SmartDataContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var query = dbContext.EntityMasters.OfType<Person>();
if (includeAddress)
query.Include(p => p.Addresses);
if (includeFiles)
query.Include(p => p.FileMasters);
output.Entity = query.Include(s=>s.Addresses).FirstOrDefault<Person>(e => e.EntityId == id);
}
while this doesn't:
using (var dbContext = new SmartDataContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var query = dbContext.EntityMasters.OfType<Person>();
if (includeAddress)
query.Include(p => p.Addresses);
if (includeFiles)
query.Include(p => p.FileMasters);
output.Entity = query.FirstOrDefault<Person>(e => e.EntityId == id);
}
I am trying to include Addresses, Files based on boolean flags coming from function. However it seems, EF not including them when using IF condition.
This is related to my previous question which actually worked using Include.
You need to assign the result of Include back to query
query = query.Include(p => p.Addresses);
Entity framework's 'Include' function only works when it is connected to the entire linq query that was looking up the entity. This is because the linq query is actually a form of Expression that can be inspected as a whole before it is executed.
In the second example there the Person object is already detached from the database so EF has no information on which table Person came from and how it should join Person with the address table to get the results you want.
If you turn on dynamic proxy generation EF is able to keep track of the relation between the entity and the database. However, I'm not sure if this will make the include statement work.

EF: how to tackle many-to-many relation (some filtering involved)

I am a beginner in EF and LINQ and I would like to retrieve a list of categories (with filter) by product id.
So, I have many-to-many relation between Product * <---> * Category and I use the following code:
var categList = dbContext.Products
.Where(prod => prod.PROD_UID == 1234)
.SelectMany(prod => prod.Categories)
.Distinct();
categList = SearchCategories(filter, categList);
categList = SortCategories(filter, categList);
categList = PageCategories(filter, categList);
where SearchCategories is used to reuse some code and looks like below
IQueryable<Category> SearchCategories(MyFilter filter, IQueryable<Category> source = null)
{
source = source ?? this.dbContext.Categories;
return source.Where(cat => [...filtering...] );
}
Although this looks ok, I would like to have it slightly optimized, with filtering inside the SelectMany (make use of SearchCategories inside SelectMany)... but I cannot make it work. I tried this, but gives me error
var categList = dbContext.Products
.Where(prod => prod.PROD_UID == 1234)
.SelectMany(cat => SearchCategories(filter, prod.Categories.AsQueryable()).AsEnumerable());
// throws LINQ to Entities does not recognize the method 'SearchCategories'
How could I filter the categories inside SelectMany?
Thank you!
Your problem is you are confusing the server query with the client query there is no magic here.
your first query until Distinct is serialized and sent to the server, then the server sends a response and you then run a filter in your client.
when you put the SearchCategories in the server query it cant be resolver so you get the error.
you have two options here:
1:Just write all your query from SearchCategories in the first query making it run in the server
.SelectMany(prod => prod.Categories.Where(c => [...filtering...]))
remember the filtering cant call client code.
2:You put a ToList or ToArray and then use the SearchCategories but this option wont optimize anything.

Categories

Resources