Use Intersect inside Where clause - c#

I'm trying to select a subset of two lists of the same object as part of a where clause, and getting an error.
{"Expression of type 'System.Linq.IQueryable1[<>f__AnonymousType772[System.Int32,System.Int32]]' cannot be used for parameter of type 'System.Linq.IQueryable1[Entity.Entities.QuestionModuleQuestionDto]' of method 'System.Linq.IQueryable1[Entity.Entities.QuestionModuleQuestionDto] Where[QuestionModuleQuestionDto](System.Linq.IQueryable1[Entity.Entities.QuestionModuleQuestionDto], System.Linq.Expressions.Expression1[System.Func`2[Entity.Entities.QuestionModuleQuestionDto,System.Boolean]])' (Parameter 'arg0')"}
public async Task<bool> ValidateQuestions(List<QuestionModuleQuestion> questions)
{
var questionModules = await _context.QuestionModule
.Include(qm => qm.Questions)
Where(qm =>
qm.Questions.Select(q => new {q.QuestionId, q.Sequence})
.Intersect(questions.Select(q => new {q.QuestionId, q.Sequence})).Any())
.ToListAsync();
return questionModules.Any(qm => qm.Questions.Count == questions.Count);
}
I rewrote the clause to look like the following, but I'm not too terribly fond of this approach.
var questionModules = await _context.QuestionModule.Include(qm => qm.Questions)
.Where(qm => qm.Questions.Any(qmq => questions.Any(question =>
question.QuestionId == qmq.QuestionId && question.Sequence == qmq.Sequence)))
.ToListAsync();

EF Core has limited support for operations with local collections. You can use only Contains and very simple Any.
You can use this extension FilterByItems and rewrite query in the following way. Query refactored to be effective and I hope that I have not changed requirements.
public async Task<bool> ValidateQuestions(List<QuestionModuleQuestion> questions)
{
var questionCount = questions.Count;
var query = _context.QuestionModule
Where(qm =>
qm.Questions.AsQueryable()
.FilterByItems(questions, (q, i) => q.QuestionId == i.QuestionId && q.Sequence == i.Sequence, true)
.Count() == questionCount);
return await questionModules.AnyAsync();
}

Related

C# linq Contains method with List

