Count predicate, how does it work? - c#

With a linq query I retrieve a list from the database. The list contains 2 items of a specific class containing the property RoleType. the type of RoleType is an int.
the first object has a property value 0.
the second object has a property value 1.
I also got an enum:
enum RoleTypes
{
Type1 = 1,
Type2 = 0,
}
The weird order of indexing the enums is a given, I can't change that.
My business rules states that there has to be exactly one role with RoleType value Type2 and at least one role with RoleType value Type1. This is the code I use for that:
var ExactlyOneRoleType2 = roles.Count(role => role.RoleType == (int)RoleTypes.Type2) == 1;
var AtLeastOneRoleType1 = roles.Any(role => role.RoleType == (int)RoleTypes.Type1);
According to the list I mentioned earlier, both variables (ExactlyOneRoleType2 and AtLeastOneRoleType1) should be true. At least, I would expect that. Only if I debug through the code I found out that ExactlyOneRoleType2 is false. After some research I find out that
roles.Count(role => role.RoleType == (int)RoleTypes.Type2)
returns 2 instead of 1 which I find odd because there is only 1 role with RoleType Type2. Why does the count return 2? It also doesn't compute with the fact that the Any call actually returns true. How can list containing 2 items have both 2 items with RoleType Type2 and have at least one item with RoleType Type1?
Also when I change the count call to
roles.Count(role => role.RoleType == 0)
it still returns 2. Only when I change the Count call to this:
private static bool IsRoleTypeType2(Role role)
{
return role.RoleType == (int)RoleTypes.Type2;
}
roles.Count(IsRoleTypeType2)
the count returns 1 as it should be.
What is happening here? Why does the count return 2 when I use a anonymous predicate? Did I misunderstood something about how the predicate works in case of count? Or is this is bug in Linq?

Related

USe LINQ to remove from a list based on conditions

I have a list 'nList' returned from a LINQ query. The data is correct but I have to check if the data in the 2nd field ('nType') occurs more than once, and if it does, remove it from the list if the 3rd field ('nAmount') is null.
To illustrate, in the example below, I would want to remove the record with ID 2.
Can this be done using LINQ?
EDIT: There can only be 2 scenarios:
one occurrence of nType, with nAmount populated
two occurences of nType, with one nAmount populated, the other nAmount null
ID nType nAmount
1 A 12.00
2 A
3 B 13.00
4 C 14.00
var result = nList.Where(x => x.nAmount != null).DistinctBy(x => x.nType);
You would need to install MoreLinq from Nuget to get the DistinctBy().
This can be done with a GroupBy overload which lets you define the grouped object
var result = nList.GroupBy(
x => x.nType,
(key,g) => new {
ID = g.First(x => x.nAmount != null).ID,
nType = key,
nAmount = g.First(x => x.nAmount != null).nAmount }
);
Live example: http://rextester.com/PFX41986
This has a few caveats
If your resultset has a single nType which has a null nAmount it will not appear in the results
If you get more than 1 distinct nType with a non-null nAmount this will take the first one.

How do I coalesce .Max() for queries against empty collections in LINQ to EF?

I have an entity in my model that has an integer property:
public class Foo
{
public int SortOrder { get; set; }
// ...other props
}
Now, for some subset of all the Foos in the database, I want to select the highest SortOrder and add one - i.e. get the sort order for a new object added last in the collection - but if there are no Foos in that subset, I want to end up with 0 - i.e. the sort order of a new object added to an empty collection.
Reading this reference page, I got the impression that .Max() returns null if the collection over which it aggregates is empty, so I tried the following:
var sortOrder = context.Foos
.Where(/* predicate for subset */)
.Max(foo => foo.SortOrder) + 1
?? 0;
but that doesn't compile, since the return type of .Max is inferred to be int which can't be null coalesced.
However, if I just don't use the null coalescing, and assume that .Max() really returns default(T) (i.e. 0 in this case), then I'll end up with 1 instead of 0 for the empty case. Since this is a call to the database, I'd rather make do with one query.
It works just as I want it to if I make the property on Foo to be of type int?, but I don't want to do that since we don't allow NULL values in the database and I see no reason to allow them on the entity then.
I got the impression that .Max() returns null if the collection over
which it aggregates is empty
You get an InvalidOperationException which is documented here
"InvalidOperationException... source does not contain any elements."
You can use DefaultIfEmpty(0):
int maxSortOrder = context.Foos
.Where(/* predicate for subset */)
.Select(foo => foo.SortOrder + 1)
.DefaultIfEmpty(0)
.Max();

