Reputation: 53
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
Reputation: 48066
Some notes:
newRecord
should not be typed dynamic
. You know it's a T
- declare it as such.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.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
.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
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).
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.
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