I need help with Linq Contains method. Here's the code below.
This code does work but outputs an empty sets.
var query = _context.RegistrationCodes.Select(x => x);
if (request.OperatorId != null && request.OperatorId != Guid.Empty)
{
var checkOperator = _context.Operators.Include(a => a.OperatorLevel).Include(a => a.City).Include("City.StateRegion.Country").FirstOrDefault(a => a.Id == request.OperatorId);
List<String> Cities = new List<String>();
if (checkOperator.OperatorLevel.Name == "City")
{
Cities = await _context.Cities
.Where(a => (checkOperator.CityId) == (a.Id))
.Select(a => a.Code)
.ToListAsync();
}
else if (checkOperator.OperatorLevel.Name == "Regional")
{
Cities = await _context.Cities
.Where(a => checkOperator.City.StateRegionId == a.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
}
else if (checkOperator.OperatorLevel.Name == "National")
{
List<Guid> StateRegion = await _context.StateRegions
.Where(a => checkOperator.City.StateRegion.CountryId == a.CountryId)
.Select(a => a.Id)
.ToListAsync();
Cities = await _context.Cities
.Where(a => StateRegion.Contains(a.StateRegionId))
.Select(a => a.Code)
.ToListAsync();
}
var nullableStrings = Cities.Cast<String?>().ToList();
query = query.Where(a => nullableStrings.Contains(a.Code));
}
I need to compare nullableStrings to a.Code which is something like this, but does not work.
query = query.Where(a => a.Code.Contains(nullableStrings));
Error : Argument 1: cannot convert from 'System.Collections.Generic.List' to 'char'
I need a method that would replace
query = query.Where(a => nullableStrings.Contains(a.Code));
A help would be appreciated. Thanks.
Looking at the code, my guess is the requirement is to get a list of operators depending on the current (check) operator's level. I suspect the issue you are encountering is that some cities may not have a code. You then want to apply all found codes to another query that you are building up.
My guess is that the crux of the problem is that some cities might not have a code, hence the concern for null-able strings, while others might have multiple codes hacked into a single-code intended field. The solution there would typically be to remove any null values
Firstly, this line:
var checkOperator = _context.Operators.Include(a => a.OperatorLevel).Include(a => a.City).Include("City.StateRegion.Country").FirstOrDefault(a => a.Id == request.OperatorId);
can be simplified to:
var checkOperator = _context.Operators
.Select(a => new
{
Level = a.OperatorLevel.Name,
CityId = a.City.Id,
CityCode = a.City.Code,
StateRegionId = a.City.StateRegion.Id,
CountryId = a.City.StateRegion.Country.Id
}).FirstOrDefault(a => a.Id == request.OperatorId);
This builds a faster query, rather than fetching an entire operator object graph, just select the fields from the object graph that we need.
Now to handle the operator level. Here I don't recommend trying to force every scenario into a single pattern. The goal is just to apply a filter to the built query, so have the scenarios do just that:
select (checkOperator.Level)
{
case "City":
query = query.Where(a => a.Code == checkOperator.CityCode);
break;
case "Regional":
var cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Id == checkOperator.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Country":
var cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Country.Id == checkOperator.CountryId)
.Select(a => a.Code)
.ToListAsync();
query = query.Where(a => cityCodes.Contains(a.Code));
break;
}
Now based on the comments it sounds like your data with cities and codes is breaking proper normalization where Code was intended as a 1-to-1 but later hacked to handle one city having multiple codes, so multiple values were concatenated with hyphens. (I.e. ABC-DEF) If this represents 2 Codes for the city then you will need to handle this..
private List<string> splitCityCodes(List<string> cityCodes)
{
if (cityCodes == null) throw NullReferenceException(nameof(cityCodes));
if (!cityCodes.Any()) throw new ArgumentException("At least one city code is expected.");
var multiCodes = cityCodes.Where(x => x.Contains("-")).ToList();
if (!multiCodes.Any())
return cityCodes;
var results = new List<string>(cityCodes);
results.RemoveRange(multiCodes);
foreach(var multiCode in multiCodes)
{
var codes = multiCode.Split("-");
results.AddRange(codes);
}
return results.Distinct();
}
That can probably be optimized, but the gist is to take the city codes, look for hyphenated values and split them up, then return a distinct list to remove any duplicates.
List<string> cityCodes = new List<string>();
select (checkOperator.Level)
{
case "City":
cityCodes = splitCityCodes(new []{checkOperator.CityCode}.ToList());
if(cityCodes.Count == 1)
query = query.Where(a => a.Code == cityCodes[0]);
else
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Regional":
cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Id == checkOperator.StateRegionId)
.Select(a => a.Code)
.ToListAsync();
cityCodes = splitCityCodes(cityCodes);
query = query.Where(a => cityCodes.Contains(a.Code));
break;
case "Country":
cityCodes = await _context.Cities
.Where(a => a.Code != null && a.StateRegion.Country.Id == checkOperator.CountryId)
.Select(a => a.Code)
.ToListAsync();
cityCodes = splitCityCodes(cityCodes);
query = query.Where(a => cityCodes.Contains(a.Code));
break;
}
... and I suspect that would about do it for handling the possibility of a city code containing multiple values.
If your search argument is in the form "ABC-DEF" and you want that to match "ABC" OR "DEF" then it can be done, but it is not clear from your data setup how that scenario comes about.
Lets assume these codes are airport codes, and that a city that has multiple airports has the City.Code as a hyphenated list of the Airport codes, then if the checkOperator is in Australia, and their OperatorLevel is "National" then this might build the following nullableStrings:
var Cities = new List<string> {
"PER",
"ADE",
"DRW",
"MEL-AVV",
"SYD",
"BNE",
"OOL",
"HBA"
};
If then your query is a listing of AirPorts and you want to search the airports by these codes, specifically to match both "MEL" and "AVV" then you can use syntax like this
var nullableStrings = Cities.Cast<String?>().ToList();
query = query.Where(ap => nullableStrings.Any(n => n.Contains(ap.Code)));
But if you intend this to be translated to SQL via LINQ to Entities (so be executed server-side) then we can make this query more efficient buy normalizing the search args so we can do an exact match lookup:
var nullableStrings = Cities.Where(x => !String.IsNullOrWhiteSpace (x))
.SelectMany(x => x.Split('-'))
.Cast<String?>()
.ToList();
query = query.Where(ap => nullableStrings.Contains(ap.Code));
As this routine is called as part of a larger set and your checkOperator goes out of scope, you should try to reduce the fields that you retrieve from the database to the specific set that this query needs through a projection
Using .Select() to project out specific fields can help improve the overall efficiency of the database, not just each individual query. If the additional fields are minimal or natural surrogate keys, and your projections are common to other query scenarios then they can make good candidates for specific index optimizations.
Instead of loading SELECT * from all these table in this include list:
var checkOperator = _context.Operators.Include(a => a.OperatorLevel)
.Include(a => a.City.StateRegion.Country)
.FirstOrDefault(a => a.Id == request.OperatorId);
So instead of all the fields from OperatorLevel, City, StateRegion, Country we can load just the fields that our logic needs:
var checkOperator = _context.Operators.Where(o => o.Id == request.OperatorId)
.Select(o => new {
OperatorLevelName = o.OperatorLevel.Name,
o.CityId,
o.City.StateRegionId,
o.City.StateRegion.CountryId
})
.FirstOrDefault();
So many of the EF has poor performance opinions out there stem from a lot of poorly defined examples that proliferate the web. Eagerly loading is the same as executing SELECT * FROM ... for simple tables it's only a bandwidth and memory waste, but for complex tables that have computed columns or custom expressions there can be significant server CPU costs.
It cannot be overstated the improvements that you can experience if you use projections to expose only the specific sub-set of the data that you need, especially if you will not be attempting to modify the results of the query.
Be a good corporate citizen, only take what you need!
So lets put this back into your logic:
if (request.OperatorId != null && request.OperatorId != Guid.Empty)
{
var checkOperator = _context.Operators.Where(o => o.Id == request.OperatorId)
.Select(o => new {
OperatorLevelName = o.OperatorLevel.Name,
o.CityId,
o.City.StateRegionId,
o.City.StateRegion.CountryId
})
.FirstOrDefault();
IQueryable<City> cityQuery = null;
if (checkOperator.OperatorLevelName == "City")
cityQuery = _context.Cities
.Where(a => checkOperator.CityId == a.Id);
else if (checkOperator.OperatorLevelName == "Regional")
cityQuery = _context. Cities
.Where(a => checkOperator.StateRegionId == a.StateRegionId);
else if (checkOperator.OperatorLevelName == "National")
cityQuery = _context. Cities
.Where(c => c.StateRegion.CountryId == checkOperator.CountryId);
// TODO: is there any default filter when operator level is something else?
if (cityQuery != null)
{
var nullableStrings = cityQuery.Select(a => a.Code)
.ToList()
.Where(x => !String.IsNullOrWhiteSpace(x))
.SelectMany(x => x.Split('-'))
.Cast<String?>()
.ToList();
query = query.Where(ap => nullableStrings.Contains(ap.Code));
}
}
If you don't want or need to normalize the strings, then you can defer this whole expression without realizing the city query at all:
// No nullable string, but we can still remove missing Codes
cityQuery = cityQuery.Where(c => c.Code != null);
query = query.Where(ap => cityQuery.Any(c => c.Code.Contains(ap.Code)));

