2016-07-08 6 views
0

J'ai écrit le code suivant pour gérer les paramètres de mappage de ma base de données à mes types de données (croyez-moi, je voudrais pouvoir utiliser un ORM std mais ce n'est pas faisable pour de nombreuses raisons)Comment définir un paramètre tout en utilisant l'expression <Func<T>>

public void LoadDatabaseValue<T>(DataTable partData, string identifier, string mappingName, Expression<Func<T>> mappingProperty) 
    { 
     var partAttributeValue = mappingProperty.Name; 
     var memberExpression = (MemberExpression)mappingProperty.Body; 
     var prop = (PropertyInfo)memberExpression.Member; 
     try 
     { 
      var selectedRow = partData.Select($"partattributename = '{mappingName}'"); 
      var selectedValue = selectedRow[0]["PartAttributeValue"]; 

      var typedOutput = (T)Convert.ChangeType(selectedValue, typeof(T)); 

      prop.SetValue(memberExpression.Expression, typedOutput, null); 
     } 
     catch (Exception exception) 
     { 
      _databaseImportError = true; 
      // code to log this error 
    } 

Lorsque je tente d'exécuter ce que je reçois l'exception suivante

{System.Reflection.TargetException: Object does not match target type. 
at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) 
at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) 
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) 
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) 
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index) } 

Quand je débugger mes lignes typedOutput avec mon type de propriété, donc je ne sais pas pourquoi il jette cette exception.

J'appelle avec par exemple

LoadDatabaseValue(partData, identifier, "Offset",() => Offset); 
+0

Vous passez un 'Func' mais attend que ce soit' expression > '. Changez l'un d'entre eux au même type. – Venky

+1

@Venky Il passe dans une expression. – Servy

Répondre

1

Si vous voulez garder votre conception actuelle de la méthode, vous avez besoin d'une manière d'évaluer en quelque sorte le memberExpression.Expression afin de pouvoir appeler SetValue méthode.

changer Ainsi, la ligne

prop.SetValue(memberExpression.Expression, typedOutput, null); 

à

prop.SetValue(Evaluate(memberExpression.Expression), typedOutput, null); 

puis utilisez l'une des implémentations suivantes:

(A) Ce sera suffisant si vous utilisez uniquement accesseurs de propriété:

static object Evaluate(Expression e) 
{ 
    if (e == null) return null; 
    var me = e as MemberExpression; 
    if (me != null) 
     return ((PropertyInfo)me.Member).GetValue(Evaluate(me.Expression), null); 
    return ((ConstantExpression)e).Value; 
} 

(B) Celui-ci est plus universel, mais plus lent:

static object Evaluate(Expression e) 
{ 
    if (e == null) return null; 
    return Expression.Lambda(e).Compile().DynamicInvoke(); 
} 
0

Le premier paramètre de SetValue doit être un objet contenant la propriété dont la valeur que vous souhaitez définir.

var obj = new TEntity(); 
prop.SetValue(obj, typedOutput); // From .NET 4.5 there is an overload with just 2 parameters 

Maintenant obj.Offset devrait avoir la valeur désirée.

Donc, il y a deux types impliqués: Le type de l'objet contenant la propriété et le type de la propriété elle-même (par exemple int, string etc.).

Par conséquent, votre expression devrait être comme ceci:

Expression<Func<TEntity, object>> mappingProperty 

TEntity est le type de l'objet et object est le type encore inconnu d'une propriété de cet objet. À moins que vous connaissez le type de la propriété à l'avance, dans ce cas, vous auriez

Expression<Func<TEntity, TProperty>> mappingProperty 

Vous appelleriez comme ceci:

LoadDatabaseValue(partData, identifier, "Offset", x => x.Offset); 

Vous devez modifier le type comme celui-ci (à moins que le selectedValue est déjà du type correct):

object typedOutput = Convert.ChangeType(selectedValue, prop.PropertyType); 
+0

J'ai essayé de l'implémenter mais la signature LoadDatabaseValue (partData, identifier, "Offset", x => x.Offset); ne compilerait pas. Quel est l'avantage de cette méthode par rapport à l'autre solution présentée? – PlTaylor

+0

Le type 'TEntity' doit bien sûr avoir une propriété' Offset'. Vous essayez de définir la valeur de 'memberExpression.Expression'. Mais cette expression ne contient que des méta-informations sur l'expression réelle et ne représente pas l'objet réel lui-même. Je ne comprends pas complètement ce que vous essayez de faire, mais je voudrais déclarer la méthode comme ceci: 'public void LoadDatabaseValue (DataTable partData, entité TEntity, chaîne mappingName, Expression > mappingProperty)' et appel 'prop.SetValue (entity, typedOutput);' afin de remplir une propriété de l'objet entité. –

+0

Et aussi 'typedOutput' doit contenir une valeur du bon type, mais peut lui-même être tapé comme' object', puisque le type de paramètre dans 'SetValue' est' object' de toute façon. –