I am making a search form that queries my database to show results based on what has been filled out on the form. The only required field is the date which I have working. all the other fields are optional, if an optional field is not filled in it should not be a part of the query. This is the code I have written:
var queryable = context.TransactionJournal.Where(s => s.TransactionDateTime <= transactionDate)
.Where(s => Region == null || Region == s.AcquirerID)
.Where(s => MCC == null || MCC == s.MerchantCategoryCode)
.Where(s => MerchantID == null || MerchantID.Contains(s.MerchantID))
.Where(s => TxnCurrency == null || TxnCurrency.Contains(s.Currency))
.Where(s => TerminalID == null || TerminalID.Contains(s.TerminalID))
.Where(s => TxnAmount.ToString() == null || TxnAmount==(s.TransactionAmount))
.Where(s => BIN == null || BIN.Contains(s.Bin))
.Where(s => MsgType == null || MsgType.Contains(s.MessageType))
.Where(s => MaskedPan == null || MaskedPan.Contains(s.PANM))
.Where(s => ProcessingCode == null || ProcessingCode.Contains(s.ProcessingCode))
.Where(s => ClearPan == null || ClearPan.Contains(s.PAN))
.Where(s => ResponseCode == null || ResponseCode.Contains(s.ResponseCode))
.Where(s => AuthorizationCode == null || AuthorizationCode.Contains(s.AuthorizationCode))
.Where(s => EntryMode == null || EntryMode.Contains(s.PosEntryMode))
.AsQueryable();
Unfortunately it does not work correctly. Can someone tell me what I am missing or if there is a better way to write this?
Took advice from the comments and went through each line and found which line was evaluating false. This fixed my problem.
I think the best you can do there is check first if you should apply the condition and then filter the list.
An example using the code you provided.
var queryable = context.TransactionJournal.Where(s => s.TransactionDateTime <= transactionDate);
if (!string.IsNullOrEmpty(your_objet.Region)
{
var queryable = queryable.Where(x=>x.Region == your_objet.Region).AsQueryable();
}
if (!string.IsNullOrEmpty(your_objet.MCC)
{
var queryable = queryable.Where(x=>x.MCC == your_objet.MCC).AsQueryable();
}
The first line is the entire list, then you check all parameters that you have in the form and evaluate it, if has value the apply the filter to list.
And the end you'll get your list filtered.
Related
I have a SQL Server table called ControlActivityChangeLog. The table has columns CurrentValue and NewValue, of which, both are nullable nvarchars. How do I query all ControlActivityChangeLogs but exclude results where CurrentValue and NewValue are null or empty. If CurrentValue is null or empty and NewValue is not then I need that record.
I tried adding this to the below query but it ends up returning no results:
!x.ControlActivityChangeLogs.Any(y => string.IsNullOrEmpty(y.CurrentValue) && string.IsNullOrEmpty(y.NewValue))
Here is my qry:
var qry = _context.ControlActivities
.Include(x => x.ControlActivityChangeLogs)
.Include(x => x.Company)
.Where(x =>
!x.IsDeleted &&
!x.IsArchived &&
!x.ControlActivityChangeLogs.Any(y => string.IsNullOrEmpty(y.CurrentValue) && string.IsNullOrEmpty(y.NewValue)))
.AsQueryable();
**OTHER COLUMN FILTERS HERE**
var results = await qry.Select(x => new ModifiedCAModel
{
**List Of Columns**,
Changes = x.ControlActivityChangeLogs
.Where(y => y => y.ControlActivityIssueId == null && y.ControlActivityTestId == null)
.OrderByDescending(y => y.ChangedDate)
.ToList()
}).ToListAsync();
The Where or Any condition is for including, so you need the inverse of
exclude results where CurrentValue and NewValue are null or empty
which is
include results where CurrentValue or NewValue is not null or empty
i.e. instead of
!x.ControlActivityChangeLogs.Any(y => string.IsNullOrEmpty(y.CurrentValue) && string.IsNullOrEmpty(y.NewValue)))
you need
x.ControlActivityChangeLogs.Any(y => !string.IsNullOrEmpty(y.CurrentValue) || !string.IsNullOrEmpty(y.NewValue)))
the below should be able to translate to SQL correctly
var qry = _(context.ControlActivities
.Include(x => x.ControlActivityChangeLogs)
.Include(x => x.Company)
.Where(x => !x.IsDeleted
&& !x.IsArchived
&& x.ControlActivityChangeLogs.CurrentValue != null
&& x.ControlActivityChangeLogs.CurrentValue != ""
&& x.ControlActivityChangeLogs.NewValue != null
&& x.ControlActivityChangeLogs.NewValue != ""
).AsQueryable();
you should check what sql is being executed tho!!!
this can be obtained but adding logging to ef.
I am getting a list of my database, selecting certain things, then i want to query another database where the original list contains this databases job reference
var jobRefs = context.jobs.Where(j => j.LSM_Status == null &&
j.despatched_time == null
)
.Select(x => new { x.job_ref, x.volumes_env, x.postout_deadline , x.UniqNo })
.ToList();
var UpdatedRefs = context.customerslas.Where(c => jobRefs.Any(z=>z.job_ref == c.job_ref) &&
(c.invoiced == 1 ||
c.invoiced == 2) &&
c.active == 1)
.Select(c => c.job_ref)
.ToList();
And get this error
Unable to create a constant value of type 'Anonymous type'. Only primitive types or enumeration types are supported in this context.'
The ToList() in the first first query is fetching data to a collection in memory while the second query, where you compare the data, is in database. To solve this you need to make them in the same area, either db or memory.
Easiest way, and recommended, would be to just remove ToList() from the first query
var jobRefs = context.jobs.Where(j => j.LSM_Status == null &&
j.despatched_time == null
)
.Select(x => new { x.job_ref, x.volumes_env, x.postout_deadline , x.UniqNo });
var UpdatedRefs = context.customerslas.Where(c => jobRefs.Any(z=>z.job_ref == c.job_ref) &&
(c.invoiced == 1 ||
c.invoiced == 2) &&
c.active == 1)
.Select(c => c.job_ref)
.ToList();
The code below work succesfully to find if a value exists in a list. How do I add a where clause such that only for list items where Type = "File"
if (MyGlobals.ListOfItemsToControl.Any(x => x.sItemName == info.FullName)) // Dont allow duplicates
{
}
Pseudo Code for what i want
if (MyGlobals.ListOfItemsToControl.Any(x => x.sItemName == info.FullName).Where(y => y.Type == "File")) // Dont allow duplicates
{
}
Your filter (Where) should be before Any
if (MyGlobals.ListOfItemsToControl
.Where(y => y.Type == "File")
.Any(x => x.sItemName == info.FullName))
You can also combine both conditions in Any like:
if (MyGlobals.ListOfItemsToControl
.Any(x => x.Type == "File"
&& x => x.sItemName == info.FullName))
instated of where you can simply use
if (MyGlobals.ListOfItemsToControl.Any(x => x.sItemName == info.FullName && x.Type == "File")) // Dont allow duplicates
{
}
I'm currently trying doing the following.
var groups = MileId == null ? test.Groups.Where(x => x.ProjectId == ProjectId)
: test.Groups.Where(x => x.Milestone == MileId &&
x.ProjectId == ProjectId);
But I also have additional terms that I need to filter groups by:
foreach (var ChartItem in ChartItems)
{
foreach (var StatusItem in ChartItem.ChartStatusItems)
{
foreach (var PriorityItem in StatusItem.ChartPriorityItems)
{
filteredgroups.AddRange(
groups.Where(x => x.Status == StatusItem.StatusID
&& x.Priority == PriorityItem.PriorityID));
}
}
}
This is fine and it works but the nested foreach loops is pretty slow when adding the ranges. If I groups.toList() before the loop, then that statement is slow and the nested loops are fast.
My question is:
Would it be possible to filter groups from the start based on those StatusIds and PriorityIds dynamically? How?
Stackoverflow recommends some articles on Expression Tree's based on my subject line... is that what I need to look into?
Thank you
EDIT:
So I'm doing this now:
foreach (var ChartItem in ChartItems)
{
foreach (var StatusItem in ChartItem.ChartStatusItems)
{
foreach (var PriorityItem in StatusItem.ChartPriorityItems)
{
var groups = MileId == null ? test.Groups.Where(x => x.ProjectId == InspectorProjectId &&
x.Status == StatusItem.StatusID &&
x.Priority == PriorityItem.PriorityID)
: test.Groups.Where(x => x.Milestone == InspectorMileId &&
x.ProjectId == InspectorProjectId &&
x.Status == StatusItem.StatusID &&
x.Priority == PriorityItem.PriorityID);
filteredgroups.AddRange(groups);
}
}
}
It's a big improvement but it's still going to the slow 'test' server for each priority. If I could get it all filtered in 1 go, it would be ideal.
EDIT 2: Oh I don't have access to the db directly :( we access it through an API.
All this should be happening in the database. Just create a view that joins all those tables. It's hard to be faster than a database when intersecting and joining sets of data.
Can you do it with Contains?
var filteredgroups =
test.Groups.Where(x =>
(MileId == null || x.Milestone == MileId) // (replaces ?: in original)
&& x.ProjectId == ProjectId
&& ChartItem.ChartStatusItems.Contains(x.Status)
&& StatusItem.ChartPriorityItems.Contains(x.Priority));
(I'm not sure how Linq-to-Sql and Linq-to-Objects are going to interact wrt performance, but at least it's concise...)
Maybe you can call .Any() within your .Where() and skip the loops entirely.
test.Groups.Where(x => (MileId == null ||
x.Milestone == MileId) &&
x.ProjectId == ProjectId &&
ChartItems.Any(c => c.ChartStatusItems.Any(s => s.StatusId == x.StatusId &&
s.ChartPriorityItems.Any(p => p.PriorityId == x.PriorityId))));
The foreach loops are most likely executing a deferred call, which is most likely hitting your database on each foreach loop. But you don't have to, using SelectMany you can simply build up your query:
var statuses = ChartItems
.SelectMany(x => x.ChartStatusItems)
.Select(i => i.StatusId);
var priorities = ChartItems
.SelectMany(x => x.ChartPriorityItems)
.Select(i => i.PriorityId);
var filtered = groups.Where(x => statuses.Contains(x.Status) &&
priorities.Contains(x.Priority))
I've been experimenting with LINQ to SQL recently and have a quick question.
The basic premise is I have a search request which contains a Make and Model which I use to search a DB containing cars.
The expression for my Where Clause is shown below:
.Where(c => c.make == search.make && c.model == search.model)
This is fine when my search contains both a make and a model. The problem arises when it only contains a make(or vice versa) and not both search fields. I want it to return all cars of that make, it is however returning none.
Im assuming this is because it is looking for the make plus a model that is null or empty?
Is there an elegant way to get around this other than manually building up the query with a series of "if not null append to query" type steps?
Have you tried:
.Where(c => (search.make == null || c.make == search.make) && (search.model == null || c.model == search.model))
Update: There's actually a nice treatment of the general problem, and clean solutions, here:
LINQ to SQL Where Clause Optional Criteria. The consensus seems to be that an extension method is cleanest.
.Where(c => (search.make == null || c.make == search.make) &&
(search.model == null || c.model == search.model))
IMO, split it:
IQueryable<Car> query = ...
if(!string.IsNullOrEmpty(search.make))
query = query.Where(c => c.make == search.make);
if(!string.IsNullOrEmpty(search.model))
query = query.Where(c => c.model== search.model);
This produces the most appropriate TSQL, in that it won't include redundant WHERE clauses or additional parameters, allowing the RDBMS to optimise (separately) the "make", "model" and "make and model" queries.
you could write something like this:
.Where(c => c.make == search.make ?? c.make && c.model == search.model ?? c.model)
This should work.
.Where(c =>
(search.make == null || c.make == search.make) &&
(search.model == null || c.model == search.model))
.Where(c => (string.IsNullOrEmpty(c.make) || c.make == search.make) &&
(string.IsNullOrEmpty(c.model) || c.model == search.model))
.Where(c => (string.IsNullOrEmpty(search.make) || c.make == search.make) &&
(string.IsNullOrEmpty(search.model) || c.model == search.model))
That's assuming the properties are strings.
Where(c => (search.make == null || c.make == search.make) &&
(search.model == null || c.model == search.model))