Linq FirstOrDefault - one liner - c#

i have the following code to determine a age from a Person
var pList = ctx.Person.Where(x => x.Create > Date);
int Age = pList.Where(x => x.ID == "foo").FirstOrDefault().Age ?? 20;
I pick a Person by ID, if it doesn't exist the default value is 20.
The 2nd line is invalid, because Age can't be null but Person can be. Is there a way to get this working in one line? I've tried with DefaultIfEmpty but doesn't seem to work.

You can use the overload of Enumerable.DefaultIfEmpty:
int Age = pList
.Where(x => x.ID == "foo")
.Select(x => x.Age)
.DefaultIfEmpty(20)
.First();
As you can see, FirstOrdefault is not necessary anymore since the default value is taken if the input sequence is empty(the id-filter returned no persons).

int Age = pList.Where(x => x.ID == "foo").FirstOrDefault()?.Age ?? 20;
Only in C# 6.
For those in suspicion:

You can do it like this:
int Age = pList.Where(x => x.ID == "foo").Select(x=>(int?)x.Age).FirstOrDefault() ?? 20;

It's not pretty, by any means, but you wanted to do it as short as possible, while still counting for several potential NullPointerExceptions. Please dont do this in a one liner, and please dont make the int nullable to acheive that. The code below is not pretty, and not tested as i don't have the possibility at the moment.
Note that i would recommend doing it differently, with the long hand if statements, for zero code repetition and readability.
Person person = ctx.Person.Where(x => x.Create > Date && x.ID.Equals("foo")).FirstOrDefault()
int age = (person != null) ? person.Age : 20;

Related

How to find the max-ID by using a lambda-query?

I wanna find the next ID (ID_Titel) by selecting ID_Artiest. I try it by using LINQ (lambda-query). What do I do wrong.
By example:
For ID_Artiest 2, I wanna have ID_Titel n+1.
First I try it without .DefaultIfEmpty(). and then I get a message: Sequence Contains No Element
Second I try it with .DefaultIfEmpty().: And then I get the message: Object reference not set to an instance of an object.
This is the source:
private void UpdateTitel(NoteringDataType itemDezeWeek)
...
...
TitelDataType titel = new TitelDataType();
if (Titelslijst.Count > 0)
titel.ID_Titel = Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest).OrderBy(t => t.ID_Titel).DefaultIfEmpty().Max(t => t.ID_Titel) + 1;
else
titel.ID_Titel = 1;
Both of the errors you got are expected if the first Where doesn't find any matching values.
Please try this:
titel.ID_Titel = Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest)
.Select(t => t.ID_Titel)
.OrderByDescending(t => t)
.FirstOrDefault() + 1;
Since the Select transforms the collection to an enumeration of ints (I am assuming), the .FirstOrDefault() should return 0 if the Where() didn't return any matches. In your code, .DefaultIfEmpty() would have returned an IEnumerable<T> containing null because it was called on an enumeration of whatever Titelslijst contains.
Apparently the errors you see happen when nothing in the list matches your where condition. DefaultIfEmpty in this case will give you just a single list with null, which does not help a lot.
To avoid such situations you should really assign ID to 1 if there no entries found. So the logic should be like:
TitelDataType titel = new TitelDataType();
int id = 1;
if (Titelslijst.Count > 0)
{
var titles = Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest);
if (titles.Any())
id = titles.Max(t => t.ID_Titel) + 1;
}
titel.ID_Titel = id;
More short, more right:
Titelslijst.Where(t => t.ID_Artiest == itemDezeWeek.ID_Artiest).Select(t => t.ID_Artiest).Max();

More fancy way to deal with selecting non-existing records to avoid null exception errors

