mark
mark

Reputation: 53

Dynamically copying generic objects

I have some code that I use to copy a datarow into an object you supply. The datarow is weakly typed. Right now I am able to assign all data from the datarow to the object using reflection to get properties, iterating over those properties and setting their value using the datarow. While this works, it only works when the underlying data types are the same (not that big of a deal) and more importantly, as long as the properties and fields are in the same order.

There is ONE caveat to this question. About 90% of the column names match the property names. This is where I get hung up. Example: In the database which I return a datarow from there is a field called Date_Created, the property for my object is called Created_Date or Modified_By in the database and Update_By in the object property. (I can't rename the properties or the tables, so that easy answer is out of the question).

The reason I am trying to do this is because my companies code that gets data from the database returns weakly typed dataset and when we update or add data to the database, we must populate a custom class and pass that to an update procedure.

Is there any way for me to compare the datarow columns and compare to the object property or assign new column names at runtime to the datarow?

    private static T CopyRecord<T>(DataRow record, dynamic newRecord)
    {
        //Get Properties
        var properties = newRecord.GetType().GetProperties();
        var column = 0;
        foreach (PropertyInfo property in properties)
        {
            //If property is generic list, continue
            if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) continue;

            //Check for dbnull, return null if true, or convert to correct type
            var columnValue = Convert.IsDBNull(record[column]) ? null : Convert.ChangeType(record[column], Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType);

            //Set property for newRecord
            property.SetValue(newRecord, columnValue);
            column++;
        }
        return newRecord;
    }

EDIT: Would using custom attributes work in this situation? I could prefix my class properties with the equivalent database column name. That way, I could use reflection on both the custom class and weakly typed datarow and matchup all data.

Upvotes: 1

Views: 941

Answers (2)

Eamon Nerbonne
Eamon Nerbonne

Reputation: 48066

Some notes:

  • newRecord should not be typed dynamic. You know it's a T - declare it as such.
  • I'd strongly recommend against using Convert - I'm willing to bet you don't understand its semantics (I sure have tried to avoid knowing all the weird corner cases); and even if you do, blindly converting types is usually a good way to hide bugs.
  • You're matching columns by ordering - the ordering of properties is NOT DEFINED and has changed in the past. There's an undocumented workaround; you can sort by MetadataToken and sort-of reconstruct source order as long as there's no inheritance, but it's really unadvisable to rely on this for correctness. A much better idea is to match columns to properties by name - you can get all the columns via record.Table.Columns.
  • But... It looks to me like you're really looking for a micro-ORM. There's a bunch of really excellent ones available, like PetaPoco or Dapper.

A micro-orm won't prescribe the way you use sql; it only maps C# objects to rows you get from the database and vice versa; that means they're much more flexible and easier to migrate to than LINQ, and it also means both PetaPoco and Dapper are much faster than LINQ and often faster than manual coding, unless you really pay attention to using the low-level api's correctly.

Upvotes: 1

FastAl
FastAl

Reputation: 6280

Note: Don't ever use positional references like you're doing here! It asks for nasty bugs. Unless of course, somebody else will maintain this code and you're planning on having quit by then (that's what happened to the code I'm thinking about I inherited).

Attributes

Put Attributes on the properties. Read these.

If you don't have control of the generic objects, but the methods are overridable, inherit from it.

Mapping

If you can't do that (or this might just be easier anyway), pass in a map as a dictionary. Now this isn't all that bad to implement easily without passing extra parameters and hammering the caller. Inherit the objects and have them implment an interface that returns the map (or if you can edit the original objects just implement directly).

The implementation (in your specific objects) of the method on this interface would return a dictionary and it would contain the mappings. For your generic method, restrict types that can be passed to it.

There's a lot to google there to flesh out the 'how's' ... but I hope it points you in the right directions.

Upvotes: 2

Related Questions