Reputation: 91
I have been tasked with writing a routine against an existing database. This database has several tables that have identical structures, but different names (I did not design this, please do not suggest database design change). I am writing this in EF, and the models were created database-first.
The best option I can think of for this situation is to create a class that has the exact same properties, and create a routine that can accept generic types to copy data from the EF model to the generic model.
My models:
// Sample EF database-first created model
namespace SampleDataModel.Models
{
using System;
using System.Collections.Generic;
public partial class SampleClassFlavorOne
{
public int Id {get; set;}
public string PropertyOne {get; set;}
public string Property2 {get; set;}
public DateTime Property3 {get; set;}
}
}
// Sample of generic class I created
public class GenericSampleClass{
public int Id {get; set;}
public string PropertyOne {get; set;}
public string Property2 {get; set;}
public DateTime Property3 {get; set;}
}
My routine:
private static void CopyFlavorToGenericList<T1, T2>(List<T1> fromList, List<T2> toList){
foreach (var t in fromList)
{
//(As you can see, I have tried entering the foreach loop a both ways
//foreach (var p in typeof(T1).GetProperties())
foreach (var p in typeof(T2).GetProperties())
{
if (p != null && p.CanWrite)
{
dynamic newObject = null;
p.SetValue((T2)newObject, p.GetValue(t, null), null);
}
}
toList.Add(toObject);
}
}
Implementing the routine:
switch (flavor){
case "FlavorOne":
List<SampleClassFlavorOne> _baseFlavor = db.SampleClassFlavorOne.ToList();
List<GenericSampleClass> _genericFlavor = new List<GenericSampleClass>();
CopyFlavorToGenericList<SampleClassFlavorOne, GenericSampleClass>(_baseFlavor, _genericFlavor);
break;
}
No matter what I try, I always get:
An exception of type 'System.Reflection.TargetException' occurred in mscorlib.dll but was not handled in user code. Additional information: Object does not match target type.
I cannot figure out what I am missing.
Any help appreciated, thanks!
Upvotes: 1
Views: 1044
Reputation: 70671
Your call to GetProperties()
gets an array of PropertyInfo
objects that apply to that specific type. Thus, when you call GetValue()
, you are trying to get the value from an object of the wrong type.
I.e. T2
, the type used to get the PropertyInfo
object, is GenericSampleClass
, but the type of the object you pass to the GetValue()
method is SampleClassFlavorOne
. In your alternative, getting the properties from T1
, you have the same problem, but with the SetValue()
method, passing (in theory…but not really, see "Note:" below) an object of type GenericSampleClass
, when the PropertyInfo
object came from the SampleClassFlavorOne
type.
To do this correctly, you need to get the PropertyInfo
objects from both classes, and use them with the objects of the appropriate type. For example:
private static void CopyFlavorToGenericList<T1, T2>(List<T1> fromList, List<T2> toList) where T2 : new()
{
var map = from p1 in typeof(T1).GetProperties()
join p2 in typeof(T2).GetProperties()
on p1.Name equals p2.Name
select new { From = p1, To = p2 };
foreach (var t in fromList)
{
T2 toObject = new T2();
foreach (var copyItem in map)
{
if (copyItem.To.CanWrite)
{
copyItem.To.SetValue(toObject, copyItem.From.GetValue(t));
}
}
toList.Add(toObject);
}
}
Note: you also had an issue with how you were creating the new object. I don't even know what you meant, using dynamic
like that, but it wouldn't have worked. You were just passing null
as the value of the destination object, which doesn't do anything useful.
You need to be able to create instances of the destination object as needed, and the way to do that in the generic method is to add the new()
constraint to the generic type parameter to require the destination type has a parameterless construction, so that you can in fact use the expression new T2()
to create a new instance of the object.
Upvotes: 1