Extracting LINQ select expression out - c#

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

Related

A lambda expression with a expression body cannot be converted to an expression tree C# entity framework

I have an entity table with below model
public class VulnerabilityImportFileLog
{
public Collection<Vulnerability> Vulnerability { get; set; }
}
Trying to return an string value using the select statement in the entity framework query
var vulnerabilityImportFileLogSelect = vulnerabilityImportFileLogQuery
.Select(vulnerabilityImportFileLog =>
new VulnerabilityImportedScansListViewDto
{
AssetName = vulnerabilityImportFileLog.Vulnerability.Select(y =>
{
if (y.AssetHostIPOrDomain.HostIPAssetId.HasValue)
return y.AssetHostIPOrDomain.HostIPAsset.Title;
else if (y.AssetHostIPOrDomain.DomainAssetId.HasValue)
return y.AssetHostIPOrDomain.DomainAsset.Title;
else
return y.AssetHostIPOrDomain.HostIPOrDomain;
}
).Distinct().ToList()
});
Where AssetName is of type List<string> and getting an exception as A lamda expression with a statement body cannot be converted to an expression tree
An suggested answer was to use AsEnumerable(), but even that not solved the issue.
You can solve the problem by using linq query syntax with 'let' keyword:
AssetName = (from y in vulnerabilityImportFileLog.Vulnerability
let resultString = y.AssetHostIPOrDomain.HostIPAssetId.HasValue
? y.AssetHostIPOrDomain.HostIPAsset.Title
: y.AssetHostIPOrDomain.DomainAssetId.HasValue
? y.AssetHostIPOrDomain.DomainAsset.Title
: y.AssetHostIPOrDomain.HostIPOrDomain
select resultString
)
.ToList()
.Distinct();
Assuming vulnerabilityImportFileLogQuery is an IQueryable<vulnerabilityImportFileLog> then a typical way to fetch details from the associated Vulnerability relations would be via a SelectMany. Given you need to do substitution depending on what is available, one option would be to do a double-projection. The first pass selects and materializes just the details we want to inspect, then the second pass composes the results.
vulnerabilityImportFileLogQuery.SelectMany(x => new
{
x.Vulnerability.AssetHostIpOrDomain.HostIpOrDomain,
HostIpAssetTitle = x.Vulnerability.AssetHostIpOrDomain.HostIpAsset.Title,
DomainAssetTitle = x.Vulnerability.AssetHostIpOrDomain.DomainAsset.Title
}).ToList()
.Select(x => x.HostIpAssetTitle ?? DomainAssetTitle ?? HostIpOrDomain)
.Distinct()
.ToList();
What this essentially does is fetch the 3 values, our default HostIpOrDomain value, and the Titles from the HostIpAsset and DomainAsset if they are available. EF will return #null if the entities aren't associated or the Title is #null. This query is materialized using the ToList, then from that we Select the title to use based on a null check, before taking our Distinct set.
Hopefully that gives you a few ideas as a starting point.

Entity Framework SQL Query Generation

I am having difficulty understanding how Entity Framework generates a single SQL Query from a couple of Linq methods. Here is a code which selects Username from a table.
var result1 = context.UserMasters
.Where(t => t.UserRole == "LOCAL")
.Skip(100)
.Take(100)
.Select(t => new { t.UserName });
Typically the above code is identical to the below code.
var test = context.UserMasters.Where(t=> t.UserRole == "LOCAL");
test = test.Skip(100);
test = test.Take(100);
var result2 = test.Select(t => new { t.UserName });
I hope we can get results from the table after any Linq Method. So there is a list readily available always. Is it selecting all results from the table initially and doing operation on the list ?
If the type is IQueryable, then the filtering of data happens at the database side. But if the type of query is IEnumerable or other than IQueryable, all the data from the table is fetched and filtering is done at .Net side. It is always advised to use IQueryable in these cases. Please read about IQueryable and IEnumerable difference.

Querying with methods in EF Core?

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.

NOT IN Condition in Linq