Distinct the collection in Linq C#

I have the collection
From the collection the column name ma_position_value should not contain the same value
For Example
If the collection have 5 records, The column ma_position_value should not contain the same value from all 5 records...but it can contain same value for 2 or 3 or 4 records from the collection Atleast one column value should change.
So the main intension is ALL 5 records should not contain same value.Any one should get different value.So if all 5 is same I tried to throw a message
So I have just write a bool to return it if it is change
bool lblMa = false;
lblMa = ibusCalcWiz.iclbMssPaServiceSummary
.Where(lbusMssPaServiceSummary => lbusMssPaServiceSummary.icdoSummary.ma_position_value.IsNotNullOrEmpty()).Distinct().Count() > 1;
But it is always return true.
Just select distinct ma_position_value property values:
bool allSame = ibusCalcWiz.iclbMssPaServiceSummary
.Select(i => i.ma_position_value)
.Distinct()
.Count() == 1;
HINT: Do not use long variable names in lambda expressions. There is a rule of thumb - the bigger scope of variable usage, the bigger should be name. If scope is very small (lambda expression) then name should be very small (single letter is enough).
You can get the first one, and check if they are all equal:
// Only works if the collection is non-empty!
string first_ma_position_value = ibusCalcWiz.iclbMssPaServiceSummary.First().ma_position_value;
bool allTheSame = ibusCalcWiz.iclbMssPaServiceSummary
.All(lbusMssPaServiceSummary.icdoSummary.ma_position_value == first_ma_position_value);
or you can do the distinct, as you originally wanted, but on the value of the column instead of on the objects
bool allTheSame = ibusCalcWiz.iclbMssPaServiceSummary
.Select(lbusMssPaServiceSummary => lbusMssPaServiceSummary.icdoSummary.ma_position_value)
.Distinct()
.Count() == 1;

Displaying specific elements of a collection's subcollection

I have a List collection that contains a List subcollection as a property within it, and I want to filter out items in that subcollection based on the value of certain properties.
To simplify, I'll call the main collection THING and the subcollection SUBTHING. They are different types. THINGS can have 1 to many SUBTHINGS. SUBTHING has 2 properties I want to filter by, PROP1 should equal 1 (it can equal 1,2,3) and PROP2 should not be NULL (it can contain a string).
So when I use a query like the one below it seems to give me what I want (though I'm not sure All() is doing what I expect):
search = from c in search
where c.SUBTHING.All(s=>s.PROP1==1)
select c;
Then I get suspicious when I add the other property:
search = from c in search
where c.SUBTHING.All(s=>s.PROP1==1 && s.PROP2 != NULL)
select c;
And I get THINGS that have PROP2 as Null.
When I switch to Any() I lose all filtering on SUBTHING and it shows SUBTHINGS where PROP1 = 1,2,3 and where PROP2 is NULL and not NULL.
What I'm trying to get is a collection that lists all THING IDs and then lists the Name of all SUBTHINGS, sort of like this:
THING.ID
SUBTHING.Name
SUBTHING.Name
THING.ID
SUBTHING.Name
SUBTHING.Name
Is this possible to also filter SUBTHINGS while filtering THINGS with LINQ since THING and SUBTHING are two different types?
Try something like this:
search =
from c in search
where c.SUBTHING.All(s=>s.PROP1==1 && s.PROP2 != NULL)
select new {
ThingId = c.ThingID,
Something = c.SomeThing.Select(x=>x.Name)
};
To apply filter on subitems try:
from product in products
where product.productid == 1
from image in product.productimages
where image.ismainimage
select image.imagename
From : 101 linq queries
One way is using Enumerable.Where and an anonymous type:
var result = from thing in search
from subthing in thing.subthings
where subthing.prop1 == 1 && subthing.prop2 != null
select new {ID = thing.ID, Name = subthing.Name};
foreach(var x in result)
{
Console.WriteLine("ID={0} Name{1}", x.ID, x.Name);
}
You need a projection as you are querying over the parent entity (THING) but in the result set you want to only have a subset of its SUBTHINGS.
You can do it e.g. in the following way:
class Thing
{
Thing(Thing original, IEnumerable<Subthing> subthings)
{
// Initialize based on original and set the collection
//
...
}
}
and then run the query like this:
var filtered = from c in search
select new Thing(c, c.Subthings.Where(x => x.PROP1 == 1 && x.PROP2 != null))
I'm not sure any of these answers really give you what you want (although they're close). From my understanding, you want a list of THINGs in which at least 1 SUBTHING has the values you're interested in (in this case, Prop1 == 1 and Prop2 != null). There are a few options here, just depends on whether you're working from a THING or a SUBTHING perspective.
Option 1: THING approach.
You're looking at any THING that has a SUBTHING with your condition. So:
var result = from thing in search
where thing.Subthings.Any(tr => tr.Prop1 == 1 && tr.Prop2 != null)
select new { ID = thing.ID, Names = thing.Subthings.Where(tr => tr.Prop1 == 1 && tr.Prop2 != null) };
Option 2: SUBTHING approach.
You're looking at ALL SUBTHINGs and finding the ones where the condition is met, grouping by the ID at that point.
var result = from thing in search
from sub in thing.Subthings
where sub.Prop1 == 1 && sub.Prop2 != null
group sub by thing.id into sg
select new { ID = sg.Key, Names = sg.Select(tr => tr.Name) };
I like this approach just a little better, but still room for improvement. The reason I like this is because you find the SUBTHINGs first, and only then will it pull the THING that's associated with it (instead of first having to find if any SUBTHING matches the criteria, and THEN selecting it).
Option 3: Hybrid approach.
This is a little of both. We're going to select from SUBTHINGs either way, so might as well just perform the select. Then, if any of the projected subcollections have any elements, then we return our THING with the Names.
var result = from thing in search
let names = thing.Subthings
.Where(sub => sub.Prop1 == 1 && sub.Prop2 != null)
.Select(sub => sub.Name)
where names.Any()
select new { ID = thing.ID, Names = names };
Cleanest option, in my opinion. The Any extension method on a collection without any parameters will return true if there are any items in the collection. Perfect for our situation.
Hope that helps, let us know what you came up with.

