Check for null after FirstOrDefault? [duplicate] - c#

This question already has an answer here:
selecting a property from FirstOrDefault in case FirstOrDefault returns null
(1 answer)
Closed 4 years ago.
I want to find the first member of a dictionary which satisfies certain condition and then get some properties from the found item. My concern is what if no item is found. Consider this code:
Dictionary<int, Class1> dict;
....
....
var foundPair = dict.Select(i => new { i }).FirstOrDefault(somePredicate);
SomeClass result = null;
if (foundPair != null)
result = foundPair.i.Value.SomeProp;
The result is what I'm looking for and this code looks obscure to me. Can we get the above functionality in a single linq chain? I mean something similar to this:
var result = protsDict.Select(i => new { i }).FirstOrDefault(somePredicate).SomeLinqChain(...).i.Value.SomeProps;
And the result should be SomeProps if an item is found and null if no item is found.

It could be rewritten, in various ways, as:
The key is: use Where
var result = protsDict.Where(somePredicate).SomeLinqChain(...) //some chain
.Where(someMore) //some more
.Where(i => i.Value != null) //or even...
.Select(i =>i.Value.SomeProps).FirstOrDefault();//then select; take or skip

You can use Where followed by Take(1) to isolate at most one element which satisfies the condition.
var foundPair = dict
.Select(i => new { i })
.Where(somePredicate)
.Take(1)
.Select(x => some_mapping(x)
...;
When done this way, the subsequent expressions will only be executed if the first element satisfying the predicate has been found, and only on that element. Should more elements satisfy the predicate, all but the first one would be ignored.
In that sense, this technique works the same as FirstOrDefault, only in a safe way.

Related

How to get all list items that match a specific interface? [duplicate]

I have a List<> of abstract objects that contains different types of objects.
I am trying to grab all the items of a certain type and set those items to their own List<>.
This is not working -
//myAbstractItems is a List<myAbstractItem>
//typeAList inherents from myAbstractItem
var typeAList = ((List<itemTypeA>)myAbstractItems.Where(i => i.GetType() == typeof(itemTypeA)).ToList());
The casting (List<itemTypeA>) appears to be failing.
Use the OfType extension method:
var typeAList = myAbstractItems.OfType<itemTypeA>().ToList();
From the documentation...
The OfType(IEnumerable) method returns only those elements in source that can be cast to type TResult.
A good old loop should be fine :
List<itemTypeA> res = new List<itemTypeA>();
foreach(var item in myAbstractItems)
{
itemTypeA temp = item as itemTypeA;
if (temp != null)
res.Add(temp)
}
Try using Where this way:
var typeAList = myAbstractItems.Where(i => i.GetType() == typeof(itemTypeA)).Select(item => item as itemTypeA).ToList())
Another way you could do this is using the OfType() method:
var typeAList = myAbstractItems.OfType<itemTypeA>().ToList();
This method basically performs the following operation:
var typeAList = myAbstractItems.Where(i=>i is itemTypeA).Select(i=>i as itemTypeA).ToList();
Keep in mind that this will fail if any element of the source collection is a null reference.
This will work for all itemTypeAs (and more derived types).
var typeAList = myAbstractItems.Select(i => i as itemTypeA).Where(i => i != null).ToList();
EDIT: edited as per Rawling's comment.

Using LINQ to populate a string with a single column value