Is there a way to simplify these linq statements using an .Include()?

Currently I am doing a keyword search on the Plates table (Name column) but also have a Search (searching on SearchTerm column) table which contains Plat Id's that I also want to search and return the corresponding platforms.
The code below works but I'd like to simplify the logic using an .Include statement if possible although I'm not quite sure how. Any help would be greatly appreciated.
if (!string.IsNullOrEmpty(request.Keyword))
{
var searchTermPlateIds = await _db.Search
.Where(x=> x.SearchTerm.ToLower().Contains(request.Keyword.Trim().ToLower()))
.Select(x => x.PlatformId)
.ToListAsync(ct);
var plateFromPlateIds = await _db.Plate
.OrderBy(x => x.Name)
.Where(x => searchTermPlateIds.Contains(x.Id) && x.Status != PlateStatus.Disabled)
.ToListAsync(ct);
plates = await _db.Plates
.OrderBy(x => x.Name)
.Where(x => !string.IsNullOrEmpty(request.Keyword.Trim()) && x.Name.ToLower().Contains(request.Keyword.Trim().ToLower()) && x.Status != PlateStatus.Disabled)
.ToListAsync(ct);
plates = plates.Union(platesFromPlateIds).ToList();
}
Remember simple thing, Include ONLY for loading related data, not for filtering.
What we can do here - optimize query, to make only one request to database, instead of three.
var query = _db.Plates
.Where(x => x.Status != PlateStatus.Disabled);
if (!string.IsNullOrEmpty(request.Keyword))
{
// do not materialize Ids
var searchTermPlateIds = _db.Search
.Where(x => x.SearchTerm.ToLower().Contains(request.Keyword.Trim().ToLower()))
.Select(x => x.PlatformId);
// queryable will be combined into one query
query = query
.Where(x => searchTermPlateIds.Contains(x.Id);
}
// final materialization, here you can add Includes if needed.
var plates = await query
.OrderBy(x => x.Name)
.ToListAsync(ct);

Which design pattern should I use to handle multiple if conditions to get search result base on inputs

Can anyone suggest to me which design pattern should I use to handle multiple if conditions for a search function.
The search function takes the product's name, type, and location. In my handler, I handle the input by using if conditions as the example below.
if (!string.isNullOrEmpty(ProductName) && !string.isNullOrEmpty(ProductType))
{
// Query product and return base on name and type.
var product = database.product
.Where(x => x.productname == productname)
.Where(x => x.producttype == producttype)
.ToList()
}
else if (!string.isNullOrEmpty(ProductName)
&& !string.isNullOrEmpty(ProductType)
&& !string.isNullOrEmpty(ProductLocation))
{
// Query product and return base on name and location.
var product = database.product
.Where(x => x.productname == productname)
.Where(x => x.ProductLocation == ProductLocation)
.ToList()
}
So, I ended up having multiples if conditions in my handler. Code starts to get bigger and bigger. In the future, when I may have new types of input. Especially, each if condition will have the same query function and only where the condition is added or removed base on inputs.
Is there a better way to handle inputs and remove duplicated query function?
It is not design pattern but common way when using LINQ
var query = database.product.AsQueryable();
if (!string.IsNullOrEmpty(productName))
query = database.product.Where(x => x.productname == productname);
if (!string.IsNullOrEmpty(productType))
query = database.product.Where(x => x.producttype == producttype);
var product = query.ToList();
Or via helper function:
public static class MyQueryableExtensions
{
public staic IQueryble<T> WhereIf<T>(this IQueryable<T> source, bool condiion, Expression<Func<T, bool> predicate)
{
if (condition)
source = source.Where(predicate);
return source;
}
}
var product = database.product
.WhereIf(!string.IsNullOrEmpty(productName), x => x.productname == productname)
.WhereIf(!string.IsNullOrEmpty(productType), x => x.producttype == producttype)
.ToList();

How I can filter IQueryable?

I have the following controller:
public class EventsController : Controller
{
private readonly ICustomerEventRepository _customerEventRepository;
public EventsController(ICustomerEventRepository customerEventRepository)
{
_customerEventRepository = customerEventRepository;
}
public IActionResult Index(int? customerid, int? pageNumber, string code = "")
{
IQueryable<CustomerEvent> customerEvents;
if (customerid.HasValue)
{
customerEvents =
_customerEventRepository.CustomerEvents.Where(x => x.Customer.CustomerId == customerid)
.Include(x => x.Customer).OrderByDescending(x => x.CustomerEventId);
}
else if (!string.IsNullOrEmpty(code))
{
customerEvents =
_customerEventRepository.CustomerEvents.Where(x => x.EventType == code)
.Include(x => x.Customer).OrderByDescending(x => x.CustomerEventId);
}
else
{
customerEvents = _customerEventRepository.CustomerEvents
.Include(x => x.Customer).OrderByDescending(x => x.CustomerEventId);
}
var page = pageNumber ?? 1;
var onePageOfEvents = customerEvents.ToPagedList(page, 15);
return View(onePageOfEvents);
}
}
Note that
_customerEventRepository = customerEventRepository;
is my repository. It returns IQueryable form my. In the signature of the method Index I have a set of parameters. It's query parameters for filtering. Right now I have 2 (pageNumber is not filter parameter, it's for pagination), but I'm planning more. So this code is not very optimal. If I have more filter parameters I will be forced to make more and more if instructions. Because conditions don't work for LINQ. Maybe somebody had the same issue? I appreciate any help.
If I have understood you correctly, you want to build up the query based on multiple conditions.
IQueryable defers execution until the collection is materialised (for example calling ToList or iterating over the collection).
This allows you to build up the query piece by piece. See an example below.
IQueryable<CustomerEvent> customerEvents = _customerEventRepository
.CustomerEvents
.Include(x => x.Customer);
if (customerid.HasValue)
{
customerEvents = customerEvents.Where(x => x.Customer.CustomerId == customerid);
}
if (!string.IsNullOrEmpty(code))
{
customerEvents = customerEvents.Where(x => x.EventType == code);
}
// other conditions
...
// finally (this is when the query is actually executed)
var onePageOfEvents = customerEvents
.OrderByDescending(x => x.CustomerEventId)
.ToPagedList(page, 15);
return View(onePageOfEvents);

