LINQ ANY() with First() And FirstOrDefault() - c#

I've written code like this
TotalCashIn = totals != null && totals.Any() ? totals.First().TotalCashIn : null;
And i have been blamed for this code and have told to write like this
TotalCashIn = totals != null ? totals.FirstOrDefault().TotalCashIn : null;
But I am wondering would not I get an exception if totals count would be 0?
Do I need also check this with .Any() or .Count()?

You can use the null-conditional operator to make all of this a lot simpler, assuming that the element type of totals is a reference type:
TotalCashIn = totals?.FirstOrDefault()?.TotalCashIn;
With this:
If totals is null, the overall result is null due to the first null-conditonal operator
If totals is empty, FirstOrDefault() will return null, so the overall result is null due to the second null-conditional operator
Otherwise, the result is the TotalCashIn property of the first element

It depends whether totals is IEnumerable or IQueryable. If it is IQueryable then you can also use following:
TotalCashIn = totals?.Select(t => (int?)t.TotalCashIn).FirstOrDefault();
It would be better as you select only desired field and not all columns in a table.

If totals is a reference type object, then indeed, FirstOrDefault might return null, and thus you can't call TotalCashIn.
If on the other hand totals is a value type, then FirstOrDefault would return an actual value and you could call TotalCashIn. For instance if totals would be a sequence of integers, then FirstOrDefault would return zero (not null!).
If TotalCashIn would be an extension function of int, then if would be perfect to call TotalCashIn after FirstOrDefault:
IEnumerable<int> totals = ...
int firstInt = totals.FirstOrDefault();
var totalCashIn = firstInt.TotalCashIn(); // extension function of int
or in one statement:
var totalCashIn = totals.FirstOrDefault().TotalCashIn();
Whether it is wise to use the null-conditional operator (?.) depends on the definition of your TotalCashIn, especially if your collection has no element at all.
If you'd say: "TotalCashIn represents the amount of money I cashed while ...", then TotalCashIn could have a zero value, but not a null value.
If you want a zero value in case of empty collections, consider to use Select to select the property you want before your FirstOrDefault:
var totalCashIn = totals.Select(item => item.TotalCashIn).FirstOrDefault();
If property TotalCashIn is a value type then this will return zero if your totals collection is empty
Upon your question whether to use Count() or Any(): never use Count() to test if your sequence has any elements unless you are certain the your input sequence is an ICollection. It is really a waste of computing power to access all elements of your sequence if after the first element you would already know there are elements.

Related

Skip an empty sequence inside a LINQ .Where statement

I have an array of Tuple arrays like this:
(Type, Func<int, bool>)[][] Formats;
I also have a Type typ and a int i.
I then need an IEnumerable<(Type, Func<int,bool>[])> (aka return items) where a potential match is evaluated by Item1 (i.e. Type) matching typ in the format .IsAssignableFrom(typ). If we get a match, then we aren't done: we only include it if Item2.Invoke(i) evaluates to true.
var results = Formats.Where(f => f.Where(o => o.Item1.IsAssignableFrom(typ)).First().Item2.Invoke(i));
This is not valid because I think if a return item doesn't have an inner item to retrieve in the .First() call, it throws an InvalidOperationException "Sequence contains no data" because you can't call .First() on an empty sequence.
Can you help restate this Linq to ignore the sequence if empty?
This is a perfect opportunity to use the Enumerable.FirstOrDefault() method:
Returns the first element of a sequence, or a default value if no element is found.
In this case, the "default value" would be (null, null). So, use FirstOrDefault() instead of First(), and then ignore items appropriately. Something like this might work:
var results = Formats.Where(f => f.Where(o => o.Item1.IsAssignableFrom(typ)).FirstOrDefault().Item2?.Invoke(i) ?? false);

How can I make Sum() return 0 instead of 'null'?

I'm trying to use LINQ-to-entities to query my DB, where I have 3 tables: Room, Conference, and Participant. Each room has many conferences, and each conference has many participants. For each room, I'm trying to get a count of its conferences, and a sum of all of the participants for all of the room's conferences. Here's my query:
var roomsData = context.Rooms
.GroupJoin(
context.Conferences
.GroupJoin(
context.Participants,
conf => conf.Id,
part => part.ConferenceId,
(conf, parts) => new { Conference = conf, ParticipantCount = parts.Count() }
),
rm => rm.Id,
data => data.Conference.RoomId,
(rm, confData) => new {
Room = rm,
ConferenceCount = confData.Count(),
ParticipantCount = confData.Sum(cd => cd.ParticipantCount)
}
);
When I try and turn this into a list, I get the error:
The cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
I can fix this by changing the Sum line to:
ParticipantCount = confData.Count() == 0 ? 0 : confData.Sum(cd => cd.ParticipantCount)
But the trouble is that this seems to generate a more complex query and add 100ms onto the query time. Is there a better way for me to tell EF that when it is summing ParticipantCount, an empty list for confData should just mean zero, rather than throwing an exception? The annoying thing is that this error only happens with EF; if I create an empty in-memory List<int> and call Sum() on that, it gives me zero, rather than throwing an exception!
You may use the null coalescing operator ?? as:
confData.Sum(cd => cd.ParticipantCount ?? 0)
I made it work by changing the Sum line to:
ParticipantCount = (int?)confData.Sum(cd => cd.ParticipantCount)
Confusingly, it seems that even though IntelliSense tells me that the int overload for Sum() is getting used, at runtime it is actually using the int? overload because the confData list might be empty. If I explicitly tell it the return type is int? it returns null for the empty list entries, and I can later null-coalesce the nulls to zero.
Use Enumerable.DefaultIfEmpty:
ParticipantCount = confData.DefaultIfEmpty().Sum(cd => cd.ParticipantCount)
Instead of trying to get EF to generate a SQL query that returns 0 instead of null, you change this as you process the query results on the client-side like this:
var results = from r in roomsData.AsEnumerable()
select new
{
r.Room,
r.ConferenceCount,
ParticipantCount = r.ParticipantCount ?? 0
};
The AsEnumerable() forces the SQL query to be evaluated and the subsequent query operators are client-side LINQ-to-Objects.