It was bugging me, why neither of ?? or == null works when I want to select a record that doesn't exist. For example if I do
var foo = db.Where(k => k.ID == 9) ?? bar;
it works fine, but it crashes (Object reference not set to an instance of an object error) when I want to select single field:
var foo = db.Where(k => k.ID == 9).Select(k => k.Field).FirstOrDefault() ?? bar;
//or
var foo = bar;
if (db.Where(k => k.ID == 9) != null)
foo = db.Where(k => k.ID == 9).Select(k => k.Field).FirstOrDefault()
In both cases It crashes, which seems to be pretty logical.
Normally I wouldn't mind doing
if (Order.A_Data.Where(k => k.FieldID == 9).Count() > 0)
writeText(cb, Order.A_Data.Where(k => k.FieldID == 9).
Select(k => k.Content).
FirstOrDefault().ToString(),
left_margin, top_margin - 24, f_cn, 10);
//this looks more like the actual code, but in fact it has multiple 'where' conditions and more tables connected with an external key, that's why I don't want to write the same thing over and over again
//or creating new variables
but sadly I have to re-create really complicated PDF document with literally hundreds of fields* like these obtaining data from dozens of tables, so every "trickier way" will be very helpfull. Also, it's very slow.
Or maybe there is a keep-your-null-exception-errors-for-yourself-and-just-return-an-empty-string switch?
*278 to be more specific. So I can either find easy way to do this, or prepare another bucket of coffee, sigh and do my job
Can't you just use:
var foo = db.Where(k => k.ID == 9).FirstOrDefault().Field;
That should return a default object with null properties if it didn't find a row.
Edit Now I understand. You want something like this:
var foo = db.Where(k => k.ID == 9).DefaultIfEmpty(bar).First().Field;
In response to your edit, a database nvarchar usually maps to a String, a reference type.
But when a database nvarchar is null, it returns DBNull.Value to the .NET client instead of null. (Must be the most confusing thing in all of .NET.)
Try:
var foo = bar;
var row = db.FirstOrDefault(k => k.ID == 9);
if (row != null && row.Field != DBNull.Value)
foo = row.Field;
The problem seems to be here
if (Order.A_Data.Where(k => k.FieldID == 9).Count() > 0)
writeText(cb, Order.A_Data.Where(k => k.FieldID == 9).
Select(k => k.Content).
FirstOrDefault().ToString(),
left_margin, top_margin - 24, f_cn, 10);
You select where ID=9, take only the Content and then FirstOrDefault. So far so good, if there is a match you get the order. But if there is no match the FirstOrDefault will return null and therefore the ToString will throw an exception. Try this:
var order = Order.A_Data.SingleOrDefault(k => k.FieldID == 9); // Get the order
if(order != null){ // and check if we have a match
writeText(cb, order.Content.ToString(), // write the Content of the match
left_margin, top_margin - 24, f_cn, 10);
}else{
// handle however you want when there are no orders matching
}
Note that I'm using SingleOrDefault instead of FirstOrDefault. This is because I'm assuming you will only have one order with FieldID equal to 9. If you can have more, use FirstOrDefault instead.

LINQ check if FirstOrDefault is null and use it

I'm writing a query that uses FirstOrDefault after an OrderBy query, which should check if it isn't null first then use some data in it. Is there a better way than writing it like this:
int count = db.Items.Count(i =>
i.Assignments.OrderByDescending(a =>
a.DateAssigned).FirstOrDefault() != null
&&
i.Assignments.OrderByDescending(a =>
a.DateAssigned).FirstOrDefault().DateReturned == null)
What this code does is there are items that has many assignments, I take the latest assignment by date, then check if it exist, then run a condition on a property (DateReturned). As you see, this query is long, and most of my queries seem to look like this where I check for null first then run a second query on it using their properties. Is there a better way of doing this?
Just call .Any(a => a.DateReturned == null) to check whether there are any items that meet the condition.
If you only want to check the latest assignment, add .Take(1) before the .Any().
My take:
int count =
itemsQuery.Select(i => i.Assignments.OrderByDescending(a => a.DateAssigned))
.Count(i => i.FirstOrDefault() != null &&
i.First().DateReturned == null);
You can put the result in a variable to avoid doing the same thing twice:
int count = itemsQuery.Count(i => {
var f = i.Assignments.OrderByDescending(a => a.DateAssigned).FirstOrDefault();
return f != null && f.DateReturned == null;
});

