In my current Silverlight3 project at work, I'm retrieving a string of XML data from a database via stored procedure. An object is initialized with this XML data via an Element
property (of type XElement
), and the following code is executed:
public XElement Element
{
set
{
this.MyVar1 = value.Element("MyVar1").Value;
this.MyVar2 = value.Element("MyVar2").Value;
}
}
I discovered that when a database column contains NULL
as its value, and the stored procedure will not return the desired/expected XML child element for the row, the code shown above will throw an exception. To combat this, I've written the following extension methods. These methods allow me to "adjust" the data on the fly so that:
a) There is always valid data in my object
b) Avoids the inevitable exception in the event that an element is missing.
Here's the code:
public static class ExtensionMethods
{
public static bool Contains(this XElement root, string name)
{
bool contains = false;
foreach (XElement element in root.Elements())
{
if (element.Name == name)
{
contains = true;
break;
}
}
return contains;
}
public static string GetValue(this XElement root, string name, string defaultValue)
{
string value = defaultValue;
if (root.Contains(name))
{
value = root.Element(name).Value;
}
return value;
}
}
Now, I can use this code to set the property, and I can stop worrying about the possibility of missing columns:
public XElement Element
{
set
{
this.MyVar1 = value.GetValue("MyVar1", "NO VALUE");
this.MyVar2 = value.GetValue("MyVar2", "NOVAL");
}
}
Using extension methods allowed me to add functionality and avoid exceptions without adding complexity to the programmer-facing code. Very cool.
EDIT ----------
Fixed some spelling errors, and reworded the descriptive text a bit.
EDIT (01/21/2011) ===============
I decided I should probably post ALL of the methods I use. Since I originally posted this tip, I've added overloads for GetValue
, and added GetAttribute
(and its overloads). The overloads were implemented to avoid the casting that was necessary where the method was being called. The ones that are shown below fill my own requirements, and you can, of course, add more as your needs evolve:
public static string GetValue(this XElement root, string name, string defaultValue)
{
return (string)root.Elements(name).FirstOrDefault() ?? defaultValue;
}
public static double GetValue(this XElement root, string name, double defaultValue)
{
string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
double value;
if (!double.TryParse(strValue, out value))
{
throw new Exception(string.Format("Element {0}: Value retrieved was not a valid double", name));
}
return value;
}
public static decimal GetValue(this XElement root, string name, decimal defaultValue)
{
string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
decimal value;
if (!decimal.TryParse(strValue, out value))
{
throw new Exception(string.Format("Element {0}: Value retrieved was not a valid decimal", name));
}
return value;
}
public static Int32 GetValue(this XElement root, string name, Int32 defaultValue)
{
string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
Int32 value;
if (!Int32.TryParse(strValue, out value))
{
throw new Exception(string.Format("Element {0}: Value retrieved was not a valid 32-bit integer", name));
}
return value;
}
public static bool GetValue(this XElement root, string name, bool defaultValue)
{
string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
bool value;
if (!bool.TryParse(strValue, out value))
{
throw new Exception(string.Format("Element {0}: Value retrieved was not a valid boolean", name));
}
return value;
}
public static DateTime GetValue(this XElement root, string name, DateTime defaultValue)
{
string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
DateTime value;
if (!DateTime.TryParse(strValue, out value))
{
throw new Exception(string.Format("Element {0}: Value retrieved was not a valid DateTime", name));
}
return value;
}
public static string GetAttribute(this XElement root, string name, string defaultValue)
{
return (string)root.Attributes(name).FirstOrDefault() ?? defaultValue;
}
public static double GetAttribute(this XElement root, string name, double defaultValue)
{
string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
double value;
if (!double.TryParse(strValue, out value))
{
throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid double", name));
}
return value;
}
public static decimal GetAttribute(this XElement root, string name, decimal defaultValue)
{
string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
decimal value;
if (!decimal.TryParse(strValue, out value))
{
throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid decimal", name));
}
return value;
}
public static Int32 GetAttribute(this XElement root, string name, Int32 defaultValue)
{
string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
Int32 value;
if (!Int32.TryParse(strValue, out value))
{
throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid 32-bit integer", name));
}
return value;
}
public static bool GetAttribute(this XElement root, string name, bool defaultValue)
{
string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
bool value;
if (!bool.TryParse(strValue, out value))
{
throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid boolean", name));
}
return value;
}
public static DateTime GetAttribute(this XElement root, string name, DateTime defaultValue)
{
string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
strValue = strValue.ToUpper().Replace("AS OF:", "").Trim();
DateTime value;
if (!DateTime.TryParse(strValue, out value))
{
throw new Exception(string.Format("Attribute {0}: Value retrieved was not a valid DateTime", name));
}
return value;
}
EDIT (07/07/2014) ======================
I've never really been happy with this code, so I refactored it to just two methods using generics:
public static T GetValue<T>(this XElement root, string name, T defaultValue)
{
T value = defaultValue;
string strValue = (string)root.Elements(name).FirstOrDefault() ?? defaultValue.ToString();
if (value is string)
{
return ((T)(object)strValue);
}
else
{
var tryParse = typeof (T).GetMethod("TryParse", new [] {typeof(string), typeof(T).MakeByRefType()});
if (tryParse == null)
{
throw new InvalidOperationException();
}
var parameters = new object[] {strValue, value};
if ((bool)tryParse.Invoke(null, parameters))
{
value = (T)parameters[1];
}
}
return value;
}
public static T GetAttribute<T>(this XElement root, string name, T defaultValue)
{
T value = defaultValue;
string strValue = (string)root.Attributes(name).FirstOrDefault() ?? defaultValue.ToString();
if (value is string)
{
value = (T)(object)strValue;
}
else
{
var tryParse = typeof (T).GetMethod("TryParse", new [] {typeof(string), typeof(T).MakeByRefType()});
if (tryParse == null)
{
throw new InvalidOperationException();
}
var parameters = new object[] {strValue, value};
if ((bool)tryParse.Invoke(null, parameters))
{
value = (T)parameters[1];
}
}
return value;
}