Issue with checking nullable xml values when converting to linq - c#

I'm trying to convert an xml feed to linq, but I'm having trouble checking if an element is null or not before retrieving that element's data. I was convinced my following code is correct, but one of the xml's rows is missing the element "ElementOne" and it's child "Element", so it's throwing the whole script when executed.
This snippet is incomplete but this is the part that's causing the whole script to error, can anyone spot why this would error?
from c in s.Element("ElementOne").Elements("ElementTwo")
where s.Element("ElementOne") != null &&
s.Element("ElementOne").Elements("ElementTwo") != null
select new{
Id = (c.Attribute("Id") == null) ? 0 : (int)c.Attribute("Id")
}

I would just change it to:
var query = s.Elements("ElementOne").Elements("ElementTwo")
.Select(c => (int?) c.Attribute("Id") ?? 0);
(Unless you really need the anonymous type, why bother with it?)
If there are no ElementOne elements, this will just give you an empty result.
If you want to only use the first ElementOne element even if there are multiple ones, you can use:
var query = s.Elements("ElementOne").Take(1).Elements("ElementTwo")
.Select(c => (int?) c.Attribute("Id") ?? 0);

Related

How to check if IQueryable element/ item contains a property in C#?

I have an IQueryable as follows
var issues = from i in jira.Issues.Queryable
where i.Project == project.Key
orderby i.Created
select i;
I am trying to get the count where issue type is Bug like this:
TotalIssues = issues.ToList().Where(i => i.Type.Name == "Bug").Count();
The problem here is there are some issues without a type. How can I check if the issue type exists?
You can try null propagation ?. in order to have null for Name if any item or item.Type are null:
TotalIssues = issues
.AsEnumerable() // Linq to objects from now on; no need in materialization
.Count(item => "Bug".Equals(item?.Type?.Name)); // just count
You don't want issues without type. You can keep code simple by not taking issues without type into account.
var bugsCount = issues.AsEnumerable()
.Where(issue => issue.Type != null)
.Count(issue => issue.Type.Name == "Bug");
You can chain as much .Where clauses as you wish, items will be still enumerated only once.
What do you mean? Some issues don't have Type Property? Or Some issues's property Type is null?
If it is some issues don't have Type Property, I think you can specify issues's class or interface. Or you can use reflection.
If it is some issues's property Type is null, you can check Type is not null first.
From your comment, I think i may be null too.
i!=null && i.Type!=null && i.Type.Name=="Bug"

How can I make Sum() return 0 instead of 'null'?

I'm trying to use LINQ-to-entities to query my DB, where I have 3 tables: Room, Conference, and Participant. Each room has many conferences, and each conference has many participants. For each room, I'm trying to get a count of its conferences, and a sum of all of the participants for all of the room's conferences. Here's my query:
var roomsData = context.Rooms
.GroupJoin(
context.Conferences
.GroupJoin(
context.Participants,
conf => conf.Id,
part => part.ConferenceId,
(conf, parts) => new { Conference = conf, ParticipantCount = parts.Count() }
),
rm => rm.Id,
data => data.Conference.RoomId,
(rm, confData) => new {
Room = rm,
ConferenceCount = confData.Count(),
ParticipantCount = confData.Sum(cd => cd.ParticipantCount)
}
);
When I try and turn this into a list, I get the error:
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 can fix this by changing the Sum line to:
ParticipantCount = confData.Count() == 0 ? 0 : confData.Sum(cd => cd.ParticipantCount)
But the trouble is that this seems to generate a more complex query and add 100ms onto the query time. Is there a better way for me to tell EF that when it is summing ParticipantCount, an empty list for confData should just mean zero, rather than throwing an exception? The annoying thing is that this error only happens with EF; if I create an empty in-memory List<int> and call Sum() on that, it gives me zero, rather than throwing an exception!
You may use the null coalescing operator ?? as:
confData.Sum(cd => cd.ParticipantCount ?? 0)
I made it work by changing the Sum line to:
ParticipantCount = (int?)confData.Sum(cd => cd.ParticipantCount)
Confusingly, it seems that even though IntelliSense tells me that the int overload for Sum() is getting used, at runtime it is actually using the int? overload because the confData list might be empty. If I explicitly tell it the return type is int? it returns null for the empty list entries, and I can later null-coalesce the nulls to zero.
Use Enumerable.DefaultIfEmpty:
ParticipantCount = confData.DefaultIfEmpty().Sum(cd => cd.ParticipantCount)
Instead of trying to get EF to generate a SQL query that returns 0 instead of null, you change this as you process the query results on the client-side like this:
var results = from r in roomsData.AsEnumerable()
select new
{
r.Room,
r.ConferenceCount,
ParticipantCount = r.ParticipantCount ?? 0
};
The AsEnumerable() forces the SQL query to be evaluated and the subsequent query operators are client-side LINQ-to-Objects.

Linq sorting with nullable object