What's Wrong With My Lambda Expression?

I'm trying to write a simple Lambda expression in C#:
int numElements = 3;
string[]firstnames = {"Dave", "Jim", "Rob"};
string[]lastnames = {"Davidson", "Jameson", "Robertson"};
List<Person> people = new List<Person>();
for(int i = 0 ; i < numElements; i++)
{
people.Add(new Person { FirstName = firstnames[i], LastName = lastnames[i] });
}
bool test = people.Contains(p => p.FirstName == "Bob");
My understanding of Lambda expressions and how they work is still a little shady and I miffed as to why this will not work...I'm trying to find out if a list contains a name...
Are you looking for:
bool test = people.Any(p => p.FirstName == "Bob");
Or are you mixing Rob and Bob?
The problem here is not lambdas but instead the boundaries of the for loop. The arrays you defined have a length of 3 but numElements is defined to have a value of 10. This means you will get an exception for an illegal array index on the 4th iteration of the loop. Try the following
int numElements = 3;
Or more simply remove the numElements variable and instead iterate to the length of the firstnames array
for (int i = 0; i < firstnames.length; i++) {
...
}
EDIT
OP indicated that the numElements originally posted was a typo. Other possible sources of error in the code
Use "Rob" instead of "Bob" if you want to find a matching element
The Contains method on GenericList<T> needs to have a compatible delegate signature. Func<T, bool> for example
Make sure you are linking the System.Linq namemespace, i.e.
using System.Linq;
You are using the Contains method. This method expects a Person and will use an equality comparison to determine if your collection already contains it. In the default case, the equality comparison defaults to reference comparison so it will never contain it, but that's another topic.
To achieve your goal, use the Any method. This will tell you if ANY of the elements in your collection conform to a condition.
people.Any(p => p.FirstName == "BoB");
You may want to read about the extension methods First and FirstOrDefault and Where as they would also solve your problem.
You don't set numElements to the correct value ( you set it to 10, but your arrays only have 3 values) - furthermore you don't even need it, just use a collection initializer instead of those separate string arrays:
GenericList<Person> people = new GenericList<Person>()
{
new Person { FirstName = "Dave", LastName = "Davidson" },
new Person { FirstName = "Jim", LastName = "Jameson" }
new Person { FirstName = "Rob", LastName = "Robertson" }
}
Now assuming your GenericList<T> class implements IEnumerable<T> you can use Any() to do your test:
bool test = people.Any(p => p.FirstName == "Bob");
what your real problem with this lambdas ?
If because you have test false then that's true you don't have "Bob" in firstName
bool test = people.Contains(p => p.FirstName == "Bob");
and
string[]firstnames = {"Dave", "Jim", "Rob"};
Couple of problems here.
One: GenericList is not a type. You were probably looking for the generic type System.Collections.Generic.List<T>.
Two: Contains accepts a Person in your example, not a delegate (lambdas are a new way to write delegates as of C# 3). One way to get what you want here would be to combine Where and Count, in the form of bool test = people.Where(p => p.FirstName == "Bob").Count() > 0;
// We will use these things:
Predicate<Person> yourPredicate = p => p.FirstName == "Bob";
Func<Person, bool> yourPredicateFunction = p => p.FirstName == "Bob";
Person specificPerson = people[0];
// Checking existence of someone:
bool test = people.Contains(specificPerson);
bool anyBobExistsHere = people.Exists(yourPredicate);
// Accessing to a person/people:
IEnumerable<Person> allBobs = people.Where(yourPredicateFunction);
Person firstBob = people.Find(yourPredicate);
Person lastBob = people.FindLast(yourPredicate);
// Finding index of a person
int indexOfFirstBob = people.FindIndex(yourPredicate);
int indexOfLastBob = people.FindLastIndex(yourPredicate);
You should play with LINQ methods somewhile...

Max return value if empty query

I have this query:
int maxShoeSize = Workers
.Where(x => x.CompanyId == 8)
.Max(x => x.ShoeSize);
What will be in maxShoeSize if company 8 has no workers at all?
UPDATE:
How can I change the query in order to get 0 and not an exception?
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
.Select(x => x.ShoeSize)
.DefaultIfEmpty(0)
.Max();
The zero in DefaultIfEmpty is not necessary.
I know this is an old question and the accepted answer works, but this question answered my question about whether such an empty set would result in an exception or a default(int) result.
The accepted answer however, while it does work, isn't the ideal solution IMHO, which isn't given here. Thus I am providing it in my own answer for the benefit of anyone who is looking for it.
The OP's original code was:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize);
This is how I would write it to prevent exceptions and provide a default result:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize as int?) ?? 0;
This causes the return type of the Max function to be int?, which allows the null result and then the ?? replaces the null result with 0.
EDIT
Just to clarify something from the comments, Entity Framework doesn't currently support the as keyword, so the way to write it when working with EF would be:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max<[TypeOfWorkers], int?>(x => x.ShoeSize) ?? 0;
Since the [TypeOfWorkers] could be a long class name and is tedious to write, I've added an extension method to help out.
public static int MaxOrDefault<T>(this IQueryable<T> source, Expression<Func<T, int?>> selector, int nullValue = 0)
{
return source.Max(selector) ?? nullValue;
}
This only handles int, but the same could be done for long, double, or any other value type you need. Using this extension method is very simple, you just pass in your selector function and optionally include a value to be used for null, which defaults to 0. So the above could be rewritten like so:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).MaxOrDefault(x => x.ShoeSize);
Hopefully that helps people out even more.
Max() won't return anything in that case.
It will raise InvalidOperationException since the source contains no elements.
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
.Select(x => x.ShoeSize)
.DefaultIfEmpty()
.Max();
If this is Linq to SQL, I don't like to use Any() because it results in multiple queries to SQL server.
If ShoeSize is not a nullable field, then using just the .Max(..) ?? 0 will not work but the following will:
int maxShoeSize = Workers.Where(x = >x.CompanyId == 8).Max(x => (int?)x.ShoeSize) ?? 0;
It absolutely does not change the emitted SQL, but it does return 0 if the sequence is empty because it changes the Max() to return an int? instead of an int.
int maxShoeSize=Workers.Where(x=>x.CompanyId==8)
.Max(x=>(int?)x.ShoeSize).GetValueOrDefault();
(assuming that ShoeSize is of type int)
If Workers is a DbSet or ObjectSet from Entity Framework your initial query would throw an InvalidOperationException, but not complaining about an empty sequence but complaining that the materialized value NULL can't be converted into an int.
Max will throw System.InvalidOperationException "Sequence contains no elements"
class Program
{
static void Main(string[] args)
{
List<MyClass> list = new List<MyClass>();
list.Add(new MyClass() { Value = 2 });
IEnumerable<MyClass> iterator = list.Where(x => x.Value == 3); // empty iterator.
int max = iterator.Max(x => x.Value); // throws System.InvalidOperationException
}
}
class MyClass
{
public int Value;
}
NB: the query with DefaultIfEmpty() may be significantly slower.
In my case that was a simple query with .DefaultIfEmpty(DateTime.Now.Date).
I was too lazy to profile it but obviously EF tried to obtain all the rows and then take the Max() value.
Conclusion: sometimes handling InvalidOperationException might be the better choice.
You can use a ternary within .Max() to handle the predicate and set its value;
// assumes Workers != null && Workers.Count() > 0
int maxShoeSize = Workers.Max(x => (x.CompanyId == 8) ? x.ShoeSize : 0);
You would need to handle the Workers collection being null/empty if that's a possibility, but it would depend on your implementation.
You can try this:
int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;
You could check if there are any workers before doing the Max().
private int FindMaxShoeSize(IList<MyClass> workers) {
var workersInCompany = workers.Where(x => x.CompanyId == 8);
if(!workersInCompany.Any()) { return 0; }
return workersInCompany.Max(x => x.ShoeSize);
}

Categories

Resources