EF linq query where condition based on bool parameter - c#

I have an EF linq query where I want to set a where condition based on a true/false parameter. I can do something similar in SQL which is somewhat how I've written my EF query but it doesnt seem to work with EF Core.
When I expect the executed SQL if I pass "activeOnly = true" then no SQL where condition is applied and when I pass "activeOnly = false" then the condition is "where 0 = 1".
Clearly I have this wrong but I cant figure out the right way to do this? Or if its possible?
public async Task<List<UserDto>> GetUsersAsync(string region, bool activeOnly)
{
var allUsers = new List<UserDto>();
var countries = _configuration.GetRegionConfiguration(region).Countries;
foreach (var country in countries)
{
_context.ChangeConnectionString(country.DatabaseConnectionString);
var users = await _context.User
.ProjectTo<UserDto>(_mapper.ConfigurationProvider)
.Where(x => (activeOnly && (x.RoleIds != null)) || (!activeOnly && (x.RoleIds == null)))
.ToListAsync();
allUsers.AddRange(users);
}
allUsers.OrderBy(x => x.FullName);
return allUsers;
}

Current versions emit a helpful warning when you use this kind of query:
Collection navigations are only considered null if their parent entity is null. Use 'Any' to check whether collection navigation 'YourEntity.YourNavigationColleciton' is empty.
So instead of
x.RoleIds != null
use
x.RoleIds.Any()

Related

.NET Core 3 InvalidOperationException on OrderBy with dynamic field name

I'm migrating an existing web API from .NET Core 2 o 3 version.
After several problems, I manage to make it work, with the exception of Dynamic OrderBy by column name.
This is my code, that worked great with .net core 2:
public async Task<IEnumerable<Clientes_view>> GetClientes(int bActivos, int nRegistroInic, int nRegistros, string sOrdenar,
int nSentido, string sFiltro, int nTipo = -1, int idCliente = -1)
{
var clientes = this.context.Set<Clientes_view>()
.Where(e => e.RazonFantasia.Contains(sFiltro) || e.RazonFantasia.Contains(sFiltro)
|| e.Cuit.Contains(sFiltro) || e.Mail.StartsWith(sFiltro) || string.IsNullOrEmpty(sFiltro))
.Where(e => (e.Activo && bActivos == 1) || bActivos == -1 || (!e.Activo && bActivos == 0))
.Where(e => e.IdTipoCliente == nTipo || nTipo == -1)
.Where(e => e.IdCliente == idCliente || idCliente == -1);
if (!string.IsNullOrEmpty(sOrdenar))
{
var propertyInfo = this.context.Set<Clientes_view>().First().GetType().GetProperty(sOrdenar,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo != null) if (nSentido == -1) clientes = clientes.OrderByDescending(e => propertyInfo.GetValue(e, null));
else clientes = clientes.OrderBy(e => propertyInfo.GetValue(e, null));
}
clientes = clientes.Skip(nRegistroInic).Take(nRegistros);
return await clientes.ToListAsync();
}
And the error I'm getting is the following:
System.InvalidOperationException: The LINQ expression 'DbSet
.Where(c => True)
.Where(c => c.Activo && True || False || False)
.Where(c => True)
.Where(c => True)
.OrderBy(c => __propertyInfo_3.GetValue(
obj: c,
index: null))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
Any thoughts?
Thanks!
You need to actually generate the member access expression, all you've done was used reflection to get the value of some object, and provided that as the expression. That will not work, the query provider will not be able to translate that.
You need to do something like this:
if (!String.IsNullOrEmpty(sOrdenar))
{
var type = typeof(Clientes_view);
var prop = type.GetProperty(sOrdenar);
if (prop != null)
{
var param = Expression.Parameter(type);
var expr = Expression.Lambda<Func<Clientes_view, object>>(
Expression.Convert(Expression.Property(param, prop), typeof(object)),
param
);
if (nSentido == -1)
clientes = clientes.OrderByDescending(expr);
else
clientes = clientes.OrderBy(expr);
}
}
Your problem is that you are using reflection inside of order by, while probably you should use sorting string.
One of the options
Install-Package System.Linq.Dynamic
using System.Linq.Dynamic;
then you can sort
query.OrderBy("item.item_id DESC")
Other option without any library in case you dont have many sort options would be:
switch(sOrdenar){
case "Field1"
clientes = nSentido == -1 ? clientes.OrderBy(entity=> entity.Field1) : clientes.OrderByDescending(entity=> entity.Field1);
break;
case "OtherField"
clientes = nSentido == -1 ? clientes.OrderBy(entity=> entity.OtherField) : clientes.OrderByDescending(entity=> entity.OtherField);
break;
}
Personally I prefer second option better, because then I can be sure that user is able to sort only on allowed fields otherwise you can have performance issues in case you have large tables and users start sorting on wrong fields (Never trust your users :) ).
EF Core attempts to translate as much of your query to a server-side query (i.e. SQL) as possible. In versions before 3.0, any code that could not be converted was silently run on the client - however, this can cause massive and often unintuitive performance issues, so from 3.0 the decision was made that if any query code cannot be translated, an exception would immediately be thrown.
Reference: https://learn.microsoft.com/en-us/ef/core/querying/client-eval#previous-versions
The end result is that you either need to rearchitect your code to separate the parts that can and can't be run on the server, or alternatively force everything to be run on the client. The referenced document explains how to achieve the latter, but note that doing so will likely have significant performance impact.
In your case, the stuff inside the if (!string.IsNullOrEmpty(sOrdenar)) block is what is causing the problem. You should be aware that this implies that whenever that block has been executed, the paging that follows it (Skip and Take) has not been executed on the server, always the client - so if you've ever had performance problems with this method, now you know why!
It's pretty obvious that calling properties via reflection can't be automatically translated into SQL query.
The only ways it could have worked before was either that this branch was never taken, or the whole query was processed by your application instead of on the database side.
To fix this, do as the error message suggests: break the query into DB and application parts, e.g.
if (!string.IsNullOrEmpty(sOrdenar))
{
IEnumerable<Clientes_view> list = await clientes.AsAsyncEnumerable();
list = list.Where(.....); //here you may use everything you like
return list;
}
If you are searching for a way to generate the OrderBy part dynamically on the server side, take a look at this answer; apparently it's written for classic EF, but should probably work in EF Core with minor adjustments.

