How does LinqKit PredicateBuilder join Expressions together? - c#

I'm using LinqKit 1.2.3 and I was just trying out how to combine multiple expressions.
Basically what I wanted to achieve is "give me results which match A && B && (C || D)".
I made the bellow code as an example:
string[] values = new string[]
{
"1","2","3","11","56",
"2","543","345","13421","562467",
"14324","23452","36789","10001","556876",
"1234","2432423","36456456","187681","50006",
};
ExpressionStarter<String> masterPredicate = PredicateBuilder.New<String>(true);
ExpressionStarter<String> andPredicate = PredicateBuilder.New<String>(true);
ExpressionStarter<String> orPredicate = PredicateBuilder.New<String>(true);
//andPredicate.And(x => x.StartsWith('1')).And(x => x.EndsWith('1')); // doesn't work ???
andPredicate.And(x => x.StartsWith('1'));
andPredicate.And(x => x.EndsWith('1'));
//orPredicate.Or(x => x.Contains('6')).Or(x => x.Contains('0')); // doesn't work ???
orPredicate.Or(x => x.Contains('6'));
orPredicate.Or(x => x.Contains('0'));
//master.And(andPredicate).And(orPredicate); // doesn't work ???
masterPredicate.And(andPredicate);
masterPredicate.And(orPredicate);
string[] filtered = values.Where(masterPredicate).ToArray();
foreach (var item in filtered)
{
Console.WriteLine(item);
}
In the end I got it working, but now I have a question, for example why does
andPredicate.And(x => x.StartsWith('1')).And(x => x.EndsWith('1'));
produce different results then
andPredicate.And(x => x.StartsWith('1'));
andPredicate.And(x => x.EndsWith('1'));
As a matter of fact none of the commented out lines worked the way I thought they would. Can someone shed some light on why this is?

This is expected. PredicateBuilder.And returns a new expression, it doesn't modify the existing one. andPredicate doesn't change. andPredicate.And(x => x.StartsWith('1')) returns a new expression that's never used.
var predicate=andPredicate.And(x => x.StartsWith('1')).And(x => x.EndsWith('1'));
is equivalent to :
var pred1=andPredicate.And(x => x.StartsWith('1'));
var predicate=pred1.And(x => x.EndsWith('1'));

Related

LINQ Lambda Improvement

I have a line like so:
var lstOfIds = db.TBL_AssocIncidentSpecialCat
.Where(x => x.IncidentId == incidentVm.ID)
.Select(t => t.SpecialCategoriesId)
.ToList();
This line gathers me a list of of the SpecialCategoriesIds. Then I have to do this:
incidentVm.LstSpecialCategories = db.TBL_SpecialCategories
.Where(x => lstOfIds.Contains(x.Id))
.Select(t => t.SpecialCategory)
.ToList();
Is there a way to combine these two lines into one? Even though it's only two lines of code.. I feel as though having to grab the Ids first then having to grab the associated property based on the Id is just an extra step and could be shortened to just one line. But I may be wrong.
Any help is appreciated.
UPDATE
incidentVm.LstSpecialCategories = db.TBL_AssocIncidentSpecialCat
.Where(x => x.IncidentId == incidentVm.ID)
.Join(
db.TBL_SpecialCategories,
x => new{Id = x.SpecialCategoriesId},
t => new{Id = t.Id},
(x,t) => {return t.SpecialCategory}
);
I am getting red squiggly under last part in Join:
A lambda expression with a statement body cannot be converted to an expression tree
You can combine the two lines using Join. Something like,
var result = db.TBL_AssocIncidentSpecialCat
.Join(
db.TBL_SpecialCategories,
ais => new { Id = ais.IncidentId },
sc => new { Id = sc.Id },
(ais, sc) => { return sc; }
)
.ToList();
C# Fiddle for this.
Update with Where Clause: You should use your Where condition after the Join.
var result = db.TBL_AssocIncidentSpecialCat
.Join(
db.TBL_SpecialCategories,
ais => new { Id = ais.IncidentId },
sc => new { Id = sc.Id },
(ais, sc) => new { ais = ais, sc = sc }
)
.Where(x => x.ais.IncidentId == 1)
.Select(y => y.sc)
.ToList();
You can try a LINQ query-style join:
incidentVm.LstSpecialCategories = (from aispc in db.TBL_AssocIncidentSpecialCat
join spc in db.TBL_SpecialCategories
on aispc.SpecialCategoriesId equals lspc.Id
where aispc.IncidentId == incidentVm.ID
select lspc.SpecialCategory).ToList();
I was able to figure this out with the help of some answers and me testing it on my own. Here is my solution:
incidentVm.LstSpecialCategories = db.TBL_AssocIncidentSpecialCat
.Where(t => t.IncidentId == incidentVm.ID)
.Join(db.TBL_SpecialCategories,
ik => ik.SpecialCategoriesId,
ok => ok.Id,
(ik, ok) => ok.SpecialCategory
)
.ToList();
Thank you for all of your help.