could you please help me!
I have object list like:
item[0].title = apple
item[0].data.weight = 1
item[1].title = lemon
item[1].data = null
item[2].title = melon
item[2].data.weight = 3
I would like to sort it (ASC and DESC) by weight with null data.
I tried like this:
item.OrderBy(x => x.data == null).ThenBy(x => x.data.weight); // failed
item.Where(x => x.data != null).OrderBy(x => x.data.weight); // ok, but as result only two records
So how i can sort items and receive all results.
ASC at first should be data with null.
DESC at first data with max weight and null at the end of the list.
item.OrderBy(x => x.data == null).ThenByDescending(x => x.data == null ? 0 : x.data.weight);
I am assuming weight is an int, otherwise provide the default value based on type.
Given you're only shipping fruit, and not, say, light, you can treat items having null data as having weight 0. Alternatively, just pick any value that's lower than the possible, valid values in order to put the null items at the top when sorting ascendingly.
You can express that like this:
var ordered = item.OrderBy(x => x.data == null ? 0 : x.data.weight);
You could use something like this: (assuming C# 6 or above)
item.OrderBy(x => x.data?.weight ?? int.MinValue);
This makes use of the new C#6 null-conditional and null-coalescing operators - if you need something applicable in lower versions of C#, you can use a ternary operator, like this:
item.OrderBy(x => x.data != null ? x.data.weight : int.MinValue);
If it's possible that you could have x.data.weight being int.MinValue, then you would need to do something like what you were doing before, but the second linq method should make use of the above lambda/s.
You can do this a few ways, one way would be to have a value replacing the null values using ternary conditional operator on the order by or filtering out the items without a value and concatenating them to the enumerable after you've sorted the objects with values.
By conditionally providing a value for items with null
This is, in my opinion, the best way, and it performs better. You only enumerate over the collection once, versus the other method where you enumerate to determine if each element has a value then order, and then check for the items without a value
item.OrderBy(x => x.data != null ? x.data.weight : int.MinValue)
Filtering and then concatenating the items without a value
There are times where this could possibly be the better solution. One example would be if you want to use a different method for ordering the values when they are missing the property you are looking for.
item.Where(x => x.data != null)
.OrderBy(x => x.data.weight)
.Concat(item.Where(a=>a.data == null))

String.IsNullOrEmpty in LINQ To SQL query?

My DBML exposes a record set that has a nullable nvarchar field. This nullable nvarchar field is represented as a string in my C# code.
Sometimes this field is null, sometimes it is an empty string, and sometimes it actually has a value.
Does String.IsNullOrEmpty() work in LINQ To SQL? For instance, would the following work:
var results = from result in context.Records
where String.IsNullOrEmpty(result.Info) == false
select result;
Curiously, per MSDN String.IsNullOrEmpty is supported (by virtue of it not being unsupported), yet I can only find complaints about it not being supported.
However, if it does work you should not explicitly compare it to a boolean value, instead:
var results = from result in context.Records
/*XXX broke :( where !String.IsNullOrEmpty(result.Info) */
where !(result.Info == null || result.Info.Equals(""))
select result;
I don't know if that works, but I'm sure this does:
where (result.Info ?? "") != ""
(strongly recommend the parens, query generator can get confused without them)
It is not supported since attempting to use it results in a NotSupportedException being thrown with this message:
Method 'Boolean IsNullOrEmpty(System.String)' has no supported
translation to SQL.
Instead, you can use this approach to do the same thing:
var results = from result in context.Records
where result.Info != null && result.Info.Length > 0
select result;
You may also use result.Info != String.Empty instead of checking the length. Both approaches will work.
I had problems with all answers except for #ahmad-mageed's answer.
Ended up using a more concise syntax of:
where (result.Info ?? "").Length > 0
Or
result => (result.Info ?? "").Length > 0
You can use a function as argument to the Where method if you use a Linq query, e.g.
var results = context.Records.Where(string.IsNullOrEmpty);
But in this case that would give you all null or empty elements, instead of the opposite. Then create an extension method to the string class (e.g. string.IsNotNullOrEmpty) or do something like this:
var results = context.Records.Except(context.Records.Where(string.IsNullOrEmpty));

Filter a query using value of XElement within XML using LINQ C#

Using LINQ C#, I am querying a database table. The table has a column which contains XML and I would like to filter the results based on an element's value inside the XML being equal to a predefined string. I am using the following query:
(from data in DataObjects.Items
where data.DataItemTimeUtc < DateTime.UtcNow &&
data.DataItemXml.XDocument
.Descendants("Items")
.Descendants("MetaData")
.Descendants("Device")
.First().Value == "abc123"
orderby data.DataItemTimeUtc descending
select data).ToArray();
but it is failing with an error:
The expression of type 'System.Collections.Generic.IEnumerable`1[System.Xml.Linq.XElement]'
is not a sequence
What about this?
var x = DataObjects.Items
.Where(data => data.DataItemTimeUtc < DateTime.UtcNow
&& data.DataItemXml.XDocument
.Elements("Items")
.Elements("MetaData")
.Elements("Device")
.First().Value == "abc123")
.OrderBy(data => data.DataItemTimeUtc);
I've just tried to remove your syntax errors, changed Descendants to Elements which takes only immediate children. Then I changed it to Method Syntax instead of Expression Syntax to keep things together that belong together. You have to be aware though that the .First() might not result any element and then the .Value will throw a null reference exception.

Categories