Make and Expression available to be used within a Linq to EntityFramework Expression

I have the following LINQ Expression that I need to be able to use within a LINQ to Entities query.
There is currently a property like this:
[NotMapped]
public Clinic CurrentClinic
{
get { return AdmissionRoot.CurrentFacility.Compile()(this); }
}
Which called the following:
public static Expression<Func<Admission, Clinic>> CurrentFacility =
a => a.Person.PersonLocations.Any(p => p.Clinic.FacilityType != (int)Clinic.FacilityTypes.CommunityServices)
? a.Person.PersonLocations.Where(p => p.Clinic.FacilityType != (int)Clinic.FacilityTypes.CommunityServices)
.OrderByDescending(l => l.TransferDate)
.ThenByDescending(l => l.LocationId)
.FirstOrDefault().Clinic
: a.Clinic;
The issue is that I cannot use CurrentClinic within a LINQ to Entities statement because it will give the following error:
"The specified type member 'CurrentClinic' is not supported in LINQ to
Entities"
I am new to LINQ to Entities expressions and was hoping someone would be able to refactor this so it can be used within a LINQ to Entities statement something like this:
Db.Admissions.Where(
a =>
(a.CurrentClinic != null
&& !a.CurrentClinic.Company.IsHomeCompany
&& a.ReferralClinicId == clinicid
)
||
(a.CurrentClinic.Company.IsHomeCompany
&& a.CurrentClinic.ClinicId == clinicid
)
);
If it can't be done in an Expression that I can "add" other criteria onto the end then is there another way/suggestion that would work without compromising speed too much?
The issue here is because you're using CurrentClinic in your query which is not something that Entity framework can turn into an expression. A possible alternate way to run your query could be
Db.Admissions
.Select(a => new
{
CurrentClinic = CurrentFacility(a),
Admission = a
})
.Where(a =>
(a.CurrentClinic != null && !a.CurrentClinic.Company.IsHomeCompany && a.Admission.ReferralClinicId == clinicid) ||
(a.CurrentClinic.Company.IsHomeCompany && a.CurrentClinic.ClinicId == clinicid));
Note: this is untested but should still work.

Entity Framework error using DefaultIfEmpty()