What is the best way to dynamically add to a where clause in a nhibernate query in C#?

I have a some C# code that is querying a database using nhibernate that looks like this:
public void Query()
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
I now have a number of user driver filters so, based on the parameters passed in, i want to add to the where clause. So something like this:
public void Query(string name)
{
if (!String.IsNullOrEmpty(name)
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted && p.Name == name)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
}
else
{
IEnumerable<Project> list = session.Query<Project>()
.Where(p => !p.IsDeleted)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList()
}
the user can select one or many filters. As you can imagine, this code above would get ridiculously complicated given the large number of combinations. Is there an elegant way to append a where clause here with additional blocks of logic. Some might be simple such as
p.Name == name
but others might be more complicated like:
p.ProjectApplications.Select(r => r.Application).Any(s => applicationIds.Contains(s.Id)))
and as I said, there may be zero or many different filters . .
UPDATE:
i have seen in other cases, people suggesting building up the where clause like
query = query.where (r=>r.name = "XYZ");
query = query.where (r=>r.Age > 10);
query = query.where (r=>r.Gender = "Male");
but that does NOT seem to work with nhibernate so what started was a generic lambda question is now a specific question to nhibernate
You can use the PredicateBuilder<T> to create the expression and apply it on your query, for sample:
public void Query(string name)
{
Expression<Func<Project, bool>> filter = PredicateBuilder.True<Project>();
filter = filter.And(p => !p.IsDeleted);
if (!string.IsNullOrEmpty(name)
filter = filter.And(p => p.Name == name);
IEnumerable<Project> list = session.Query<Project>()
.Where(filter)
.FetchMany(r => r.UnfilteredProjectApplications)
.ThenFetch(r => r.Application)
.ToList();
}
With PredicateBuilder you can create the expression you need, adding conditions using And(), Or(), Not() methods.
If you are looking something like this:
public IList<Bestellung> GetAll(Expression<Func<Order, bool>> restriction)
{
ISession session = SessionService.GetSession();
IList<Order> bestellungen = session.Query<Order>()
.Where(restriction).ToList();
return bestellungen;
}
Read this.

Categories