Need help merging two LINQ statements - c#

I am editing my code that used to be just
return phases
.OfType<ServicePhase>()
.Where(p => p.Service.Code == par.Service.Code)
.Cast<ParPhase>()
however now i want it to include both
return phases
.OfType<ServicePhase>()
.Where(p => p.Service.Code == par.Service.Code)
.Cast<ParPhase>()
.OfType<ParTypePhase>()
.Where(p => p.ParType.Code == par.Type.Code)
.Cast<ParPhase>();
How can i merge both of these together

Use Concat or Union method.
Sample:
var result =
phases
.OfType<ServicePhase>()
.Where(p => p.Service.Code == par.Service.Code)
.Cast<ParPhase>()
.Union(
phases.OfType<ParTypePhase>()
.Where(p => p.ParType.Code == par.Type.Code)
.Cast<ParPhase>()
);

return phases
.Where(p => ((p is ServicePhase) && (p as ServicePhase).Service.Code == par.Service.Code) ||
((p is ParTypePhase) && (p as ParTypePhase).ParType.Code == par.Type.Code))
.Cast<ParPhase>()
This works because if p is not a ServicePhase, this line (p as ServicePhase).Service.Code which would be object reference not set to an instance of an object is never evaluated.
false && NeverGoingToGetCalled()
because false AND anything is always false. It's called short-circuit evaluation if you care to read more about it.

Not sure which of these you mean. The first is if you want to further restrict the list, the second if you want to expand it.
from p in phrases
where p.Service.Code == par.Service.Code && p.ParType.Code == par.Type.Code
select new ParPhase(p)
or
from p in phrases
where p.Service.Code == par.Service.Code || p.ParType.Code == par.Type.Code
select new ParPhase(p)

It can be easier if think about Specification Pattern: http://en.wikipedia.org/wiki/Specification_pattern

Here is the combined query:
return phases.OfType<ServicePhase>()
.Where(p =>
{
bool tmpResult = p.Service.Code == par.Service.Code;
if(tmpResult && p is ParTypePhase)
{
tmpResult = (p as ParTypePhase).ParType.Code == par.Type.Code;
}
return tmpResult;
}).Cast<ParPhase>()

Related

Linq optional parameters query

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.

Removing items from a generic list not working

I am trying to remove an item from a list. It finds the item with the above query, but it doesn't remove it from the list. I don't know why is it so?
var qry = db.AssemblyListItems
.AsNoTracking()
.Where(x => x.ProductionPlanID == (long)_currentPlan.ProductionPlan)
.ToList();
var hasbeenAssembled = db.Assembleds
.AsNoTracking()
.Where(x => x.ProductionPlanId == (long)_currentPlan.ProductionPlan)
.ToList();
foreach (var item in hasbeenAssembled)
{
qry = qry.RemoveAll(X => X.DocumentNo == item.DocumentId &&
X.ItemCode == item.KitHeaderId &&
X.ProductionPlanID == item.ProductionPlanId );
}
olvData.SetObjects(qry);
Above is a listView where i want the items to appear. The main query "qry" is on the top.
You can handle this all in one query by excluding the assembled items from the list in a subquery:
var productionPlan = (long)_currentPlan.ProductionPlan;
var qry = db.AssemblyListItems
.AsNoTracking()
.Where(item => item.ProductionPlanID == productionPlan
&& !db.Assembleds
.Any(x => x.ProductionPlanId == item.ProductionPlanID
&& x.DocumentNo == item.DocumentId
&& x.ItemCode == item.KitHeaderId))
The advantage is (as also said by others) that you don't pull AssemblyListItems into memory that you're going to discard again later. Entity Framework will be able to translate this into one SQL statement, so everything is handled efficiently by he database.
Don't include the unwanted items in the results of the query. Don't prematurely bring over query results from the database when it might be able to process the query for you.
var hasBeenAssembled = db.Assembleds
.AsNoTracking()
.Where(x => x.ProductionPlanId == (long)_currentPlan.ProductionPlan);
var qry = db.AssemblyListItems
.AsNoTracking()
.Where(x => x.ProductionPlanID == (long)_currentPlan.ProductionPlan)
.Where(ali => !hasBeenAssembled.Any(hba => hba.DocumentId == ali.DocumentNo && hba.KitHeaderId == ali.ItemCode && hba.ProductionPlanId == ali.ProductionPlanID))
.ToList();
olvData.SetObjects(qry);
Easier way to do this. Items in the first list does not exist in the second list.
from item in hasbeenAssembled
where !(qry.Any(X => X.DocumentNo == item.DocumentId &&
X.ItemCode == item.KitHeaderId &&
X.ProductionPlanID == item.ProductionPlanId))
select item;

Select particular record if match is found otherwise First record in IGrouping<T>

Here is my LINQ query:
pmcPrices = (from pd in interfaceCtx.PmcPriceDatas
where
pd.ClientIdValue != null
&& pd.ClientIdCode == "FII"
&& (pd.MetricVal != null || pd.PMCPrice1 != null || pd.PMCPrice2 != null)
&& pd.EffectiveDate.Value == eodDate.DateTime
group pd by pd.ClientIdValue into g
select g).ToDictionary(g => Convert.ToInt32(g.Key),
g => g.SingleOrDefault(p => p.FeedCode == "EQRMS-CB"));
What I want to achieve is if any of the record in group 'g' has FeedCode == 'EQRMS-CB' select that record otherwise First record in group 'g'.
You could use a ternary operator
..
.ToDictionary(g => Convert.ToInt32(g.Key),
g => g.Any(p => p.FeedCode == "EQRMS-CB")
? g.First(p => p.FeedCode =="EQRMS-CB")
: g.First())
If it doesn't actually matter which you get if there is no match so long as you get one, or if you are using a Linq provider with a stable OrderBy (linq to objects and providers that make use of it is, most are not) then:
g => g.OrderBy(p => p.FeedCode != "EQRMS-CB").FirstOrDefault()
Otherwise:
g => g.FirstOrDefault(p => p.FeedCode == "EQRMS-CB") ?? g.FirstOrDefault()

List where Clause

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
{
}

Dynamically filtering linq lambda expressions

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

Categories