I have a problem with Entity Framework using the DefaultIfEmpty method. The following query is returning empty when it should return an offer that matches all criteria in the database.
If I remove one or both DefaultIfEmpty method calls it works, but with them it doesn't. I need those to prevend another problem in the query.
When I execute the generated SQL query directly on the database it works and it returns the offer.
I also made an Unit Test reproducing the same example and it also passes so it must be an Entity Framework issue.
Here's the query:
private static Expression<Func<Offer, bool>> AddFilter(Service criteria)
{
return offer => offer.Restrictions.
SelectMany(rest => rest.OperatorRange.DefaultIfEmpty(), (rest, alop) => new { Restriction = rest, OperatorRange = alop.Id }).
Where(alop => criteria.ServiceUseNet == null || alop.OperatorRange.ToUpper() == criteria.ServiceUseNet.ToUpper()).
SelectMany(rest => rest.Restriction.CallType.DefaultIfEmpty(), (rest, till) => new { Restriction = rest, CallType = till.Id }).
Any(till => criteria.UseServiceCoverage == null || till.CallType.ToUpper() == criteria.UseServiceCoverage.ToUpper());
}
Change it into two Any calls:
return offer => offer.Restrictions
.Any(rest
=> rest.OperatorRange
.Where(alop => criteria.ServiceUseNet == null
|| alop.OperatorRange.ToUpper() == criteria.ServiceUseNet.ToUpper())
.Any(till => criteria.UseServiceCoverage == null
|| till.CallType.ToUpper() == criteria.UseServiceCoverage.ToUpper()));
The predicate is supposed to test whether there are any OperatorRanges (meeting some criteria) having any CallTypes meeting some criteria. If there are no OperatorRanges, there won't be any CallTypes either, let alone matching CallTypes.
In this form, the predicate always returns true or false.

Multiple WHERE's in same LINQ 2 SQL Method

I have the below LINQ Method I am trying to create. The issue seems to be the Second WHERE clause. I am getting this error -->
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<MatrixReloaded.Data.CMO.tblWorkerHistory>' to 'bool'
I also had && there vs WHERE but I was getting a similar error. I don't NEED anything from tblWorkerHistories except the EndDate stuff.
There is a Many To Many relationship between the 2 tables with EnrollmentID being a FK on both.
public static DataTable GetCurrentWorkersByEnrollmentID(int enrollmentID)
{
using (var context = CmoDataContext.Create())
{
context.Log = Console.Out;
var currentWorkers = from enrollment in context.tblCMOEnrollments
where enrollment.EnrollmentID == enrollmentID
where enrollment.tblWorkerHistories.Where(a => a.EndDate == null || a.EndDate > DateTime.Now)
select
new
{
enrollment.CMONurseID,
enrollment.CMOSocialWorkerID,
SupportWorkerName = enrollment.tblSupportWorker.FirstName + " " + enrollment.tblSupportWorker.LastName,
SupportWorkerPhone = enrollment.tblSupportWorker.Phone
};
return currentWorkers.CopyLinqToDataTable();
}
}
This is the problem:
where enrollment.tblWorkerHistories.Where(/* stuff */)
Where returns a sequence... whereas you need something that will return a Boolean value. What are you trying to do with that embedded Where clause?
As Marc says, it could be that you just need an Any call instead of Where... but if you could explain what you're trying to do, that would make it a lot easier to help you. Note that Any does return a Boolean value, instead of a sequence.
EDIT: Okay, so in SQL you'd use a join, but you don't need an explicit join here because LINQ is implicitly doing that for you, right? If you're trying to find enrollments where any of the histories match the date, and you don't care about the histories themselves, then Any is indeed what you want:
var currentWorkers = from enrollment in context.tblCMOEnrollments
where enrollment.EnrollmentID == enrollmentID
where enrollment.tblWorkerHistories.Any
(a => a.EndDate == null || a.EndDate > DateTime.Now)
select ...
I suspect you mean .Any instead of .Where in the sub-query; the outermost .Where (i.e. the second where) expects a predicate expression, but yours is currently a selector - try:
where enrollment.tblWorkerHistories.Any(
a => a.EndDate == null || a.EndDate > DateTime.Now)

Dynamic WHERE clause in LINQ

