I am trying to figure out how to handle nodes that do not exist for all of my "card" elements. I have the following linq query:
FinalDeck = (from deck in xmlDoc.Root.Element("Cards")
.Elements("Card")
select new CardDeck
{
Name = deck.Attribute("name").Value,
Image = deck.Element("Image").Attribute("path").Value,
Usage = (int)deck.Element("Usage"),
Type = deck.Element("Type").Value,
Strength = (int)deck.Element("Ability") ?? 0
}).ToList();
with the Strength item, I had read another posting that the ?? handles the null. I am getting the following error though:
Operator '??' cannot be applied to operands of type 'int' and 'int'
How do I handle this issue?
Thanks!
Rather than use the Value property, cast to string... and for the int, cast to int? instead. The user defined conversions to nullable types will return null if the source XAttribute/XElement is null:
FinalDeck = (from deck in xmlDoc.Root.Element("Cards")
.Elements("Card")
select new CardDeck
{
Name = (string) deck.Attribute("name"),
Image = (string) deck.Element("Image").Attribute("path"),
Usage = (int?) deck.Element("Usage"),
Type = (string) deck.Element("Type"),
Strength = (int?) deck.Element("Ability") ?? 0
}).ToList();
Note that this won't help for the case where the Image element is missing, as then it'll try to dereference a null element to find the path attribute. Let me know if you want a workaround for that, but it'll be a bit of a pain, relatively speaking.
EDIT: You can always create an extension method for this yourself:
public static XAttribute NullSafeAttribute(this XElement element, XName name)
{
return element == null ? null : element.Attribute(name);
}
Then call it like this:
Image = (string) deck.Element("Image").NullSafeAttribute("path"),
Related
I have an XML file like this:-
Notice that each <Field></Field> can have different element like the highlighted <I32> or <String>. I want to show the element name in a datagridview like this which Type is for the name of element (either I32 or String or other child element of <Field>) :-
So far, I've tried this code but it return An unhandled exception of type 'System.NullReferenceException'.
XDocument doc = XDocument.Load("GetLotDetails.xml");
var data = doc.Descendants("Document").Where(x => (String)x.Attribute("name") == "DATA").SelectMany(x => x.Elements("Field"));
var query = from d in data
let str = d.Element("String").Name
let other = d.Element("I32").Name
select new
{
Name = d.Attribute("name").Value,
Type = str.Equals("String") ? "String" : (other.Equals("I32") ? "I32" : null),
Value = d.Value,
};
dataGridView1.DataSource = query.ToList();
So the idea is to let the anonymous Type = *whatever element name under field*. How can I extracted different name of element in the LINQ select statement and give it to the same unknown type variable?
It has nothing to do with anonymous types. You are missing a null check
var query =
from d in data
let element = d.Element("String") ?? d.Element("I32")
select new
{
Name = d.Attribute("name").Value,
Type = element?.Name,
d.Value
};
In your original query, you unconditionally read the Name from both possible nodes, but for any given d, one of the nodes will be null. I could have written it using the null conditional operator, d.Element("String")?.Name, but the above is more readable in this context as the additional projection in your original query adds noise and potential confusion.
Well, using .NET 3.5 and XDocument I am trying to find <table class='imgcr'> element. I created the code below but it crashes, of course, because e.Attribute("class") may be null. So... I have to put null check everywhere? This will double e.Attribute("class"). Not laconic solution at all.
XElement table =
d.Descendants("table").
SingleOrDefault(e => e.Attribute("class").Value == "imgcr");
If you are sure you exception is thrown because you table element may come without class attribute, then you could do this instead:
XElement table =
d.Descendants("table").
SingleOrDefault(e => ((string)e.Attribute("class")) == "imgcr");
In that case you are casting a null value to string, which is null at the end, so you are comparing null == "imgcr", what is false.
You can check this msdn page if you need more info about how to retrieve the value of an attribute. There you will find this affirmation:
You can cast an XAttribute to the desired type; the explicit
conversion operator then converts the contents of the element or
attribute to the specified type.
I guess this is quite short
XElement table =
d.Descendants("table").
SingleOrDefault(e => { var x = e.Attribute("class"); return x==null ? false: x.Value == "imgcr";});
this is shorter (but not much -- unless you can re-use t variable.)
XAttribute t = new XAttribute("class","");
XElement table =
d.Descendants("table").
SingleOrDefault(e => (e.Attribute("class") ?? t).Value == "imgcr");
I have the following code:
// TryGetAttributeValue returns string value or null if attribute not found
var attribute = element.TryGetAttributeValue("bgimage");
// Convert attribute to int if not null
if (attribute != null) BgImage = convert.ToInt32(attribute);
The thing I don't like is that I have to create a temp variable, attribute, in order to test if it's null or not, and then assign the value to the BgImage variable, which is a nullable int.
I was hoping I could figure out a way to write it all on one line, but I cannot figure a way. I even tried using a ternary statement, but got nowhere:
if (element.TryGetAttributeValue("bgimage") != null) ? BgImage = //Convert result to int : else null;
Realistically, my original two lines of code do the job. I was just hoping to pare it down to one line. But, if anyone knows how to do what I'm trying to accomplish, I'd love to learn how.
I recommend you to use Linq to Xml for parsing Xml (according to your attempt you have BgImage as nullable integer):
BgImage = (int?)element.Attribute("bgimage");
You also can assign some default value if BgImage is not nullable:
BgImage = (int?)element.Attribute("bgimage") ?? 0;
Assuming TryGetAttributeValue returns a string you could do something like
BgImage = convert.ToInt32(element.TryGetAttributeValue("bgimage") ?? "-1")
This would set BgImage to a default value (-1) if the attribute does not exist. If you would prefer to have BgImage set to null when there is no bgimage attribute then it gets a little bit clunkier
BgImage = element.TryGetAttributeValue("bgimage") != null ?
convert.ToInt32(element.TryGetAttributeValue("bgimage")) : (int?)null;
I am getting the error:
Input string was not in a correct format.
Some of the Parent Id's can be null. If I comment the ParentId it is successful, so I know it is that line.
var mModel = new MyModel
{
//non nullable
Id = int.Parse(dr["Id"].ToString()),
//nullable
ParentId = int.Parse(dr["ParentId"].ToString()), <--- Throwing the error here
//non nullable
ProductService = dr["MyString"].ToString()
};
I have tried Convert.ToInt32(), and TryParse() and event a ToNullableInt extension Method, but that didnt work.
Check for database null in the dr["ParentId"] and then convert to an int or assign null, like this:
ParentId = !dr.IsDBNull(dr.GetOrdinal("ParentId"))
? dr.GetInt32(dr.GetOrdinal("ParentId"))
: null;
Note: The above code assumes that the ParentId variable is a nullable integer (int? or Nullable<int>.
I don't like calling to string on datareader, moreover, it has methods to get data of specific type
Pattern is
if (!dr.IsDBNull(1)) s = dr.GetString(1)
Or for nullable int:
if (!dr.IsDBNull(1)) i = dr.GetInt32(1)
Note: if you know column name and don't know ordinal or vise-a-versa, you can use GetOrdinal and
GetName methods:
var i = dr.GetOrdinal("ParentId")
var name = dr.GetName(1)
You shouldn't use int.Parse on a value that will not parse correctly... it'll throw an exception.
If you're sure the value will be null or have a valid value, you could try this:
ParentId = String.IsNullOrEmpty(Convert.ToString(dr["ParentId"]))
? (int?)null
: int.Parse(dr["ParentId"].ToString());
You may also want to look into Int32.TryParse, which allows you to try parsing and take some alternative action if the parse fails:
int id;
if (Int32.TryParse(Convert.ToString(dr["ParentId"]), out id)
ParentId = id;
else
ParentId = 0; // the parse failed
Incidentally, I like using Convert.ToString() over .ToString() in most cases, as the former converts null to an empty string, while the latter throws an exception. Just something to consider - doesn't help in this case, as int.Parse("") will throw an exception anyway.
I am trying to prevent having NULL values when I parse an XML file to a custom object using LINQ.
I found a great solution for this on Scott Gu's blog, but for some reason it does not work for integers with me. I think I have used the same syntax but it seems I am missing something. Oh and for some reason it works when the node is not empty.
Below is an extract of my code.
List<GrantAgresso> lsResult = (from g in xml.Element("root").Elements("Elementname")
select new GrantAgresso()
{
Year = (int?)g.Element("yearnode") ?? 0,
Subdomain = (string)g.Element("domainnode") ?? ""
}).ToList();
The errormessage is:
Input string was not in a correct format.
If anyone has a clue as to what I'm doing wrong, please help :)
Edit: piece of XML (strange names but it's not by choice)
<Agresso>
<AgressoQE>
<r3dim_value>2012</r3dim_value>
<r0r0r0dim_value>L5</r0r0r0dim_value>
<r7_x0023_province_x0023_69_x0023_V005>0</r7_x0023_province_x0023_69_x0023_V005>
<r7_x0023_postal_code_x0023_68_x0023_V004 />
<r7_x0023_country_x0023_67_x0023_V003>1004</r7_x0023_country_x0023_67_x0023_V003>
<r7_x0023_communitydistrict_x0023_70_x0023_V006>0</r7_x0023_communitydistrict_x0023_70_x0023_V006>
</AgressoQE>
</Agresso>
It is this expression which is throwing:
(int?)g.Element("yearnode")
That's because if the actual value of the element's text node is String.Empty and not null, since the empty string is not a valid format for Int32.Parse, the attempted cast fails.
If the element is missing completely from your XML, this works as you expect, but if there is an empty tag <yearnode/> or <yearnode></yearnode>, you'll get the exception.
The following extension method will return 0 both if the element is not present, the element is empty or it contains a string that cannot be parsed to integer:
public static int ToInt(this XElement x, string name)
{
int value;
XElement e = x.Element(name);
if (e == null)
return 0;
else if (int.TryParse(e.Value, out value))
return value;
else return 0;
}
You could use it like this:
...
Year = g.ToInt("r3dim_value"),
...
Or if you're ready to consider the cost of reflection and to accept the default value of any value type, you may use this extension method:
public static T Cast<T>(this XElement x, string name) where T : struct
{
XElement e = x.Element(name);
if (e == null)
return default(T);
else
{
Type t = typeof(T);
MethodInfo mi = t.GetMethod("TryParse",
BindingFlags.Public | BindingFlags.Static,
Type.DefaultBinder,
new Type[] { typeof(string),
t.MakeByRefType() },
null);
var paramList = new object[] { e.Value, null };
mi.Invoke(null, paramList);
return (T)paramList[1]; //returns default(T), if couldn't parse
}
}
and use it:
...
Year = g.Cast<int>("r3dim_value"),
...
You can add Where operator
.....
.Where(a => ! string.IsNullOrEmpty(a)).ToList();
If year is null or empty string that you will get an "Input string was not in a correct format" exception. You may write an extension method to read values. I haven't tested code below, but it may give you some hints.
public static ReadAs<T>(this XElement el, T defaultValue) {
var v = (string)el; // cast string to see if it is empty
if (string.IsNullOrEmpty(v)) // test
return defaultValue;
return (T)el; // recast to our type.
}
The message Input string was not in a correct format looks like the one thrown by int.parse () so it could be that you have a yearnode with a value (not null) but which cannot be successfully parsed to an integer value.
Something like this may fix it:
List<GrantAgresso> lsResult = (from g in xml.Element("root").Elements("Elementname")
let yearNode = g.Element("yearnode")
select new GrantAgresso
{
Year = string.IsNullOrWhiteSpace(yearNode.Value) ? 0 : int.Parse(yearNode.Value),
Subdomain = g.Element("domainnode").Value
}).ToList();
A couple of things to note:
select new GrantAgresso - you don't need parenthesis for a default constructor with object initializers.
string.IsNullOrWhiteSpace - was introduced in .net 4.0, use string.IsNullOrEmpty if you're on 3.5 or earlier
g.Element("domainnode").Value - will always return a string
if you want a null for Year instead of 0, use (int?)null instead of 0
Your problem is that the cast from XElement to int? uses int.Parse method on the string value of the XElement, which in your case is String.Empty. The following results in the same error:
XElement x = new XElement("yearnode", String.Empty);
int? a = (int?)x; // throws FormatException
You can avoid this by first casting the XElement to string and checking if it is null or empty and only if not doing the cast to int?. To do this, replace
Year = (int?)g.Element("yearnode") ?? 0,
with
Year = !string.IsNullOrEmpty((string)g.Element("yearnode")) ? (int?)g.Element("yearnode") : 0,
Its not pretty and will still throw if the string is otherwise not legal, but it works if you can assume that the element is always an integer or null/empty.