Linq sorting with nullable object

could you please help me!
I have object list like:
item[0].title = apple
item[0].data.weight = 1
item[1].title = lemon
item[1].data = null
item[2].title = melon
item[2].data.weight = 3
I would like to sort it (ASC and DESC) by weight with null data.
I tried like this:
item.OrderBy(x => x.data == null).ThenBy(x => x.data.weight); // failed
item.Where(x => x.data != null).OrderBy(x => x.data.weight); // ok, but as result only two records
So how i can sort items and receive all results.
ASC at first should be data with null.
DESC at first data with max weight and null at the end of the list.
item.OrderBy(x => x.data == null).ThenByDescending(x => x.data == null ? 0 : x.data.weight);
I am assuming weight is an int, otherwise provide the default value based on type.
Given you're only shipping fruit, and not, say, light, you can treat items having null data as having weight 0. Alternatively, just pick any value that's lower than the possible, valid values in order to put the null items at the top when sorting ascendingly.
You can express that like this:
var ordered = item.OrderBy(x => x.data == null ? 0 : x.data.weight);
You could use something like this: (assuming C# 6 or above)
item.OrderBy(x => x.data?.weight ?? int.MinValue);
This makes use of the new C#6 null-conditional and null-coalescing operators - if you need something applicable in lower versions of C#, you can use a ternary operator, like this:
item.OrderBy(x => x.data != null ? x.data.weight : int.MinValue);
If it's possible that you could have x.data.weight being int.MinValue, then you would need to do something like what you were doing before, but the second linq method should make use of the above lambda/s.
You can do this a few ways, one way would be to have a value replacing the null values using ternary conditional operator on the order by or filtering out the items without a value and concatenating them to the enumerable after you've sorted the objects with values.
By conditionally providing a value for items with null
This is, in my opinion, the best way, and it performs better. You only enumerate over the collection once, versus the other method where you enumerate to determine if each element has a value then order, and then check for the items without a value
item.OrderBy(x => x.data != null ? x.data.weight : int.MinValue)
Filtering and then concatenating the items without a value
There are times where this could possibly be the better solution. One example would be if you want to use a different method for ordering the values when they are missing the property you are looking for.
item.Where(x => x.data != null)
.OrderBy(x => x.data.weight)
.Concat(item.Where(a=>a.data == null))

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

How to Get a Object from IEnumerable collection using LINQ Lambda?

storageColl is having a IStorage with property "Id" as "Test".
What I am doing-
string id="Test";
IEnumerable<IStorageObject> storageColl = getStorageCollection();
IStorageObject storageObject = storageColl.ToList().Where(m => m.Properties["Id"] == id)
.ToList()
.Cast<IStorageObject>().ToArray()[0];
Is there a better way to do this. As this may throw array out of bound exception if the storageColl will not have that "Test".
You can use FirstOrDefault on the IEnumerable.
var storageObject = storageCol1.Where(m => m.Properties["Id"] == id).FirstOrDefault();
Or as David Hedlund pointed out, use the predicate overload on FirstOrDefault and remove the Where.
var storageObject = storageCol1.FirstOrDefault(m => m.Properties["Id"] == id);
Your storageColl is a sequence of objects that implement IStorageObject. The use of the Where only limits the elements you get when you enumerate over the sequence, it does not change them.
It is a waste of processing power to convert the sequence to a list when you only need the first element of the sequence or the a subset of it.
Familiarize yourself with the following Ling functions:
Any() returns true if the sequence contains at least one element
Any( item => ....) return true if any of the elements in the sequence meets the requirement
First() returns the first element of the sequence. Exception if not Any()
FirstOrDefault returns the first element of the sequence or the default (usually null) if not Any()
The nice thing about these functions is that they don't have to enumerate over all elements in the sequence, but can stop as soon as they found something.
If you use ToList() the code enumerates over all elements, throws most of them away and uses only the first element. FirstOrDefault() would have stopped after the first enumeration.
since the collection is implement IStorageObject you don't neet to cast them and for get item by index you can use any class that utilizes Array or IList
since LINQ operates on IEnumerable (Array itself is enumerable->to iterate) you don't need to cast them to array.you can utilize ElementAt method or Use IList classes (as List)
IStorageObject storageObject = storageColl.Where(m => m.Properties["Id"] == id).First();
You can simply achieve this by
var result = storageColl.Where(m => m.Properties["Id"] == id);
You should check firstOrDefault because can return null.
var Object = storageCol.Where(p => p.Properties["Id"] == id).FirstOrDefault();
if(Object != null)
{
// Do some Work
}

Categories