How I can filter IQueryable? - c#

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

Related

Use Intersect inside Where clause

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

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();

(Code-Completion) Combine two task lists (tables) in order to show the total results

Today I'm trying to achieve something that I'm having difficulties with the code-completion.
I'm trying to combine two tasks (which are from different types).
Before reaching here, I've tried several scenarios.... Using WhenAll to combine Tasks (which I cannot do this since both tasks are different).
The last part of the code (last 3 lines) are not correct and I'll ask if someone can take a look on this :) (Also if the method arguments are okay for this matter). All I want is to return the list of the total rows combined by the two tasks.
Thanks in advance! (Code is below)
public async Task<(ApplyPerfectMatches, ApplyNonPerfectMatches)>
GetMatchesAsync(string currentUser)
{
// Perfect Matches
var listPerfectMatches = _context.ApplyPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
// Use AsNoTracking to disable EF change tracking
// Use ToListAsync to avoid blocking a thread
.AsNoTracking().ToListAsync();
// Determine Total matches vs. Limit matches per user
int totalMatches = _context.ApplyPerfectMatches.Where(x => x.IdEmailInvestor == currentUser).Count();
int totalMatchesBy10 = totalMatches / 10;
// n(n-1)/2 + 2 (n = 1...n+1 => 2, 3, 5, 8, 12, 17, 23, 30...)
int limitMatches = totalMatches < 10 ? 3 : (totalMatchesBy10 * (totalMatchesBy10 - 1) / 2) + 2;
var submitDate = _context.ApplyPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
.OrderByDescending(x => x.DtSubmitInvestor)
.Select(x => x.DtSubmitInvestor)
.FirstOrDefault();
int days = (DateTime.Now - submitDate).Days;
if (days > 3)
{
listPerfectMatches = _context.ApplyPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
.Take(limitMatches)
// Use AsNoTracking to disable EF change tracking
// Use ToListAsync to avoid blocking a thread
.AsNoTracking().ToListAsync();
}
else
{
listPerfectMatches = _context.ApplyPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
.Take(3)
// Use AsNoTracking to disable EF change tracking
// Use ToListAsync to avoid blocking a thread
.AsNoTracking().ToListAsync();
}
// Total Matches (Perfect + Non-Perfect Matches)
var listNonPerfectMatches = _context.ApplyNonPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
// Use AsNoTracking to disable EF change tracking
// Use ToListAsync to avoid blocking a thread
.AsNoTracking().ToListAsync();
var finalResult = listPerfectMatches.Result.Concat(listNonPerfectMatches.Result).ToList();
//await Task.WhenAll(tasks);
//return tasks.SelectMany(t => t.Result).ToList();
}
Update (I don't mind the negative votes but if someone needs help, that same person might be confused with the problem...):
result = await _context.ApplyTotalMatches
// Only get entries for the current logged in user
.Where(x => x.IdEmailInvestor == currentUser && x.UnionSetOrder == 1)
.Take(limitMatches)
.Concat(_context.ApplyTotalMatches
.Where(x => x.IdEmailInvestor == currentUser && x.UnionSetOrder == 2))
// Use AsNoTracking to disable EF change tracking
// Use ToListAsync to avoid blocking a thread
.AsNoTracking().ToListAsync();
You have to await a Task object. This "unwraps" the result:
public async (List<ApplyPerfectMatches> PerfectMatches, List<ApplyNonPerfectMatches> NonApplyPerfectMatches) GetMatchesAsync(string currentUser)
{
List<ApplyPerfectMatches> listPerfectMatches = await _context.ApplyPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
.Take(3)
.AsNoTracking()
.ToListAsync();
List<ApplyNonPerfectMatches> listNonPerfectMatches = await _context.ApplyNonPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
.AsNoTracking()
.ToListAsync();
return (listPerfectMatches, listNonPerfectMatches);
}
async Task Run()
{
(List<ApplyPerfectMatches> PerfectMatches, List<ApplyNonPerfectMatches> NonApplyPerfectMatches) result = await GetMatchesAsync("Admin");
}
Directly accessing Task.Result can cause a deadlock. QueryableExtensions.ToListAsync is an asynchronous method. Asynchronous methods always return a Task<T> or Task and must be awaited using await in order to execute asynchronously.
First, you need to define a new class:
public class GetMatchesResult {
public string IdEmailInvestor { set; get; }
public bool IsPerfectMatch { set; get; }
... rest of properties/columns go here
}
Since your method is defined as
async Task<(ApplyPerfectMatches, ApplyNonPerfectMatches)>
you need to change it to:
async Task<List<GetMatchesResult>> GetMatches
Now, you need to update the queries to:
// Perfect Matches
var listPerfectMatches = await _context.ApplyPerfectMatches
.Where(x => x.IdEmailInvestor == currentUser)
.Select(x => new GetMatchesResult {
IdEmailInvestor = x.IdEmailInvestor,
IsPerfectMatch = true,
... rest of properties go here
})
// Use ToListAsync to avoid blocking a thread
.ToListAsync();
You need to do the same with the listNonPerfectMatches, but set the IsPerfectMatch to false.
In the end, you can do:
return listPerfectMatches.Concat(listNonPerfectMatches);

NHibernate QueryOver with SelectList

I have multiple queries against one table. As not all columns/properties are needed I specify the columns with the help of select list. Take the following method as example. This method is working
public IEnumerable<ResultDto> GetEntityAsDto(eStatusBinderProduktion fromState, eStatusBinderProduktion toState)
{
EntityClass entityAlias = null;
ResultDto resultAlias = null;
var query = Session.QueryOver<EntityClass>(() => entityAlias)
.Where(() => entityAlias.ProduktionStatus >= (byte)fromState)
.And(() => entityAlias.ProduktionStatus <= (byte)toState);
query.SelectList(list => list
.Select(() => entityAlias.PrimaryID).WithAlias(() => resultAlias.PrimaryID)
.Select(() => entityAlias.SecondaryID).WithAlias(() => resultAlias.SecondaryID)
);
return query.TransformUsing(Transformers.AliasToBean<ResultDto>())
.List<ResultDto>();
}
As I need to define the SelectList in multiple methods, I tried to to move the SelectList into a separate method.
The following code is not working, NHibernate throws the exception
NHibernate.QueryException: 'could not resolve property: entity.PrimaryID of: MyProjectNamespace.DAL.Interfaces.Entities.EntityClass'
"
public IEnumerable<ResultDto> GetEntityAsDto(eStatusBinderProduktion fromState, eStatusBinderProduktion toState)
{
EntityClass entityAlias = null;
ResultDto resultAlias = null;
var query = Session.QueryOver<EntityClass>(() => entityAlias)
.Where(() => entityAlias.ProduktionStatus >= (byte)fromState)
.And(() => entityAlias.ProduktionStatus <= (byte)toState);
MapPropertiesOfEntityToResult(entityAlias, resultAlias, query);
return query.TransformUsing(Transformers.AliasToBean<ResultDto>())
.List<ResultDto>();
}
private void MapPropertiesOfEntityToResult(EntityClass entity, ResultDto resultAlias, IQueryOver<EntityClass, EntityClass> query)
{
query.SelectList(list => list
.Select(() => entity.PrimaryID).WithAlias(() => resultAlias.PrimaryID)
.Select(() => entity.SecondaryID).WithAlias(() => resultAlias.SecondaryID)
);
}
Additional information:
- The mappings are correct
- table is filled with test data
We do not need alias for filling SelectList. We can profit from the Type parameters coming with IQueryOver<TRoot,TSubType>:
//private void MapPropertiesOfEntityToResult(EntityClass entity
// , ResultDto resultAlias, IQueryOver<EntityClass, EntityClass> query)
private void MapPropertiesOfEntityToResult( // no need for entity
ResultDto resultAlias, IQueryOver<EntityClass, EntityClass> query)
{
query.SelectList(list => list
//.Select(() => entity.PrimaryID).WithAlias(() => resultAlias.PrimaryID)
//.Select(() => entity.SecondaryID).WithAlias(() => resultAlias.SecondaryID)
.Select(entity => entity.PrimaryID).WithAlias(() => resultAlias.PrimaryID)
.Select(entity => entity.SecondaryID).WithAlias(() => resultAlias.SecondaryID)
);
}
The entity is now a parameter of the passed Function and its type is coming from IQueryOver definition

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