Simplify multiple nhibernate queryover

I'm doing a query over two different tables.
In the first query, i get some Ids that I then have to check in another table.
Then I do the first query again with the result of the second query.
This can't be the best way to do this.
But I haven't found a good way to solve it. So some help would be appreciated.
IntOrderInvoiceCostOut y = null;
var list = session.QueryOver<IntOrderInvoiceCostOut>(() => y)
.Where(x => x.IntegrationHandleDate == null)
.Select(Projections.Distinct(Projections.Property(() => y.Externalid)))
.List<string>();
var nonPreliminaryOrders = session.QueryOver<RefImplOrderEntity>()
.WhereRestrictionOn(x => x.ExternalId).IsIn(list.ToList())
.Where(x => x.StatusTypeId != 95)
.Select(x => x.ExternalId)
.List<string>();
var finalList = session.QueryOver<IntOrderInvoiceCostOut>()
.WhereRestrictionOn(x => x.Externalid).IsIn(nonPreliminaryOrders.ToList())
.Where(x => x.IntegrationHandleDate == null)
.OrderBy(x => x.IntegrationCreateDate)
.Asc
.List();
The code works...but i't really ugly.
you could use detacheCriteria for this. I have omitted couple of conditions and you might have to twick a bit as per your requirement.
for example
IntOrderInvoiceCostOut y = null;
var list = QueryOver.Of<IntOrderInvoiceCostOut>(() => y)
.Where(x => x.IntegrationHandleDate == null)
.Select(Projections.Distinct(Projections.Property(() => y.Externalid)))
.DetachedCriteria;
var nonPreliminaryOrders = QueryOver.Of<RefImplOrderEntity>()
.Where(Subqueries.PropertyIn(nameof(RefImplOrderEntity.ExternalId), list));
.Select(x => x.ExternalId)
.DetachedCriteria
var finalList = session.QueryOver<IntOrderInvoiceCostOut>()
.Where(Subqueries.PropertyIn(nameof(IntOrderInvoiceCostOut.ExternalId), nonPreliminaryOrders));
.List();

LINQ equal instead of Contains

