LINQ: Set a default value if navigation property is null - c#

This is my current LINQ statement:
var results = from l in leads
select new MyObject
{
LeadID = l.LeadID,
SelectedProposalEngineerID = l.LeadContacts.Where(contact => contact.LeadContactTypeID == LeadContactType.ProposalEngineer).FirstOrDefault().ContactID
};
The trouble I'm having is that the last item is often null. So when I try to convert "results" to a List, I get
{"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 don't want to make SelectedProposalEngineerID a nullable int, for problems that would cause down stream. How would I give it a value of 0 when it's null?
I have seen a LOT of other threads about this, but I can't seem to adapt any of their answers to this case.

Use DefaultIfEmpty extension method.
var results = from l in leads
select new MyObject
{
LeadID = l.LeadID,
SelectedProposalEngineerID =
l.LeadContacts.Where(contact => contact.LeadContactTypeID == LeadContactType.ProposalEngineer)
.Select(contact => contact.ContactID)
.DefaultIfEmpty(0)
.FirstOrDefault()
};

Nullable<int> ID;
var results = from l in leads
select new MyObject
{
LeadID = l.LeadID,
SelectedProposalEngineerID = (ID = l.LeadContacts.Where(contact => contact.LeadContactTypeID == LeadContactType.ProposalEngineer).FirstOrDefault().ContactID).HasValue ? ID.Value : 0;
};
A ternary operator should do the job. Assign the result to a variable, then if it's not null, cast the variable to int and return it, else return 0.

Related

Linq to SQL to map a nullable bool value to IsNull and not = NULL

I'm trying to use Linq to SQL to filter a table with a nullable bool value based on a variable.
But if the variable's value is NULL, LINQ seems to translate the query to Field = NULL instead of Field IS NULL.
Based on other posts I've seen to use object.Equals to map correctly. And that works, but only when the variable's value is null.
Sample code:
bool? relevantValue = null;
var q = from m in MyTable
where ???
select m;
Case 1:
If where clause is object.Equals(m.IsRelevant, relevantValue), it works when relevantValue is null and true, but returns incorrect results when relevantValue = false.
It translates in SQL to
WHERE (
(CASE
WHEN [t0].[IsRelevant] = 1 THEN 1
ELSE 0
END)) = 0
Case 2:
For where (relevantValue == null ? object.Equals(m.IsRelevant, relevantValue) : m.IsRelevant == relevantValue) it works for relevantValue = true or false, but when null gives an error that:
The argument 'value' was the wrong type. Expected 'System.Boolean'. Actual 'System.Nullable`1[System.Boolean]'.
Do I have other options that will work for null, true and false?
Maybe I missed something about the question but, Why not use the Nullable<> .HasValue and .Value to get a real boolean?
IQueryable<Person> People = new List<Person>
{
new Person
{
PersonId = 1,
IsMarried = true
},
new Person
{
PersonId = 2,
IsMarried = false
},
}
.AsQueryable();
bool? isMarried = null;
var query = from p in People
where !isMarried.HasValue || (isMarried.HasValue && p.IsMarried == isMarried.Value)
select p;

new blank, non null anonymous type

I need to insert a new blank, but non null anonymous type into a list of other anonymous types returned by a linq query. Is that possible? All I can get are nulls
var something =
( from a in x.As
where x != null
join b in x.Bs
on a.key equals b.key
select new
{
a.prop1,
a.prop2,
b.prop1,
b.prop2,
b.prop3
}).ToList();
// insert blank
//something.InsertRange(0, something.DefaultIfEmpty());
//something.InsertRange(0, something.Take(0));
//?
I don't know of a way to do it in a single query since the default for an anonymous type is null. What I would do is pre-create a "default" item and append it if necessary:
var blank = new {
prop1 = default(string), // can't use null
prop2 = default(string), // because the type cannot be inferred
prop3 = default(string),
prop4 = default(string)
};
var something = /*...*/.ToList();
if(!something.Any())
something.Add(blank);
Note that as long as the field names match (in name and type) blank will be of the same anonymous type as the one created by the Linq query.

Linq's FirstOrDefault to act as other type?

I have this code :
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.FirstOrDefault();
however , since this returns an anonymous type , its default value is null .
I want it to act as FirstOrDefault for int type.
so if there is no record , it will return 0 ( default behavior as int).
is it possible ?
p.s. ( of course i can check it in a condition but still , i prefer the linq way).
Return an anonymous type that signifies "nothing" and either use the null coalescing operator:
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.FirstOrDefault() ?? new { val = 0, idpolicy = "" };
Or the DefaultIfEmpty extension method:
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.DefaultIfEmpty(new { val = 0, idpolicy = "" })
.FirstOrDefault();
You would only be able to return an int in place of an anonymous type if you in fact return an object and cast later on (as per #recursive's answer), but this to me seems counter-productive.
FirstOrDefault does not offer a way to specify what "default" is.
You can't have an expression evaluate to 2 different data types on two execution paths.
Also, even if that's possible, var is different to dynamic so the variable type won't be inferred by the compiler.
Doing this doesn't make any sense, and I would encourage you to think about why you want to do this, and find some cleaner, more direct way to accomplish it.
With that said, here's a small tweak to #IronicMuffin's approach that will actually work.
object res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new {
val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString()
})
.FirstOrDefault() as object ?? 0;
I don't think this is very useful though.

How can I fix this LINQ function?

I'm using this code:
var nextLevel = (from p in cd.Objective
where p.Parent_ObjectiveID == null
select p.Level);
And it works, by the moment it returns no elements (because I don't have any element in my database). Although I'd like to know the Top level doing this:
var nextLevel = (from p in cd.Objective
where p.Parent_ObjectiveID == null
select p.Level).Max();
But I get an error:
The null value cannot be assigned to a member with type System.Int32 which is a non-nullable value type.
Parent_ObjectiveID is a nullable int and level in only int.
Max is looking to return an int because that's the type of p.Level, but forced to return a null (because there are no items in the query). If you cast p.Level to a nullable int, your query should work.
var nextLevel = (from p in cd.Objective
where p.Parent_ObjectiveID == null
select (int?)p.Level).Max();

Problem with LINQ to Entities query using Sum on child object property

Given this query:
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = s.Reservations.Sum(r => r.PartySize), // problem here. r.PartySize is int
s.MaxSeats
}
If the service doesn't have any reservations, this exception is thrown:
System.InvalidOperationException: The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
I get it, but how should I deal with it? My intention is if there are no reservations, then HeadCount be assigned 0.
There's an even simpler solution:
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = (int?)s.Reservations.Sum(r => r.PartySize),
s.MaxSeats
}
Note the cast. This may also produce simpler SQL than #Ahmad's suggestion.
Essentially, you're just helping out type inference.
You should check for it:
HeadCount = s.Reservations != null ? s.Reservations.Sum(r => r.PartySize) : 0,
This should resolve your problem:
Try to cost the int to int?
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = s.Reservations.Sum(r => (int?) r.PartySize),
s.MaxSeats
};
HeadCount = HeadCount ?? 0;
A simple ternary operator should fix the problem nicely...
something like this:
HeadCount = (s.Reservations != null && s.Reservations.Any()) ? s.Reservations.Sum(r => r.PartySize) : 0;
This will handle for both null and empty situations

Categories