c# filter search, multiple searchboxes

List<LICENSE> licenseList = context.LICENSE.Where(l => ( string.IsNullOrEmpty(licenseID) || l.LICENSE_ID.Contains(licenseID) ) && ( string.IsNullOrEmpty(hardwareID) || l.HARDWARE_ID.Contains(hardwareID) ) ).Take(10).ToList();
This is my current solution for handling more than one searchbox. Its a search function that combines 2 or more textfields in to a search. So my questions are: Is this an ok way to filter out the passed searchstrings. And how do i use it when the queries are decimals instead of strings? Thanks
Your example is perfectly fine.
With regards to it being decimals:
If it is a nullable type, then you first have to check if it has a value, and if it has that it is not the default value for a decimal which is 0.
If it is not a nullable type, then all you have to do is check that it is/is not == to 0 which is the default type. I always just check to make sure it's greater than zero, based on the assumption that a license won't be negative.
I am going to assume that it's not a nullable type as it seems to be an inline declared var, so here is a formatted example for decimal:
List<LICENSE> licenseList =
context.LICENSE.Where(l => licenseID == 0 || l.LICENSE_ID.Contains(licenseID))
.Where(l => hardwareID == 0 || l.HARDWARE_ID.Contains(hardwareID))
.Take(10)
.ToList();
Interesting thing to note, if you don't know the default type of a field, you can always do
licenseID == default(decimal)
You may try using a foreach loop on the search boxes, modifying linq, for any of those.
object[] a = {"seach", 5}; // "Data"
string[] Search = { "asdf", "asdf" }; //Search boxes
var s = a.Where(l => ((string)l).Contains(Search[0])); //first search
for (int i = 1; i < Search.Length; i++) //consecutive searches
s = s.Where(l => ((string)l).Contains(Search[i]));
Yes it looks ok. You could also use a loop somehow like this:
var query = context.LICENSE;
foreach(var item in stringVariables) {
query = query.Where(x => string.IsNullOrEmpty(item) || l.LICENSE_ID.Contains(item));
}
and stringVariables can be predefined or some algorithm to decide whether it's a search field or not.
Concerning the numbers (and assuming your column has type int, if it is a string you don't have to change anything), you probably have a nullable number, depending on your search form. So, you also have to check whether it's null or not and whether it's the right number. You may want to cast it to a string to also have the Contains function. But that all depends on your application.

Categories