I need to use equal instead of Contains.
I have an array of codes called selectedDeviceTypeIDs i assume it has two codes {1,2}
I need get result from the query if Devices ids are exactly {1,2} so i have replace selectedDeviceTypeIDs.Contains with selectedDeviceTypeIDs.equal or something like that ...
m => m.Devices.Any(w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID)
if (DeviceTypeIDs != null)
{
Guid[] selectedDeviceTypeIDs = DeviceTypeIDs.Split(',').Select(Guid.Parse).ToArray();
query = query.Where(j => j.HospitalDepartments.Any(jj => jj.Units.Any(m => m.Devices.Any(w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID)))));
}
Use !.Except().Any() to make sure m.Devices doesn't contains any DeviceTypeID not present in selectedDeviceTypeIDs
query = query.Where(j => j.HospitalDepartments.Any(jj => jj.Units
.Where(m => !m.Devices.Select(w => w.DeviceTypeID).Except(selectedDeviceTypeIDs).Any())));
Option 1:
If you care about the Order of the items, use SequenceEqual extension method. This will return false, even if the collection has the items but in different order
m => m.Devices.Any(w => selectedDeviceTypeIDs.SequenceEqual(w.DeviceTypeID)
Option 2:
If you don't care about the order , use All extension method. This will return true, if the items in both collections are same irrespective of the order.
m => m.Devices.Any(w => selectedDeviceTypeIDs.All(w.DeviceTypeID.Contains)
You need to check if the selectedDeviceTypeIDs contains every device, and that every device contains selectedDeviceTypeIDs. You could use this:
query = query
.Where(j =>
j.HospitalDepartments.Any(jj =>
jj.Units.Any(m =>
m.Devices.All(
w => selectedDeviceTypeIDs.Contains(w.DeviceTypeID))
&&
selectedDeviceTypeIDs.All(
g => m.Devices.Select(d => d.DeviceTypeID).Contains(g))
)
)
);

How to write a query in method syntax

If there is an ObservableCollection of class Printer called Printers...
and each Printer contains properties IsSelected (bool) and Index (int)...
how can I transform the following LINQ query from query syntax to method syntax?
string[] printerListing =
(from p in Printers
where p.IsSelected
select p.Index.ToString()).ToArray();
I came up with the following, but it only returns the first Printer from the query (split across multiple lines for readability):
var printers2 =
Printers.Where(p => p.IsSelected)
.FirstOrDefault()
.Index.ToString().ToArray();
Use .Select(), which functions like the select keyword in query syntax.
var printers2 = Printers
.Where(p => p.IsSelected)
.Select(x => x.Index.ToString())
.ToArray();
string[] printerListing =
(from p in Printers
where p.IsSelected
select p.Index.ToString()).ToArray();
You can do this simply step by step from the end of your query to the beginning:
ToArray(); stays:
....ToArray();
select:
....Select(p => p.Index.ToString()).ToArray();
where:
....Where(p => p.IsSelected).Select(p => p.Index.ToString()).ToArray();
from (the source):
Printers.Where(p => p.IsSelected).Select(p => p.Index.ToString()).ToArray();
So finally:
string[] printerListing =
Printers
.Where(p => p.IsSelected)
.Select(p => p.Index.ToString())
.ToArray();
Actually, it's also working the other way round, but sometimes the reverse order is easier to follow.
You used FirstOrDefault() so it will return one first element where isSelected = true.
Use
var printers2 = Printers.Where(p => p.IsSelected)
.Select(x => x.Index.ToString).ToArray();

Multiple Any() in one Where() clause LINQ

Is it possible to use several Any() in one where() clause ?
For example, If I need to get favourite beers, this query will do the job:
var favouriteDrinks = drinks
.Where(f => favouriteBeers
.Any(d => d.drinkID == f.drinkID));
But what if I need to get favourite Beers and favourite Wines ? I am looking for something like this:
var favouriteDrinks = drinks
.Where(f => favouriteBeers.Any(d => d.drinkID == f.drinkID) ||
f => favouriteWines.Any(d => drinkID == f.drinkID));
var favouriteDrinks = drinks
.Where(f => favouriteBeers.Any(d => d.drinkID == f.drinkID) ||
favouriteWines.Any(d => d.drinkID == f.drinkID));
why not do it like this:
var favouriteDrinks = drinks.Where(f =>
favouriteBeers.Any(d => d.drinkID == f.drinkID)) ||
favouriteWines.Any(d => d.drinkID == f.drinkID)));
also you can use Contains:
var favouriteDrinks = drinks.Where(f =>
favouriteBeers.Contains(f.drinkID) ||
favouriteWines.Contains(f.drinkID));
You can use .Union() and .Join()
var favouriteDrinks = favouriteBeers
.Union(favouriteWines)
.Join(drinks,
x => x.drinkID,
y => y.drinkID,
(x,y) => y
);
This will work as long as favouriteBeers, and favouriteWines are of the same type.
This fixes Tim.Tang's second example I believe. There are several approaches in here, but if it comes down to "contains" vs "any", I prefer contains as the intention is much more clear to me.
var favouriteDrinks = drinks.Where(d =>
favouriteBeers.Select(b => b.drinkId).Contains(d.drinkID) ||
favouriteWines.Select(w => w.drinkId).Contains(d.drinkID));
Similarly, with the correct IEquatable interfaces implemented on your "drink" classes, you can slightly simplify too
var favouriteDrinks = drinks.Where(d =>
favouriteBeers.Contains(d) || favouriteWines.Contains(d));

Categories

Resources