Skip an empty sequence inside a LINQ .Where statement - c#

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

Related

LINQ ANY() with First() And FirstOrDefault()

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.

Get All instead of FirstOrDefault

I have the following query:
PromotionList dataPromotion = authenticateCustomerResponseRootObject.Response
.AuthenticateCustomerResponse.EligiblePromotions.PromotionList
.Where(p => p.LineOfBusiness.ToUpper().Equals("DATA"))
.FirstOrDefault();
My PromotionList contains 3 objects, with LineOfBusiness Data,Video and third object contains Data too. Above query returns PromotionList of first object only whereas I want ALL whose LineOfBusiness is equal to Data. Why is it happening?
You are using FirstOrDefault so you are returning only the first.
PromotionList dataPromotion = authenticateCustomerResponseRootObject.Response.AuthenticateCustomerResponse.EligiblePromotions.PromotionList.Where(p => p.LineOfBusiness.ToUpper().Equals("Data")).FirstOrDefault();
If you want all of them just remove that call at the end and replace with a ToList, ToArray or similar that meets your needs:
var data = authenticateCustomerResponseRootObject.Response.AuthenticateCustomerResponse.EligiblePromotions.PromotionList.Where(p => p.LineOfBusiness.ToUpper().Equals("Data")).ToList();
Also as mentioned in the comments your Where call uses ToUpper then compares on a string containing lower case characters so will never return any results. You either need to remove the ToUpper, use a upper case string or even use ignore case:
Where(p => p.LineOfBusiness.Equals("Data", StringComparison.OrdinalIgnoreCase))

Linq query “where [column] in (list of values)” that return single value for each values?

Let say we have this LinQ code (in LinqPad):
List<string> list = new List<string>();
list.Add("1611080010");
list.Add("1611080011");
list.Add("WRONGID");
var result = Orders.AsQueryable().Where(y => list.Contains(y.Id));
// And yes my Ids are string for this sample
result.Dump(); // To display the result in LinqPad
result.Count(); // equal 2
Is it possible to improve this query to force the system to return one element for each of my list element or throw an exception? So if I have 3 values in my list I should have 3 values in my result?
On your question: Is it possible to improve this query to force the system to return one element for each of my list element or throw an exception:
var result = Orders.AsQueryable().SingleOrDefault(y => list.Contains(y.Id));
SingleOrDefault
Returns the only element of a sequence, or a default value if the
sequence is empty; this method throws an exception if there is more
than one element in the sequence.
You can use any one from below one
Whenever you use SingleOrDefault, you clearly state that the query should result in at most a single result. On the other hand, when FirstOrDefault is used, the query can return any amount of results but you state that you only want the first one.
I personally find the semantics very different and using the appropriate one, depending on the expected results, improves readability.
var result = Orders.AsQueryable().SingleOrDefault(y => list.Contains(y.Id));
var result = Orders.AsQueryable()..FirstOrDefault(y => list.Contains(y.Id));
If I understand your question correctly you want to return one object from Orders where the Id of the object equals an Id in your list and if an Id in the list is not present in the Orders object an exception should be thrown.
To achieve that you could do:
var result = list.Select(id => Orders.AsQueryable().First(y => y.Id == id)).ToList();
This will throw an exception if an Id from the list has no match in the Orders object. If all Ids are found then your result will contain the same number of elements as there are Ids in the list.
Another option would be to use:
var result = list.Select(id => Orders.AsQueryable().FirstOrDefault(y => y.Id == id)).ToList();
This would not throw an error, but it would always return the same number of elements as there are Ids in the list. The Ids that would not be found would have a null entry though.

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
}

how to convert from IEnumerable<type> to type?

I am using EF 4 with repository pattern which has the generic query method shown below:
public IEnumerable<T> Query(Expression<Func<T, bool>> filter)
{
return objectSet.Where(filter);
}
I know I can select a complete object like this:
context.PeriodRepository.Query(a => a.EntityId == selectedEntityId);
But I want to pass a Linq query that returns it as type instead of IEnumerable<type> using a LINQ expression without changing the method. Please advise me how to do that.
Use the First() or FirstOrDefault() methods and pass in a predicate to find the element you want if it is not the first.
A Query describes the operations performed on the list; you must perform the query to get the result(s).
Using .ToList()/.ToArray() will return all items that match the query. Use .First() to get the first item that matches or .FirstOrDefault() to get the first item or the default value for no matches.
The default value for a class is null.
I think your code should be like this:
var myMatch = context.PeriodRepository
.Query(a => a.EntityId == selectedEntityId)
.First();
Also note that, First() will throw an exception when there is no match.

Categories