Why is the sum of an empty set null? - linq

If I do:
int updateGamePlays = db.tblArcadeGames.Where(c => c.ParentGameID == GameID).Sum(c => c.Plays);
If no records are returned in this query it throws:
System.InvalidOperationException: The null value cannot be assigned to
a member with type System.Int32 which is a non-nullable value type.
The only way to get it to return 0 is by doing:
int updateGamePlays = db.tblArcadeGames.Where(c => c.ParentGameID == GameID).Sum(c => (int?)c.Plays) ?? 0;
In the database c.Plays is a non-nullable int.
In set theory the sum of an empty set should equal 0 (ref). How comes in Linq-to-SQL did they decide to make it return null?

According to a source at Microsoft, Sum() on an empty set is null because of the way it works in SQL:
when the table is empty i´m getting this exception: InvalidOperationException
In SQL, Sum() aggregate operator returns null for an empty set. So this is as designed.

Another alternative is to add a 0 to the set to make sure there's always at least one value.
int updateGamePlays = db.tblArcadeGames.Where(c => c.ParentGameID == GameID)
.Select(c => c.Plays)
.Concat(new [] { 0 })
.Sum();

You can use the more general Aggregate method with a seed of zero:
int updateGamePlays = db.tblArcadeGames
.Where(c => c.ParentGameID == GameID)
.Aggregate(0, (a, c) => a + c.Plays);
This does not require using nullable types.

Related

Entity Framework: Any or All - Unable to create a constant value of type 'System.Collections.Generic.List`1'

