Reputation: 704
I have a problem where I am trying to write a generic method that reads xml and writes the data to the database.
In this case that data was serialized by another copy of the program.
Before I add an item from the xml to the SqlParameter I need to run it through one of several different "Fixing" methods in the utilities file.
The particular method needed is something that needs to be passed in, so I have ended up passing it in as a string and using a switch structure to determine which method to call with reflection as a the default case.
I was wondering if there is any way to actually pass in the method reference without having to specify that it returned an int, string, bool, decimal, double, or DateTime.
I have tried using a Func but it requires a result type.
The new argument would probably look something like:
Tuple<string,Func<?>[] parameters
These utility functions all take an object as a parameter.
I found a question that is almost what I am looking for except they don't have a list that can all be different:
My current code:
private void SaveBundleToTable<T1, T2>
(
string xmlData,
string table,
string storedProcedure,
Tuple<string, string>[] parameters
)
where T1 : DataSet, new()
where T2 : DataRow
{
T1 BundleData = new T1();
BundleData.Clear();
StringReader theReader = new StringReader(xmlDataset);
BundleData.ReadXml(theReader);
foreach (T2 item in BundleData.Tables[table].Rows)
{
SqlCommand _cmd = new SqlCommand(storedProcedure, [SQLConnection]);
_cmd.CommandType = CommandType.StoredProcedure;
foreach (var par in parameters)
{
var _param = getSqlParameter<T2>(item, par.First, par.Second);
_cmd.Parameters.Add(_param);
}
_cmd.ExecuteNonQuery();
}
}
Switch structure in a separate method:
private SqlParameter getSqlParameter<T>(T item, string columnName, string method) where T : DataRow
{
switch (method)
{
case "FixString":
return new SqlParameter(columnName, util.FixString(item[columnName]));
case "FixInt":
return new SqlParameter(columnName, util.FixInt(item[columnName]));
case "FixBooleanToInt":
return new SqlParameter(columnName, util.FixBooleanToInt(item[columnName]));
case "FixBoolean":
return new SqlParameter(columnName, util.FixBoolean(item[columnName]));
case "FixFloat":
return new SqlParameter(columnName, util.FixFloat(item[columnName]));
case "FixDouble":
return new SqlParameter(columnName, util.FixDouble(item[columnName]));
case "FixDate":
return new SqlParameter(columnName, util.FixDate(item[columnName]));
case "FixDateForZeroTime":
return new SqlParameter(columnName, util.FixDateForZeroTime(item[columnName]));
case "FixDecimal":
return new SqlParameter(columnName, util.FixDecimal(item[columnName]));
default:
try
{
Type thisType = util.GetType();
MethodInfo theMethod = thisType.GetMethod(method);
return new SqlParameter(columnName, theMethod.Invoke(this, new object[]{item[columnName]}));
}
catch (Exception ex)
{
throw new Exception("Unknown utility method: " + method,ex);
}
}
}
Edit 1:
I just tried using a parameter type of Tuple<string,Func<object,dynamic>>
and it would not compile as it said it could not determine the type of Tuple<string,?>
As a note, This project is in .Net 3.5 so I implemented a local version of Tuple as discussed in this question:
Equivalent of Tuple (.NET 4) for .NET Framework 3.5
Edit 2:
If I make a change to the generic method so I can pass an array of type Tuple<string,Func<object,object>>
then when I try to call it I get a compile error on the decimal line:
new Tuple<string, Func<object,object>>[]
{
Tuple.New<string, Func<object,object>>("LAB_CODE",util.FixString),
Tuple.New<string, Func<object,object>>("MORM_SELL",util.FixDecimal),
...
There is a larger error if I use Tuple.New<string, Func<object,decimal>>
Upvotes: 0
Views: 469
Reputation: 704
I did figure out a way to make the function passing work but it is way too verbose for my situation.
new Tuple<string, Func<object,object>>[]
{
Tuple.New<string, Func<object,object>>("LAB_CODE",util.FixString),
Tuple.New<string, Func<object,object>>("MORM_SELL",x => (object)util.FixDecimal(x)),
...
Unless I find a more concise way to do this I think I will stick with my switch statement.
Edit:
I added these functions to my static Tuple class:
public static Tuple<string, Func<object, object>> Util<T>(string first, Func<object, T> second)
{
Func<object, object> method = getFunc<T>(second);
var tuple = new Tuple<string, Func<object, object>>(first, method);
return tuple;
}
private static Func<object,object> getFunc<T>(Func<object,T> method)
{
return x => (object)method(x);
}
This lets me use this syntax to pass in the parameters:
new Tuple<string, Func<object,object>>[]
{
Tuple.Util<string>("LAB_CODE",util.FixString),
Tuple.Util<decimal>("MORM_SELL",util.FixDecimal),
...
These could be implemented in a extension function to use it with the .Net 4 Tuple class
Upvotes: 0
Reputation: 181
Can you simply not use Func<object, object>
? Even if the return type isn't an object, I am still able to compile it. Here is the example I tested on VS 2013:
public static string FixString(object data)
{
return null;
}
static void Main(string[] args)
{
DataRow row = null;
SqlParameter param = getSqlParameter(row, "Address", FixString);
}
private static SqlParameter getSqlParameter<T>(T item, string columnName, Func<object, object> method) where T : DataRow
{
return new SqlParameter(columnName, method(item[columnName]));
}
Upvotes: 0
Reputation: 767
You should just have a util.Fix(Object o) function which returns the proper fix by determining the code through:
if (o is string) return util.FixString(o);
else if (o is int) return util.FixInt(o) ...
It's similar to what you have but it cleans up the management. Your are stuck in that you are forced to perform some sort of if statement because you are doing different logic per data type.
For classes just create an interface which has the necessary method defined for the fix.
Upvotes: 0