views:

59

answers:

3

This is a C# winforms app.

Preface:

I am creating a form that will allow users to import data from an existing MySql or SQL Server database into my SQL Server database. This allows users to quickly import IP addresses and other data without having to re-enter it via a control.

Example:

Simplified, I have two objects, FieldObject and SensorObject. FieldObject exists to store the source and destination database field names and types. SensorObject is the object I later populate from the records in the database. For simplicity's sake, I am omitting the type handling and other functionality that is not relevant to the question. (The data for DestinationField is limited to a list that I provide the user, and comes from an array or list within the program.) Here is an example of both:

public class FieldObject
{
    public string DestinationField {get; set;}
    public string SourceField {get; set;}
}

public class SensorObject
{
    public string Name {get; set;}
    public string IPAddress {get; set;}
}

Problem:

When the user populates the various fields of FieldObject, I use the information to populate the destination database, though I have a large switch statement that checks the destination field name to know what property of SensorObject it belongs.

For example:

// Reader is a SqlDataReader with the prerequisite database connection
FieldObject myField = new FieldObject
    {
        DestinationField = "name",
        SourceField = "proprietary"
    };
SensorObject mySensor = new SensorObject();
switch (myField.DestinationField)
{
    case "name":
        mySensor.Name = Convert.ToString(Reader[myField.DestinationField]);
        break;
    case "ip_address":
        mySensor.IPAddress = Convert.ToString(Reader[myField.DestinationField]);
        break;
}

As you can see it would require more redundant code to handle more properties for the object.

Question:

I'd like some way of storing the property of SensorObject that the data belongs to, so that when iterating FieldObjects in a list, I can effectively eliminate the switch statement.

Something like:

foreach(FieldObject f in myFieldList)
{
    mySensor(f.mySensorField) = Convert.ToString(Reader[f.DestinationField]);
}

I am not certain what technique in C# lends itself to this sort of application. I've looked into reference values and reflection, and neither seem appropriate.

I appreciate any insight and advice or even ways to rethink this approach.

+1  A: 

Actually, reflection is not a bad idea, particularly if you populate a dictionary with PropertyInfo instances for each property name.

Steven Sudit
+2  A: 

You have to use reflection to do this. Should end up as something like:

var sensorFields = typeof(SensorObject).GetProperties()
foreach(var field in fields)
{
    var info = sensorFields.First(sensorField => sensorField.Name == field.Name);
    var value = Convert.ToString(Reader[field.Destination]);
    info.SetValue(sensorObj, value, new object[0]);
}

GetProperties gets a property info for each property which can be used to set the value.

Of course property infos can be cached. Just write it once and refactor as soon as it runs. Don't over complicate too much this is called premature optimization and leads straight to hell ;)

Zebi
This looks very helpful, it helps to have someone who understands reflection and its use to point the right direction!
JYelton
If you replace the loop contents with `lookup.Add(field.Name,field)`, where `lookup` is a `Dictionary<string,FieldInfo>`, that's all you need to cache these results for later.
Steven Sudit
I'm not clear on what `Reader[myField.DestinationField]` is, but I'm not sure you'd need to convert it to string. Or, if you did, I would think you'd just call `x.ToString()` instead of using `Convert.ToString(x)`.
Steven Sudit
Actually I have an entire library for reading stuff from SQL, but I didn't want to use proprietary method names to distract from the question, so I just used a placeholder. I'd like to have used `Reader.GetString("fieldname")` however MS-SQL requires an integer (a zero-based column ordinal) to designate the column rather than a field name. To be clear about the field name association, I employed the `convert` hack. :)
JYelton
@Zebi: I'm having a little trouble getting the LINQ portion of your code to work, I've recently begun using LINQ to work with lists (it's such a great tool!) but I'm not sure how to "fix" the `.First` query. I thought perhaps I needed to try: `var info = sensorFields.Where(x => x == field.Name).First();`. I'm sure I can get it eventually, but I thought I'd give you the heads up.
JYelton
Little update on the Setvalue part, the correct signature isPropertyInfo.SetValue (Object, Object, Object[])The last one is for indexed properties (if your property is an Array and you want to set the n-th element in the array, or a specific key in a dictionary). Updated the usage above.
Zebi
ah sorry I am writing without VS so no compiler checking ;)First takes a predicate which determines which element to take.If you got a list of Numbers 1...10 then numbers.First(x => x>5) returns 6. numbers.First() returns 1. You have to provide a comparison so call fields.First(sensorField => sensorField.Name == field.Name) should do the trick.
Zebi
@Zebi: Thanks for the revision. It's working and I am able to move on, thanks for the help.
JYelton
+1  A: 

You can do it with reflection. You get the PropertyInfo of the proprty with the name in question, get the MethodInfo of the setter, and then invoke it.

It might though, be possible to store an associative array (Dictionary or HastTable, etc.) of values stored by name, which would be much easier to deal with if appropriate.

Jon Hanna
It might make sense to call `GetSetMethod` on the `PropertyInfo`, which returns a `MethodInfo' to the setter itself.
Steven Sudit
@Steven, right you are. I'd forgotten when object was updating which in the question by the time I'd started answering :) Fixed, thanks.
Jon Hanna
Just use PropertyInfo.SetValue (Object, Object, Object[])
Zebi
Right you are Zebi.
Jon Hanna
@Zebi: If we're going to cache something, why not the `MethodInfo` of the setter as opposed to the `PropertyInfo`. I expect that the latter would just use the former, so we could cut out a step there.
Steven Sudit
I don't think this change makes a measurable difference but in my opinion it adds unnecessary complexity. It's the reason I suggested to solve it the easy way without even caching. If you see serious benefits out of caching implement it.
Zebi
@Zebi, @Steven. Yes, if you were going to cache here, then the MethodInfo would be the sensible one to cache, but I don't see a need to cache anything, and a good reason not too (is this cache going to grow? do we need complex LRU code to keep it from getting massive? if so it'll become slower than the code needed to obtain the PropertyInfo each time).My idea for a dictionary is not to cache the property infos, but just to change the properties themselves to be name-value pairs. Evil as a normal way to do properties, but sensible if this sort of by-name look up is the class' purpose.
Jon Hanna