I have a simple scenario.I want to list out all the employees except the logged in user.
Similar SQL Condition is
select * from employee where id not in(_loggedUserId)
How can I acheive the above using LINQ.I have tried the following query but not getting the desired list
int _loggedUserId = Convert.ToInt32(Session["LoggedUserId"]);
List<int> _empIds = _cmn.GetEmployeeCenterWise(_loggedUserId)
.Select(e => e.Id)
.Except(_loggedUserId)
.ToList();
Except expects argument of type IEnumerable<T>, not T, so it should be something like
_empIds = _cmn.GetEmployeeCenterWise(_loggedUserId)
.Select(e => e.Id)
.Except(new[] {_loggedUserId})
.ToList();
Also note, this is really redundant in the case when exclusion list contains only one item and can be replaces with something like .Where(x => x != _loggedUserId)
Why not use a very simple Where condition?
_empIds = _cmn.GetEmployeeCenterWise(_loggedUserId).Where(e=>e.Id != _loggedUserId).ToList();
The title of your question is how to perform a not in query against a database using LINQ. However, as others have pointed out your specific problem is better solved by a using users.Where(user => user.Id != loggedInUserId).
But there is still an answer on how to perform a query against a database using LINQ that results in NOT IN SQL being generated:
var userIdsToFilter = new[] { ... };
var filteredUsers = users.Where(user => !userIdsToFilter.Contains(user.Id));
That should generate the desired SQL using either Entity Framework or LINQ to SQL.
Entity Framework also allows you to use Except but then you will have to project the sequence to ID's before filtering them and if you need to original rows you need to fetch them again from the filtered sequence of ID's. So my advice is use Where with a Contains in the predicate.
Use LINQ without filtering. This will make your query execute much faster:
List<int> _empIds = _cmn.GetEmployeeCenterWise(_loggedUserId)
.Select(e => e.Id).ToList();
Now use List.Remove() to remove the logged-in user.
_empIds.Remove(_loggedUserId);

Linq to sql expression tree execution zone issue

I have got a bit of an issue and was wondering if there is a way to have my cake and eat it.
Currently I have a Repository and Query style pattern for how I am using Linq2Sql, however I have got one issue and I cannot see a nice way to solve it. Here is an example of the problem:
var someDataMapper = new SomeDataMapper();
var someDataQuery = new GetSomeDataQuery();
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(x => someDataMapper.Map(x));
return results.Where(x => x.SomeMappedColumn == "SomeType");
The main bits to pay attention to here are Mapper, Query, Repository and then the final where clause. I am doing this as part of a larger refactor, and we found that there were ALOT of similar queries which were getting slightly different result sets back but then mapping them the same way to a domain specific model. So take for example getting back a tbl_car and then mapping it to a Car object. So a mapper basically takes one type and spits out another, so exactly the same as what would normally happen in the select:
// Non mapped version
select(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
// Mapped version
select(x => carMapper.Map(x));
So the car mapper is more re-usable on all areas which do similar queries returning same end results but doing different bits along the way. However I keep getting the error saying that Map is not able to be converted to SQL, which is fine as I dont want it to be, however I understand that as it is in an expression tree it would try to convert it.
{"Method 'SomeData Map(SomeTable)' has no supported translation to SQL."}
Finally the object that is returned and mapped is passed further up the stack for other objects to use, which make use of Linq to SQL's composition abilities to add additional criteria to the query then finally ToList() or itterate on the data returned, however they filter based on the mapped model, not the original table model, which I believe is perfectly fine as answered in a previous question:
Linq2Sql point of retrieving data
So to sum it up, can I use my mapping pattern as shown without it trying to convert that single part to SQL?
Yes, you can. Put AsEnumerable() before the last Select:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.AsEnumerable()
.Select(x => someDataMapper.Map(x));
Please note, however, that the second Where - the one that operates on SomeMappedColumn - will now be executed in memory and not by the database. If this last where clause significantly reduces the result set this could be a problem.
An alternate approach would be to create a method that returns the expression tree of that mapping. Something like the following should work, as long as everything happening in the mapping is convertible to SQL.
Expression<Func<EntityType, Car>> GetCarMappingExpression()
{
return new Expression<Func<EntityType, Car>>(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
}
Usage would be like this:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(GetCarMappingExpression());

Categories

Resources