I'm a newbie both to C# and to LINQ and would appreciate a small push in the right direction.
Firstly, I have an Overrides SQL table (and a corresponding EF DB context), which has a Type, Value, and Override Value. The idea is that for a particular kind ("Type") of override, the code can check a particular value and go see if there is an override value that should be used instead.
var branchOverrides = overridesSqlContext.Overrides
.Where(q => q.Type == "Branch Override")
.Select(s => new
{
s.Value,
s.OverrideValue
});
In this case, I want the list of different override values of the "Branch Override" type. From there, I would like to be able to retrieve a specific override value at a given point within my code. How can I query the branchOverrides variable I've created to be able to say something like:
string readingOverride = select OverrideValue from branchOverrides where Value = "Reading"
My code will need to be able to read various override values for different branches at different points, and being able to query the branchOverrides variable at any point would seem like the ideal approach.
Thank you for any assistance on this.
You can use Single() on the query object you have:
string readingOverride = branchOverrides
.Single(bo => bo.Value == "Reading")
.OverrideValue;
This will throw an exception if an entry doesn't exist though so you probably want to use SingleOrDefault instead and check for a null return.
Also note that the branchOverrides object here is an IQueryable<> which means that every time you use it, it will send a query to the database. You may want to materialise that to a local list by adding .ToList() after the Select(...). Alternatively, you may want to look at caching this data, especially if it's going to be used frequently.
If I understood you right, you want the entry with Value = "Reading" and Type="Branch Override":
var branchOverride = overridesSqlContext.Overrides
.SingleOrdDefault(q => q.Type == "Branch Override"
&& q.Value == "Reading")
.Select(s => new
{
s.Value,
s.OverrideValue
});
if (branchOverride != null)
{
// do whatever ...
}
For performance issue is good to put .ToList() in the end of your LINQ expression if you need to iterante over that list too many times.
var branchOverrides = overridesSqlContext.Overrides
.Where(q => q.Type == "Branch Override")
.Select(s => new
{
s.Value,
s.OverrideValue
}).ToList();
If it you will load the entire list into the memory avoiding to execute the sql query to fetch the data if you need to iterate through your list.
Other thing that you can do is:
string readingOverride = string.Empty;
var branchOverride = branchOverrides.FirstOrDefault(x => x.Value == "Reading");
if(branchOverride != null)
{
readingOverride = branchOverride.OverrideValue;
}
Hope that helps.
If Value is unique within "Branch Override" perhaps you want to turn it to a dictionary for fast lookup
var branchOverrides = overridesSqlContext.Overrides
.Where(q => q.Type == "Branch Override")
.Select(s => new
{
s.Value,
s.OverrideValue
})
.ToDictionary(k => k.Value, v => v.OverrideValue);
Then later on you can find the override value quickly and efficiently
var readingOverride = branchOverrides["Reading"];

Null Propagation

When the linq query condition is not met, I'd expect a null to be returned from questions.FirstOrDefault() - but instead, an exception
Sequence contains no matching element
is thrown. Any ideas why?
var firstQ = questions.FirstOrDefault(a =>
a.Answers.Single(x => x.CourseAssignmentId ==
courseAssignmentId)?.Score == null) ?? questions.FirstOrDefault();
That's the difference between Single and SingleOrDefault.
Single throws an exception if there's any number of items different than 1 that match your predicate.
You should be using FirstOrDefault() instead. BTW you can combine the condition probably like
a.Answers.Single(x => x.CourseAssignmentId == courseAssignmentId && x.Score == null)
As others have already mentioned, it's the expected behavior of Enumerable.Single.
Anyway, it looks like an XY problem. Probably you should store the last scored question somewhere (e.g a dictionary).
That is, you could refactor your code as follows:
var assignmentScoredQuestionMap = new Dictionary<int, Question>();
// Fill the whole dictionary:
// You need to add for which assignment identifier you've answered a question
int assignmentId = 384;
// If the whole assignment exists, you set lastScoredQuestion, otherwise
// you set it to first question.
if(!assignmentScoredQuestionMap.TryGetValue(assignmentId, out var lastScoredQuestion))
lastScoredQuestion = questions.FirstOrDefault();

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 assign to a variable the results of Find

I want to assign a variable from a Find method. Something like this:
object a = Collection.Find(x => x.propertie == whatever).propertie
The problem here is if my find query doesn't find anything. I just wanted to know if there was a way to do it only with one line.
What you can do is use LINQ to project your sequence of zero to n items into a property of that sequence. This will only apply the projection if the item exists:
var a = collection.Select(x => x.Property)
.FirstOrDefault(value => value == whatever);

Categories