I am trying to do something like this:
from t in ent.myEntities
where SelectedProperties == null || SelectedProperties.Any(le => le == t.Entity)
select t
basically trying to cover 2 cases. accepting an empty list, should return all entities, or filter on the list if it is supplied.
above actually does work when i supply the list, however in the case when it is null i get:
Unable to create a constant value of type
'System.Collections.Generic.List`1'. Only primitive types ('such as
Int32, String, and Guid') are supported in this context
also tried using this with a string array:
where arr == null || arr.Contains(t.Entity)
is it possible to have such a condition without having to build a predicate (which is a bigger effort)?
You might want to try using the list in a simpler way:
where SelectedProperties == null || SelectedProperties.Contains(t.Entity)
It may well not work, but it's worth a try. Otherwise, if this is really your whole query, I'd just write it as:
var query = SelectedProperties == null
? ent.myEntities
: ent.myEntities.Where(t => SelectedProperties.Contains(t.Entity));
EDIT: Okay, if you have to use Any, and have lots of these to compose, you can do it like this:
var query = ent.myEntities;
if (SelectedProperties != null)
{
query = query.Where(t => SelectedProperties.Any(x => x == t.Entity));
}
if (SomethingElse)
{
query = query.Where(...);
}
// etc
I'm using EF5, something like this will fix the issue:
ent.myEntities.ToList().Where(t => SelectedProperties == null || SelectedProperties.Contains(t.Entity));

Linq 2 SQL Sum using lambda and handling nulls

When using sum with lambda in Linq to SQL using the following code:
int query = (from f in odc.RDetails
where f.ticketID == int.Parse(ticket.ToString())
select f).Sum(x => x.Rate);
I get the following error:
The null value cannot be assigned to a member with type System.Int32 which is a non-nullable value type.
. You have to make sure x.Rate is an int, and not an int? (an int that accepts null as a value).
. If the query has no elements, .Sum won't do anything and will return null. Choose a default value, let's say 0.
var query = from f in odc.RDetails
where f.ticketID == int.Parse(ticket.ToString())
select f;
int result = query.Any()
? query.Sum(x => x.Rate ?? 0) // use the ?? if x.Rate is an "int?".
: 0; // default value you can choose.
I would break the int.Parse(ticket.ToString()) onto its own line to isolate that parse from the Linq for debugging.
We don't know whether that is throwing the exception or if one of the RDetails.Rate values is null. Is it indeed a Nullable<int>?
If RDetails.Rate is a Nullable<int>, then you could ...Sum(x => x.Rate ?? 0) and avoid the exception.

LINQ read Select values

I have a LINQ query where I want to select and read the p.Api value.
var api = DataAccessNew.Instance.dcServers.Where(p => p.Ip == IpAddress).Select(p => p.Api);
How do I read the p.Api value?
I have tried api.ToString() but I get SQL instead of actual column value.
You are getting an IEnumerable<> back (and your ToString call is showing you the value of that expression).
If you are expecting a single value, do this:
var api = DataAccessNew.Instance.dcServers
.Where(p => p.Ip == IpAddress)
.Select(p => p.Api)
.Single();
You might be interested to read about the other methods like Single(): SingleOrDefault, First, FirstOrDefault. Which one you used depends on whether you are expecting a single or multiple values returned (Single vs. First) and what you want to happen if there are no values (the *Default methods will return the type default instead of throwing an exception).
Or if you want to look at all the returned values:
var api = DataAccessNew.Instance.dcServers
.Where(p => p.Ip == IpAddress)
.Select(p => p.Api);
foreach (var apiValue in api)
{
// apiValue will have the value you're looking for.
}
Try this snippet of code:
string apiValue = api.FirstOrDefault().ToString();
your syntex seems ok..
By the way try this
string api =DataAccessNew.Instance.dcServers.Where(p => p.Ip == IpAddress).Select(p => p.Api).FirstOrDefault();
if p.Ip is a unique key in your table you could try to add .FirstOrDefault() after your Linq query.
public string getselectedvalue(ListBox l)
{
string vtext="",vval="";
var selectedQueryText = l.Items.Cast<ListItem>().Where(item => item.Selected);
var selectedQueryVal = l.Items.Cast<ListItem>().Where(item => item.Selected).Select(item => item.Value);
vtext= String.Join("','", selectedQueryText ).TrimEnd();
vval= String.Join("','", selectedQueryVal ).TrimEnd();
return v;
}

Linq error generic parameter or the query must use a nullable type

I got this error when i use sum function in LINQ:
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.
GroupProduct.Where(a => a.Product.ProductID==1).Sum(Content => Content.Amount==null?0:Content.Amount),
This is what I usually use. This will cover the possibility of Amount being null and also cover the possibility of an empty set.
GroupProduct.Where(a => a.Product.ProductID == 1)
.Select(c => c.Amount ?? 0) // select only the amount field
.DefaultIfEmpty() // if selection result is empty, return the default value
.Sum(c => c)
DefaultIfEmpty() returns the default value associated with Amount's type, which is int, in which case the default value is 0.
Did you try the following:
GroupProduct.Where(a => a.Product.ProductID==1).Sum(Content => (decimal?)Content.Amount)
The code from my application looks like:
var data = query.AsEnumerable().Select(row => TaskInfo.FetchTaskInfo(row,
ctx.ObjectContext.Hours.Where(hour => hour.TaskId == row.TaskId).Sum(hour => (decimal?)hour.Duration),
ctx.ObjectContext.Notes.Count(note => note.SourceType == (int)SourceType.Task && note.SourceId == row.TaskId)));
You could exclude at source?
var sum = GroupProduct.Where(a => a.Product.ProductID==1 && a.Amount != null)
.Sum(a => (decimal)a.Amount);
Try this:
var sum = GroupProduct.Where(a => a.Product.ProductID==1).Sum(Content => (int?) Content.Amount);
sum = sum ?? 0;
This looks like it should work (and usually does) but fails when the Where() method returns null:
decimal sum1 = GroupProduct
.Where(a => a.Product.ProductID == 1)
.Sum(c => c.Amount ?? 0);
The error: "The cast to value type 'Decimal' failed because the materialized value is null" is due to the Sum() method returning null (not zero) when summing over an empty set.
Either of these work for me:
decimal? sum2 = GroupProduct
.Where(a => a.Product.ProductID == 1)
.Sum(c => c.Amount);
decimal sum3 = GroupProduct
.Where(a => a.Product.ProductID == 1)
.Sum(c => c.Amount) ?? 0;

How to make a linq Sum return null if the summed values are all null

I have a LINQ query that looks like this...
var duration = Level3Data.AsQueryable().Sum(d => d.DurationMonths);
If all the d.DurationMonths values are null the Sum returns 0. How can I make the Sum return null if all the d.DurationMonths are null? Or do I need to run a separate query first to eliminate this situation before performing the sum?
Along with the previous suggestion for an extension method - you could use a ternary operator...
var duration = Level3Data.AsQueryable().Any(d => d.DurationMonths.HasValue)
? Level3Data.AsQueryable().Sum(d => d.DurationMonths)
: null;
You can use Aggregate to provide custom aggregation code :
var items = Level3Data.AsQueryable();
var duration = items.Aggregate<D,int?>(null, (s, d) => (s == null) ? d.DurationMonths : s + (d.DurationMonths ?? 0));
(assuming the items in Level3Data are of type D)
var outputIndicatorSum = (from OutputIndicatorTable in objDataBaseContext.Output_Indicators
where OutputIndicatorTable.Output_Id == outputId
select (int?)OutputIndicatorTable.Status).Sum();
int outputIndicatorSumReturn = Convert.ToInt32(outputIndicatorSum);
return outputIndicatorSumReturn;
You can explicitly type cast non-nullable varaible into nullable type.
i.e, select (int?)OutputIndicatorTable.Status).Sum();
Using Sum alone, this is impossible. As you indicated in your question, you will need to check for this situation before you call Sum:
var q = Level3Data.AsQueryable();
var duration = q.All(d => d.DurationMonths == null)
? null
: q.Sum(d => d.DurationMonths);
If you would like the result without two queries try:
var duration = Level3Data.AsQueryable().Sum(d => (double?)d.DurationMonths);
If you want zero instead of null as the result of this query use:
var duration = Level3Data.AsQueryable().Sum(d => (double?)d.DurationMonths) ?? 0;

Resources