What is the best way to assemble a dynamic WHERE clause to a LINQ statement?
I have several dozen checkboxes on a form and am passing them back as: Dictionary<string, List<string>> (Dictionary<fieldName,List<values>>) to my LINQ query.
public IOrderedQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string,List<string>> filterDictionary)
{
var q = from c in db.ProductDetail
where c.ProductGroupName == productGroupName && c.ProductTypeName == productTypeName
// insert dynamic filter here
orderby c.ProductTypeName
select c;
return q;
}
(source: scottgu.com)
You need something like this? Use the Linq Dynamic Query Library (download includes examples).
Check out ScottGu's blog for more examples.
I have similar scenario where I need to add filters based on the user input and I chain the where clause.
Here is the sample code.
var votes = db.Votes.Where(r => r.SurveyID == surveyId);
if (fromDate != null)
{
votes = votes.Where(r => r.VoteDate.Value >= fromDate);
}
if (toDate != null)
{
votes = votes.Where(r => r.VoteDate.Value <= toDate);
}
votes = votes.Take(LimitRows).OrderByDescending(r => r.VoteDate);
You can also use the PredicateBuilder from LinqKit to chain multiple typesafe lambda expressions using Or or And.
http://www.albahari.com/nutshell/predicatebuilder.aspx
A simple Approach can be if your Columns are of Simple Type like String
public static IEnumerable<MyObject> WhereQuery(IEnumerable<MyObject> source, string columnName, string propertyValue)
{
return source.Where(m => { return m.GetType().GetProperty(columnName).GetValue(m, null).ToString().StartsWith(propertyValue); });
}
It seems much simpler and simpler to use the ternary operator to decide dynamically if a condition is included
List productList = new List();
productList =
db.ProductDetail.Where(p => p.ProductDetailID > 0 //Example prop
&& (String.IsNullOrEmpty(iproductGroupName) ? (true):(p.iproductGroupName.Equals(iproductGroupName)) ) //use ternary operator to make the condition dynamic
&& (ID == 0 ? (true) : (p.ID == IDParam))
).ToList();
I came up with a solution that even I can understand... by using the 'Contains' method you can chain as many WHERE's as you like. If the WHERE is an empty string, it's ignored (or evaluated as a select all). Here is my example of joining 2 tables in LINQ, applying multiple where clauses and populating a model class to be returned to the view. (this is a select all).
public ActionResult Index()
{
string AssetGroupCode = "";
string StatusCode = "";
string SearchString = "";
var mdl = from a in _db.Assets
join t in _db.Tags on a.ASSETID equals t.ASSETID
where a.ASSETGROUPCODE.Contains(AssetGroupCode)
&& a.STATUSCODE.Contains(StatusCode)
&& (
a.PO.Contains(SearchString)
|| a.MODEL.Contains(SearchString)
|| a.USERNAME.Contains(SearchString)
|| a.LOCATION.Contains(SearchString)
|| t.TAGNUMBER.Contains(SearchString)
|| t.SERIALNUMBER.Contains(SearchString)
)
select new AssetListView
{
AssetId = a.ASSETID,
TagId = t.TAGID,
PO = a.PO,
Model = a.MODEL,
UserName = a.USERNAME,
Location = a.LOCATION,
Tag = t.TAGNUMBER,
SerialNum = t.SERIALNUMBER
};
return View(mdl);
}
Just to share my idea for this case.
Another approach by solution is:
public IOrderedQueryable GetProductList(string productGroupName, string productTypeName, Dictionary> filterDictionary)
{
return db.ProductDetail
.where
(
p =>
(
(String.IsNullOrEmpty(productGroupName) || c.ProductGroupName.Contains(productGroupName))
&& (String.IsNullOrEmpty(productTypeName) || c.ProductTypeName.Contains(productTypeName))
// Apply similar logic to filterDictionary parameter here !!!
)
);
}
This approach is very flexible and allow with any parameter to be nullable.
You could use the Any() extension method. The following seems to work for me.
XStreamingElement root = new XStreamingElement("Results",
from el in StreamProductItem(file)
where fieldsToSearch.Any(s => el.Element(s) != null && el.Element(s).Value.Contains(searchTerm))
select fieldsToReturn.Select(r => (r == "product") ? el : el.Element(r))
);
Console.WriteLine(root.ToString());
Where 'fieldsToSearch' and 'fieldsToReturn' are both List objects.
This is the solution I came up with if anyone is interested.
https://kellyschronicles.wordpress.com/2017/12/16/dynamic-predicate-for-a-linq-query/
First we identify the single element type we need to use ( Of TRow As DataRow) and then identify the “source” we are using and tie the identifier to that source ((source As TypedTableBase(Of TRow)). Then we must specify the predicate, or the WHERE clause that is going to be passed (predicate As Func(Of TRow, Boolean)) which will either be returned as true or false. Then we identify how we want the returned information ordered (OrderByField As String). Our function will then return a EnumerableRowCollection(Of TRow), our collection of datarows that have met the conditions of our predicate(EnumerableRowCollection(Of TRow)). This is a basic example. Of course you must make sure your order field doesn’t contain nulls, or have handled that situation properly and make sure your column names (if you are using a strongly typed datasource never mind this, it will rename the columns for you) are standard.
System.Linq.Dynamic might help you build LINQ expressions at runtime.
The dynamic query library relies on a simple expression language for formulating expressions and queries in strings.
It provides you with string-based extension methods that you can pass any string expression into instead of using language operators or type-safe lambda extension methods.
It is simple and easy to use and is particularly useful in scenarios where queries are entirely dynamic, and you want to provide an end-user UI to help build them.
Source: Overview in Dynamic LINQ
The library lets you create LINQ expressions from plain strings, therefore, giving you the possibility to dynamically build a LINQ expression concatenating strings as you require.
Here's an example of what can be achieved:
var resultDynamic = context.Customers
.Where("City == #0 and Age > #1", "Paris", 50)
.ToList();

Categories

Resources