How can I fix this LINQ function? - c#

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

Related

LINQ: Set a default value if navigation property is null

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.

Null value in the result of a left outer join linq causes error

I have linq query, that left outer join two tables. I found if a value of a field returns null,, then I will get an error message:
"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 copied my linq below:
var SrvRef = from s in db.SrvHeads
join r in db.Referrants on s.svhReferrer equals r.refID into r_join
from r in r_join.DefaultIfEmpty()
where s.svhAccID == accId &&
s.svhHeadCnt == HeadId
select new
{
s.svhBalance,
r.refID
};
bool FBeenPaid = SrvRef.FirstOrDefault().svhBalance == 0M; //this causes error
How can I fix this problem?
I'm slightly surprised at the kind of error you're getting, but there are two places you need to take account of the possibility of the result being null:
Within the query, where r can be null. (If you don't want to match when there are no elements in r_join matching s, you shouldn't be using a left outer join)
In the result itself: you're using FirstOrDefault() which will return null if SrvRef is empty.
So at first glance it should probably be something like:
var query = from s in db.SrvHeads
join r in db.Referrants on s.svhReferrer equals r.refID into r_join
from r in r_join.DefaultIfEmpty()
where s.svhAccID == accId && s.svhHeadCnt == HeadId
select new
{
s.svhBalance,
refId = r == null ? 0 : r.refID // Adjust to the appropriate type of refID
};
var result = query.FirstOrDefault();
bool beenPaid = result != null && result.svhBalance == 0m;
With C# 6, you can change the bottom two lines to:
bool beenPaid = query.FirstOrDefault()?.svhBalance == 0m ?? false;
Having said that:
You're not currently using refId in the result anyway - why are you including it in the result?
Are you sure you want a left outer join at all?
Are you sure that taking the first result is really what you want? What if there are multiple results in the join?
Is there any reason you're not doing the whole thing in a query? Something like:
var paid = db.SrvHeads
.Where(s => s.svhAccID == accId && s.svhHeadCnt == HeadId)
.Any(s => db.Refererrants.Any(r => s.svhReferrer == r.refID
&& s.svhBalance == 0m);
.. but just for the precise semantics you want.
I had a similar issue.
Cause: You are using from "r" in r_join.DefaultIfEmpty(). You cannot use same alias name for left outer join.
Solution: Use different alias name if DefaultIfEmpty() cases. Eg: rEmpty
I modified the below query and its working.
var SrvRef = from s in db.SrvHeads
join r in db.Referrants on s.svhReferrer equals r.refID into r_join
from rEmpty in r_join.DefaultIfEmpty()
where s.svhAccID == accId &&
s.svhHeadCnt == HeadId
select new
{
s.svhBalance,
refID = rEmpty == null ? 0 : rEmpty.refID
};
what i think is causing an error is that svhBalance is an int32 value type and you are accessing a null value returned by SrvRef.FirstOrDefault().
please try the following line and let me know if it helped you.
if svhBalance is an int value type
var SrvRefObj= SrvRef.FirstOrDefault(); bool FBeenPaid = (((SrvRefObj!=null)&&(SrvRefObj.svhBalance
!=null))?(SrvRefObj.svhBalance == 0):(false))
else if it's a decimal value type
var SrvRefObj= SrvRef.FirstOrDefault(); bool FBeenPaid = (((SrvRefObj!=null)&&(SrvRefObj.svhBalance
!=null))?(SrvRefObj.svhBalance == 0M):(false))

Comparing a fk_id with id in linq

Want to compare two ids, if they are the same take only these rows and send them so i can print them out.
public IList<tbl_user> getLastc()
{
var fk_id = (from m in dc.tbl_checkins orderby m.checkin_datetime descending select m.fk_user_id).Take(1);
var result = (from c in dc.tbl_users
where c.user_id.Equals(fk_id)
select c).ToList();
/*(from m in dc.tbl_users where m.user_id == fk_id select m).ToList();*/
return result;
}
error:
The comparison operators are not supported for the type
System.Linq.IQueryable1[System.Nullable1[System.Int32]].
In m.user_id == fk_id one of this variables is nullable int, so if you want to compare them do
m.user_id!=null && user_id.Value==fk_id
it will check first if value is not null, then compare it's value with other value. Nullable values have also a property HasValue you can use it to determine if variable has value:
m.user_id.HasValue && user_id.Value==fk_id
note: if fk_id is nullable int change it with m.user_id (error does not say which is nullable int)
EDIT:
this is how it will fit:
(from m in
dc.tbl_users where fk_id.HasValue && fk_id.Value==m.user_id select m).ToList()
EDIT2:
var fk_ids = (from m in dc.tbl_checkins orderby m.checkin_datetime descending select m.fk_user_id).Where(x=>x.HasValue).Select(x=>x.Value);
(from m in
dc.tbl_users where fk_ids.Contains(m.user_id) select m).ToList()

The property 'x' on 'tblX' could not be set to a 'null' value. You must set this property to a non-null value of type 'Int16'

List<tblX> messages = (from x in db.tblX where x.msg_id == id_id
|| x.name == firstName select x).ToList();
I get the error:
The property 'x' on 'tblX' could not be set to a 'null' value. You must set this property to a non-null value of type 'Int16'.
I have a property msg_blocked in db, which is nullable and integer. I know that I need to make a conversion, but I don't use it or need it anywhere in my linq.
Seems like your class definition for tblX doesn't match the database representation, so either modify your class to accept a nullable value, or just project out the required fields:
List<tblX> messages = (from x in db.tblX
where (x.msg_id == id_id || x.name == firstName)
select new tblX
{
//required fields
msg_id = x.msg_id,
name = x.name,
...
}).ToList();
Addendum: The reason you run into this problem is behind the scenes when you
select x
this is translated into a
select new tblX
which projects into all its available fields. The code provided is more explicit and specifies which fields to query for and then project into.
List<tblX> messages = (from x in db.tblX
where (x.msg_id == id_id || x.name == firstName) &&
x.msg_blocked != null
select x).ToList();

error in Linq query

I have following Linq:
var ownerRegistryId = 731752693037116688;
var excludeTypes = new[]
{
"CA00", "CA01", "CA03", "CA04", "CA02",
"PA00", "PA01", "PA02", "PA03", "PA04"
};
var maxStateChangeMonth = 4;
var excludeStatusId = 999;
var includeMortgage = new[] { "CL10", "CL11", "PL10", "PL11" };
var sum = (
from account in context.Accounts
from owner in account.AccountOwners
where owner.AccountOwnerRegistryId == ownerRegistryId
where !excludeTypes.Contains(account.AccountType)
where account.StateChangeDate == null ||
(account.StateChangeDate.Month - DateTime.Now.Month)
<= maxStateChangeMonth
where includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage")
where account.AccountStatusId != excludeStatusId
select account.MinimumInstallment)
.Sum(minimumInstallment => Math.Abs(minimumInstallment));
but I get the error:
The cast to value type 'Decimal'
failed because the materialized value
is null. Either the result type's
generic parameter or the query must
use a nullable type.
this error comes as soon as I add this:
where (includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage"))
If I remove this from above query, it works.
The query is translation of following SQL:
SELECT Sum(ABS([Minimum Installment])) AS SumOfMonthlyPayments FROM tblAccount
INNER JOIN tblAccountOwner ON tblAccount.[Creditor Registry ID] = tblAccountOwner.
[Creditor Registry ID] AND tblAccount.[Account No] = tblAccountOwner.[Account No]
WHERE (tblAccountOwner.[Account Owner Registry ID] = 731752693037116688)
AND (tblAccount.[Account Type] NOT IN
('CA00', 'CA01', 'CA03', 'CA04', 'CA02', 'PA00', 'PA01', 'PA02', 'PA03', 'PA04'))
AND (DATEDIFF(mm, tblAccount.[State Change Date], GETDATE()) <=
4 OR tblAccount.[State Change Date] IS NULL AND ((tblAccount.[Account Type] IN ('CL10','CL11','PL10','PL11')) OR
tblAccount.[Account Type] LIKE 'Mortgage')) AND (tblAccount.[Account Status ID] <> 999)
I'd try to rewrite the last two lines of your query like so:
var sum = (
...
select account)
.Sum(a => Math.Abs(a.MinimumInstallment));
That's how I interprete this part of the exception "...or the query must use a nullable type". By using the projection select account.MinimumInstallment you have a non-nullable type, namely decimal which is the type of account.MinimumInstallment.
Not sure though, just a guess.
Edit
The problem might actually be the final assignment var sum = .... Since you don't specify the result type explicitely the compiler will here infer the type to decimal because MinimumInstallment is decimal. The query can actually return null when the selected recordset was empty so the cast to decimal is impossible.
So, let's help the compiler to infer the result type of the query to decimal?:
var sum = (decimal?)(from ... ) ?? 0;
(Replace from ... by your original query or maybe by my modified version above.)
Edit 2
OK, the first Edit didn't work (according to comment in another question). Indeed I could reproduce the issue in a similar example. But the following worked in my example:
var sum = (
...
select account)
.Sum(a => (decimal?)Math.Abs(a.MinimumInstallment))
.GetDefaultOrValue();
Try using:
Math.Abs((decimal)(minimumInstallment.HasValue ? minimumInstallment : 0));
How about:
Math.Abs((decimal)(minimumInstallment!= null ? minimumInstallment : 0));

Categories