C
C#6mo ago
Core

✅ EF - How to extract LINQ Select into a method does not work

I am trying to only include only the needed columns from a table with EF, by using Select. The following class is given:
c#
public class Animal
{
public int Id { get; set; }
public string? Name { get; set; }
}
c#
public class Animal
{
public int Id { get; set; }
public string? Name { get; set; }
}
Having a DbSet<Animal> Animals, I can do the following to select only the Id:
c#
_dbContext.Animals.Select(a => new Animal {Id = a.Id})
c#
_dbContext.Animals.Select(a => new Animal {Id = a.Id})
This works perfectly, but If I extract the object creation into a method, then the generated SQL query will still select all columns.
c#
_dbContext.Animals.Select(a => SelectOnlyNeeded(a))

private Animal SelectOnlyNeeded(Animal a) {
return new Animal {Id = a.Id};
}
c#
_dbContext.Animals.Select(a => SelectOnlyNeeded(a))

private Animal SelectOnlyNeeded(Animal a) {
return new Animal {Id = a.Id};
}
I am using TPC Inheritance and need to repeat some Select on individual Tables, that's why I am trying to extract the logic from Select. That way I would avoid repetition (DRY) I even tried a static class for creating the object, but it did not work.
c#
private static Animal SelectOnlyNeeded(this Animal a) {
return new Animal {Id = a.Id};
}
c#
private static Animal SelectOnlyNeeded(this Animal a) {
return new Animal {Id = a.Id};
}
Given the outcome, I am left with the impression that it is not possible what I am trying to do, but it would be great if someone proved me wrong.
9 Replies
Pobiega
Pobiega6mo ago
No, its not possible as such due to how EF needs to look at the expression generated by the select There are ways to work around it, but its.. not pretty
Patrick
Patrick6mo ago
create an extension method to operate on IQueryable<Animal>
Core
Core6mo ago
That's what I did, but like this:
c#
private IQueryable<Link> AddSelectStatement(IQueryable<Link> linkQuery, LinkCollectionQueryParams queryParams)
{
if (!string.IsNullOrEmpty(queryParams.Domain))
{
linkQuery = queryParams.Domain == _defaultDomain.Hostname
? linkQuery.Select(l => (Link)MapToUnbrandedLink(l))
: linkQuery.Select(l => (Link)MapToBrandedLink(l));
}
else
{
linkQuery = linkQuery
.Select(l => l is UnbrandedLink ? (Link)MapToUnbrandedLink(l) : MapToBrandedLink(l));
}

return linkQuery;
}
}
c#
private IQueryable<Link> AddSelectStatement(IQueryable<Link> linkQuery, LinkCollectionQueryParams queryParams)
{
if (!string.IsNullOrEmpty(queryParams.Domain))
{
linkQuery = queryParams.Domain == _defaultDomain.Hostname
? linkQuery.Select(l => (Link)MapToUnbrandedLink(l))
: linkQuery.Select(l => (Link)MapToBrandedLink(l));
}
else
{
linkQuery = linkQuery
.Select(l => l is UnbrandedLink ? (Link)MapToUnbrandedLink(l) : MapToBrandedLink(l));
}

return linkQuery;
}
}
These are my actual classes, but as you can see there are multiple Selects which are repeated. That's why I tried to extract the logic
Patrick
Patrick6mo ago
i dont really understand the complexity of that at a crude level you can do:
public static async Task<List<Animal>> ToProjection(this IQueryable<Animal> input)
{
return input.Select(x => new Animal { }).ToListAsync();
}
public static async Task<List<Animal>> ToProjection(this IQueryable<Animal> input)
{
return input.Select(x => new Animal { }).ToListAsync();
}
i also advise you dont project into your actual entity type, and use DTOs instead
Core
Core6mo ago
Thank you guys I do that on Controller level, Business, Repo layer works with entity types
Patrick
Patrick6mo ago
I'm not sure I understand. you're still projecting. You're half hydrating your entity type and abusing it to end up in an illegal state.
Core
Core6mo ago
oh, do you refer to the explicit conversion, like (Link)MapToUnbrandedLink(l)?
Patrick
Patrick6mo ago
I'm referring to the original question:
_dbContext.Animals.Select(a => new Animal {Id = a.Id})
.Select(l => l is UnbrandedLink ? (Link)MapToUnbrandedLink(l) : MapToBrandedLink(l));
.Select(l => l is UnbrandedLink ? (Link)MapToUnbrandedLink(l) : MapToBrandedLink(l));
I don't know what either of these two methods do
Core
Core6mo ago
My bad for not sticking with the initial example. But I understand now what you meant Alright, I ended up with a way cleaner code, thanks!