I am using a routine to read data from Xml files similar to this:
<VerificationSample X1 = "1" X3 = "3" ../>
using this call:
XmlReader reader = XmlReader.Create(path);
reader.ReadToFollowing("VerificationSample");
this.X1 = (double)FileStructure.GetAttributeSafe(reader, "X1", typeof(double)); // exists
this.X2 = (double)FileStructure.GetAttributeSafe(reader, "X2", typeof(double)); // doesn't exist
However, some attributes might not exist so I handle it using this routine inside that attribute reader function definition:
public static object GetAttributeSafe(XmlReader reader, string attributeName, Type objectType)
{
// ..
string value = reader.GetAttribute(attributeName);
if (value != null) // attribute exists
{
if (objectType != typeof (string))
{
var converter = TypeDescriptor.GetConverter(objectType);
returnValue = converter.ConvertFrom(value);
}
else // is already a string and doesn't need to be converted
{
return value;
}
}
else // attribute doesn't exist
{
return "0";
}
}
If the attribute doesn't exist, application pops an error:
Specified cast is not valid
What is my mistake?
Because you are returning "0" , which is a String, and you can't Cast this to double. Use Convert.toDouble(String s) instead.
If the attribute doesn't exist, you return "0", which you are then trying to cast to a double. You need to return a default value of whatever objectType is, perhaps by doing
else // attribute doesn't exist
{
if(objectType.IsValueType)
{
return Activator.CreateInstance(objectType);
}
return null;
}
You will only return null if you try to convert to something non-immutable like a class instead of an int or string, which will blow up your function anyway; it's just there so all code paths return something.
Related
I got the above error in my app. Here is the original code
public string GetCustomerNumber(Guid id)
{
string accountNumber =
(string)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidmyApp,
CommandType.StoredProcedure,
"GetCustomerNumber",
new SqlParameter("#id", id));
return accountNumber.ToString();
}
I replaced with
public string GetCustomerNumber(Guid id)
{
object accountNumber =
(object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM,
CommandType.StoredProcedure,
"spx_GetCustomerNumber",
new SqlParameter("#id", id));
if (accountNumber is System.DBNull)
{
return string.Empty;
}
else
{
return accountNumber.ToString();
}
}
Is there a better way around this?
With a simple generic function you can make this very easy. Just do this:
return ConvertFromDBVal<string>(accountNumber);
using the function:
public static T ConvertFromDBVal<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T); // returns the default value for the type
}
else
{
return (T)obj;
}
}
A shorter form can be used:
return (accountNumber == DBNull.Value) ? string.Empty : accountNumber.ToString()
EDIT: Haven't paid attention to ExecuteScalar. It does really return null if the field is absent in the return result. So use instead:
return (accountNumber == null) ? string.Empty : accountNumber.ToString()
ExecuteScalar will return
null if there is no result set
otherwise the first column of the first row of the resultset, which may be DBNull.
If you know that the first column of the resultset is a string, then to cover all bases you need to check for both null and DBNull. Something like:
object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null) ? String.Empty : accountNumber.ToString();
The above code relies on the fact that DBNull.ToString returns an empty string.
If accountNumber was another type (say integer), then you'd need to be more explicit:
object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null || Convert.IsDBNull(accountNumber) ?
(int) accountNumber : 0;
If you know for sure that your resultset will always have at least one row (e.g. SELECT COUNT(*)...), then you can skip the check for null.
In your case the error message "Unable to cast object of type ‘System.DBNull’ to type ‘System.String`" indicates that the first column of your result set is a DBNUll value. This is from the cast to string on the first line:
string accountNumber = (string) ... ExecuteScalar(...);
Marc_s's comment that you don't need to check for DBNull.Value is wrong.
You can use C#'s null coalescing operator
return accountNumber ?? string.Empty;
This is the generic method that I use to convert any object that might be a DBNull.Value:
public static T ConvertDBNull<T>(object value, Func<object, T> conversionFunction)
{
return conversionFunction(value == DBNull.Value ? null : value);
}
usage:
var result = command.ExecuteScalar();
return result.ConvertDBNull(Convert.ToInt32);
shorter:
return command
.ExecuteScalar()
.ConvertDBNull(Convert.ToInt32);
There is another way to workaround this issue. How about modify your store procedure? by using ISNULL(your field, "") sql function , you can return empty string if the return value is null.
Then you have your clean code as original version.
I suppose you can do it like this:
string accountNumber = DBSqlHelperFactory.ExecuteScalar(...) as string;
If accountNumber is null it means it was DBNull not string :)
String.Concat transforms DBNull and null values to an empty string.
public string GetCustomerNumber(Guid id)
{
object accountNumber =
(object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM,
CommandType.StoredProcedure,
"spx_GetCustomerNumber",
new SqlParameter("#id", id));
return String.Concat(accountNumber);
}
However, I think you lose something on code understandability
Since I got an instance which isn't null and if I compared to DBNULL I got Operator '==' cannot be applied to operands of type 'string' and 'system.dbnull' exeption,
and if I tried to change to compare to NULL, it simply didn't work ( since DBNull is an object) even that's the accepted answer.
I decided to simply use the 'is' keyword.
So the result is very readable:
data = (item is DBNull) ? String.Empty : item
based on answer from #rein
public static class DbDataReaderExtensions
{
public static TObjProp Get<TObj, TObjProp>(
this DbDataReader reader,
Expression<Func<TObj, TObjProp>> expression)
{
MemberExpression member = expression.Body as MemberExpression;
string propertyName = member.Member.Name;
//PropertyInfo propInfo = member.Member as PropertyInfo;
var recordOrdinal = reader.GetOrdinal(propertyName);
var obj = reader.GetValue(recordOrdinal);
if (obj == null || obj == DBNull.Value)
{
return default(TObjProp);
}
else
{
return (TObjProp)obj;
}
}
}
Given:
public class MyClass
{
public bool? IsCheckPassed { get; set; }
}
Use as:
var test = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);
or, if you hardcode class type in exception method:
var test = reader.Get(o => o.IsCheckPassed);
p.s. I haven't figured yet how to make generics implicit without sacrificing code length.. fee free to comment and suggest improvements
Full example:
public async Task<MyClass> Test(string connectionString) {
var result = new MyClass();
await using var con = new SQLiteConnection(connectionString);
con.Open();
await using var cmd = con.CreateCommand();
cmd.CommandText = #$"SELECT Id, IsCheckPassed FROM mytable";
var reader = await cmd.ExecuteReaderAsync();
while (reader.Read()) {
// old, not working! Throws exception!
//bool? isCheckPassed1 = reader.GetBoolean(reader.GetOrdinal("IsCheckPassed"));
// old, working, but too long (also if you have like 20 properties then all the more reasons to refactor..)
bool? isCheckPassed2 = null;
bool? isCheckPassed2Temp = reader.GetValue(reader.GetOrdinal("IsCheckPassed"));
if (isCheckPassed2Temp != null && isCheckPassed2Temp != DBNull.Value)
isCheckPassed2 = (bool?)isCheckPassed2Temp;
// new
var isCheckPassed3 = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);
// repeat for 20 more properties :)
result.IsCheckPassed = isCheckPassed3;
}
return result;
}
Solution will work for as long as table column names match property names of the class. And might not be production-grade performance wise, so use or modify at your own risk :)
A more concise approach using more recent C# syntax and also accounting for nullable types:
private static T? FromDbNull<T>(object? obj) =>
obj == null || obj == DBNull.Value ? default : (T)obj;
Can be used with a data reader as follows:
while (reader.Read())
{
var newObject = new SomeObject(
FromDbNull<string?>(reader["nullable_field_1"]),
FromDbNull<string?>(reader["nullable_field_2"]),
FromDbNull<string?>(reader["nullable_field_3"]),
FromDbNull<double?>(reader["nullable_field_4"])
);
response.Add(newObject);
}
I use an extension to eliminate this problem for me, which may or may not be what you are after.
It goes like this:
public static class Extensions
{
public String TrimString(this object item)
{
return String.Format("{0}", item).Trim();
}
}
Note:
This extension does not return null values! If the item is null or DBNull.Value, it will return an empty String.
Usage:
public string GetCustomerNumber(Guid id)
{
var obj =
DBSqlHelperFactory.ExecuteScalar(
connectionStringSplendidmyApp,
CommandType.StoredProcedure,
"GetCustomerNumber",
new SqlParameter("#id", id)
);
return obj.TrimString();
}
Convert it Like
string s = System.DBNull.value.ToString();
Given any object, I want to be able to retrieve the value of a property and any "depth".
var name = myObject.GetValue<String>("Name")
or
var father_name = myObject.GetValue<String>("Father.Name")
This is easy and I'm able do achieve it with the following code:
public static T GetValue<T>(this Object obj, String fqname)
{
try
{
Object value = obj;
foreach (var prop in fqname.Split('.').Select(s => value.GetType().GetProperty(s)))
{
value = prop.GetValue(value, null);
}
if (value is T)
{
return (T)value;
}
else
{
// if the type requested is not the same as the stored, attempt a blind conversion
var converter = TypeDescriptor.GetConverter(typeof(T));
return (T)converter.ConvertFromInvariantString(value.ToString());
}
}
catch (NotSupportedException)
{
throw new InvalidCastException($"Cannot convert value to reuested type");
}
}
Now the problem is that this doesn't work for arrays, like:
var main_street = myObject.GetValue<String>("Addresses[0].StreetName")
Not even for the cases of arrays of arrays and so on...
I can start adding these conditions and special cases to my code but before that I figured, as C# already does this, maybe we could leverage some code parsing strategy, Roslyn, ... don't know, something that doesn't feel like reinventing the wheel and supports as many cases as possible.
Any ideas?
I'm trying to build a method that queries a SQL table and assigns the values it finds to a new list of objects. Here's a quick example of how it works (assume the reader and connection are set up and working properly):
List<MyObject> results = new List<MyObject>();
int oProductID = reader.GetOrdinal("ProductID");
int oProductName = reader.GetOrdinal("ProductName");
while (reader.Read())
{
results.Add(new MyProduct() {
ProductID = reader.GetInt32(oProductID),
ProductName = reader.GetString(oProductName)
});
}
There are about 40 other properties too, all of them nullable in the MyObject definition, so I'm trying to keep the assignments as tidy as possible. The problem is that I need to assign null values to the object wherever the reader returns a null. In the above code, if the reader throws a "Data is Null" exception. I'm aware it's possible to use an if statement to check for a DbNull first, but since there are so many properties I'm hoping to keep the code cleaner by not having to spell out an if statement for every single property.
A bit of searching led me to the null-coalescing operator, which seems like it should do exactly what I want. So I tried changing the assignments to look like this:
ProductID = reader.GetInt32(oProductID) ?? null,
ProductName = reader.GetString(oProductName) ?? null
Which works fine for any string but gives me errors of Operator '??' cannot be applied to operands of type 'int' and '<null>' (or any other data type except string. I specifically called out the int (and everything else) as nullable in the object definition, but here it's telling me it can't do that.
The Question
Is there a way to handle nulls in this case that can: (1) Be written clearly in-line (to avoid separate if statements for each property), and (2) Work with any data type?
Null from a database is not "null", it's DbNull.Value. ?? and ?. operators won't work in this case. GetInt32, etc. will throw an exception if the value is null in the DB. I do a generic method and keep it simple:
T SafeDBReader<T>(SqlReader reader, string columnName)
{
object o = reader[columnName];
if (o == DBNull.Value)
{
// need to decide what behavior you want here
}
return (T)o;
}
If your DB has nullable ints for example, you can't read those into an int unless you want to default to 0 or something like. For nullable types, you can just return null or default(T).
Shannon's solution is both overly complicated and will be a performance issue (lots of over the top reflection) IMO.
You can write a series of extensions method for each of the standard GetXXXX. These extensions receive an extra parameter that is the default to return in case the value of the field is null.
public static class SqlDataReaderExtensions
{
public int GetInt32(this SqlDataReader reader, int ordinal, int defValue = default(int))
{
return (reader.IsDBNull(ordinal) ? defValue : reader.GetInt32(ordinal);
}
public string GetString(this SqlDataReader reader, int ordinal, int defValue = "")
{
return (reader.IsDBNull(ordinal) ? defValue : reader.GetString(ordinal);
}
public int GetDecimal(this SqlDataReader reader, int ordinal, decimal defValue = default(decimal))
{
....
}
}
This allows you to leave your current code as is without changes or just change the fields that needs the null as return
while (reader.Read())
{
results.Add(new MyProduct() {
ProductID = reader.GetInt32(oProductID),
ProductName = reader.GetString(oProductName, "(No name)"),
MinReorder = reader.GetInt32(oReorder, null)
.....
});
}
You can also have a version where you pass the column name instead of the ordinal position and do the search for the position inside the extension, but this is probably not good from a performance point of view.
Here's an example that works for fields (can easily be converted to properties) and allows for null checks. It does the dreaded if (in a switch), but it's pretty fast.
public static object[] sql_Reader_To_Type(Type t, SqlDataReader r)
{
List<object> ret = new List<object>();
while (r.Read())
{
FieldInfo[] f = t.GetFields();
object o = Activator.CreateInstance(t);
for (int i = 0; i < f.Length; i++)
{
string thisType = f[i].FieldType.ToString();
switch (thisType)
{
case "System.String":
f[i].SetValue(o, Convert.ToString(r[f[i].Name]));
break;
case "System.Int16":
f[i].SetValue(o, Convert.ToInt16(r[f[i].Name]));
break;
case "System.Int32":
f[i].SetValue(o, Convert.ToInt32(r[f[i].Name]));
break;
case "System.Int64":
f[i].SetValue(o, Convert.ToInt64(r[f[i].Name]));
break;
case "System.Double":
double th;
if (r[f[i].Name] == null)
{
th = 0;
}
else
{
if (r[f[i].Name].GetType() == typeof(DBNull))
{
th = 0;
}
else
{
th = Convert.ToDouble(r[f[i].Name]);
}
}
try { f[i].SetValue(o, th); }
catch (Exception e1)
{
throw new Exception("can't convert " + f[i].Name + " to doube - value =" + th);
}
break;
case "System.Boolean":
f[i].SetValue(o, Convert.ToInt32(r[f[i].Name]) == 1 ? true : false);
break;
case "System.DateTime":
f[i].SetValue(o, Convert.ToDateTime(r[f[i].Name]));
break;
default:
throw new Exception("Missed data type in sql select ");
}
}
ret.Add(o);
}
return ret.ToArray();
}
When I try to get an Integer from my SQLite db I can only get it working by reading it as a string and then run int.Parse on it.
Is this right, I read something about this having to do with ExeculeScalar possibly giving back null?
Here is my current code SendSQLExecScalar() sends the command string etc. and return an object
public object SendSQLExecScalar(string C)
{
OpenConnection();
SQLiteCommand SQLCommand = new SQLiteCommand(C, DbConnection);
try
{
object Output = SQLCommand.ExecuteScalar();
CloseConnection();
return Output;
}
catch (Exception X)
{
MessageBox.Show(X.Message);
return null;
}
}
And:
int ID = int.Parse(SendSQLExecScalar(C).ToString());
EDIT :
Specified cast is not valid.
public static int GetImageID(string Path)
{
string C = "SELECT ID FROM Images WHERE Path LIKE '" + Path + "' LIMIT 1";
return ConvertFromDBVal<int>(SendSQLExecScalar(C));
}
public static T ConvertFromDBVal<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T);
}
else
{
return (T)obj; //breaks here saying this cast is invalid
}
}
I read something about this having to do with execuleScalar possibly
giving back null?
Yes, if there is no data that your sql query returns, ExecuteScalar returns null reference.
If you are 100% sure the return value on the first column of the first row is already int, you can just cast it like;
int ID = (int)SendSQLExecScalar(C);
To prevent null cases on this method, I almost always uses rein's generic method as;
public static T ConvertFromDBVal<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T); // returns the default value for the type
}
else
{
return (T)obj;
}
}
Use TryParse instead of Parse, this allows you to test whether something is parseable.
If you use int.Parse() with an invalid int, you'll get an exception while in the TryParse, it returns a boolean letting you know whether the parse succeeded or not.
In short use Parse if you are sure the value will be valid; otherwise use TryParse.
int number = int.Parse(someString);
int number;
int.TryParse(someString, out number);
(int)(long)SendSQLExecScalar(C);
Solved with this, looks Like SQLite integer will return a long object which I needed to unpack before casting it to an int.
I'm writing an application that runs "things" to a schedule.
Idea being that the database contains assembly, method information and also the parameter values. The timer will come along, reflect the method to be run, add the parameters and then execute the method.
Everything is fine except for the parameters.
So, lets say the method accepts an ENUM of CustomerType where CustomerType has two values of CustomerType.Master and CustomerType.Associate.
EDIT
I don't know the type of parameter that will be getting passed in. ENUM used as an example
END OF EDIT
We want to run Method "X" and pass in parameter "CustomerType.Master". In the database, there will be a varchar entry of "CustomerType.Master".
How do I convert the string "CustomerType.Master" into a type of CustomerType with a value of "Master" generically?
Thanks in advance,
Jim
OK, the scope of the question shifted but my original observation and objection to some other solutions still stands.
I think you don't/can't want to use 'generics' here. You don't know the type ahead of time, and since you will need to create the type, there is no need to use a generic implementation because MethodBase.Invoke takes an array of Object.
This code assumes you are instantiating the target from database field. If not just adjust accordingly.
Of course this is not all encompassing and has no useful exception handling, but it will allow you to dynamically execute arbitrary methods on an arbitrary type with arbitrary parameters values all coming from string values in a row.
NOTE: there are many many many scenarios in which this simple executor will not work. You will need to ensure that you engineer your dynamic methods to cooperate with whatever strategy you do end up deciding to use.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using NUnit.Framework;
namespace DynamicMethodInvocation
{
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
// from your database
string assemblyQualifiedTypeName = "DynamicMethodInvocation.TestType, DynamicMethodInvocation";
string methodName = "DoSomething";
// this is how you would get the strings to put in your database
string enumString = Executor.ConvertToString(typeof(AttributeTargets), AttributeTargets.Assembly);
string colorString = Executor.ConvertToString(typeof(Color), Color.Red);
string stringString = "Hmm... String?";
object result = Executor.ExecuteMethod(assemblyQualifiedTypeName, methodName,
new[] { enumString, colorString, stringString });
Assert.IsInstanceOf<bool>(result);
Assert.IsTrue((bool)result);
}
}
public class TestType
{
public bool DoSomething(AttributeTargets #enum, Color color, string #string)
{
return true;
}
}
public class Executor
{
public static object ExecuteMethod(string assemblyQualifiedTypeName, string methodName,
string[] parameterValueStrings)
{
Type targetType = Type.GetType(assemblyQualifiedTypeName);
MethodBase method = targetType.GetMethod(methodName);
ParameterInfo[] pInfo = method.GetParameters();
var parameterValues = new object[parameterValueStrings.Length];
for (int i = 0; i < pInfo.Length; i++)
{
parameterValues[i] = ConvertFromString(pInfo[i].ParameterType, parameterValueStrings[i]);
}
// assumes you are instantiating the target from db and that it has a parameterless constructor
// otherwise, if the target is already known to you and instantiated, just use it...
return method.Invoke(Activator.CreateInstance(targetType), parameterValues);
}
public static string ConvertToString(Type type, object val)
{
if (val is string)
{
return (string) val;
}
TypeConverter tc = TypeDescriptor.GetConverter(type);
if (tc == null)
{
throw new Exception(type.Name + " is not convertable to string");
}
return tc.ConvertToString(null, CultureInfo.InvariantCulture, val);
}
public static object ConvertFromString(Type type, string val)
{
TypeConverter tc = TypeDescriptor.GetConverter(type);
if (tc == null)
{
throw new Exception(type.Name + " is not convertable.");
}
if (!tc.IsValid(val))
{
throw new Exception(type.Name + " is not convertable from " + val);
}
return tc.ConvertFrom(null, CultureInfo.InvariantCulture, val);
}
}
}
I would think you have 2 major options:
Store the type name along with the parameter value and use that to cast things using Type.GetType(string) to resolve the type in question.
Standardize all the methods to be called this way to accept an array of strings, and expect the methods to do any necessary casting.
I know you've stated that you're not doing option 1, but it would help things from the standpoint of calling the functions.
Option 2 is the far more 'generic' way to handle the situation, assuming all values can be represented by and cast/converted from strings to the appropriate type. Of course, that only helps if you actually have control over the definition of the methods being called.
Below is a useful extension method I use in .NET 3.5.
With this extension method available, your code could look like this:
var valueInDb = GetStringFromDb().Replace("CustomerType.", string.Empty);
var value = valueInDb.ToEnum(CustomerType.Associate);
By supplying the default value in the parameter, the compiler will know which Enum you want your string to be turned into. It will try to find your text in the Enum. If it doesn't it will return the default value.
Here is the extension method: (this version also does partial matches, so even "M" would work nicely!)
public static T ToEnum<T>(this string input, T defaultValue)
{
var enumType = typeof (T);
if (!enumType.IsEnum)
{
throw new ArgumentException(enumType + " is not an enumeration.");
}
// abort if no value given
if (string.IsNullOrEmpty(input))
{
return defaultValue;
}
// see if the text is valid for this enumeration (case sensitive)
var names = Enum.GetNames(enumType);
if (Array.IndexOf(names, input) != -1)
{
// case insensitive...
return (T) Enum.Parse(enumType, input, true);
}
// do partial matching...
var match = names.Where(name => name.StartsWith(input, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
if(match != null)
{
return (T) Enum.Parse(enumType, match);
}
// didn't find one
return defaultValue;
}
I still don't fully understand your question... however, you say "Everything is fine except for the parameters."
I'll assume "CustomerType" the name of a property on your object, and "Master" is the string value you want to put in that property.
Here is (another) extension method that may help.
Once you have your new object and the value and property name from the database field, you could use this:
// string newValue = "Master";
// string propertyName = "CustomerType";
myNewObject.SetPropertyValue(propertyName, newValue)
Method:
/// <summary>Set the value of this property, as an object.</summary>
public static void SetPropertyValue(this object obj,
string propertyName,
object objValue)
{
const BindingFlags attr = BindingFlags.Public | BindingFlags.Instance;
var type = obj.GetType();
var property = type.GetProperty(propertyName, attr);
if(property == null) return;
var propertyType = property.PropertyType;
if (propertyType.IsValueType && objValue == null)
{
// This works for most value types, but not custom ones
objValue = 0;
}
// need to change some types... e.g. value may come in as a string...
var realValue = Convert.ChangeType(objValue, propertyType);
property.SetValue(obj, realValue, null);
}
If you are using .NET 4 you can do the following.
var result = default(CustomerType);
if (!Enum.TryParse("Master", out result))
{
// handle error
}