I have a class that helps me read data from an MS SQL database into a list of objects. For the most part it's pretty straightforward; I can assume the property name of the class matches the column name of the table and just assign it accordingly, but sometimes I need to be able to transform data.
I have created a custom attribute to put on my class properties:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class TransformDataAttribute : Attribute
{
public Func<object, string, object> TransformThisData { get; set; }
}
Now, let's say I want to create the Func on the fly, like this:
[TransformData(TransformThisData = new Func<object, string, object>((v, p) => "My name is " + v.ToString()))]
public string Name { get; set; }
The error that I am seeing is 'TransformThisData' is not a valid named attribute argument because it is not a valid attribute parameter type.
What is the best way to accomplish Func as a property attribute?
Well, here's the best I have been able to come up with.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class TransformDataAttribute : Attribute
{
public string TransformDataClass { get; set; }
// This method must contain these parameters: (object value, PropertyInfo pi)
public string TransformDataMethod { get; set; }
}
I put it on the class property, like so...
public class Tracker
{
[TransformData(TransformDataClass = "CompanyTracker.DataTransformation", TransformDataMethod = "FunkyData")]
public string FunkyData { get; set; }
}
I can have a single data transformation class with different methods of transformation:
public class DataTransformation
{
public object FunkyData(object value, PropertyInfo pi)
{
// if data is this, return that, blah, blah
return value;
}
}
A static utility method for interpreting the three parameters:
public static object CallThisMethod(string className, string methodName, object[] parms)
{
Type type = Type.GetType(className);
MethodInfo theMethod = type.GetMethod(methodName);
object classInstance = Activator.CreateInstance(type);
return theMethod.Invoke(classInstance, parms);
}
...and then in my ADO Helper code, when it comes to assigning values to properties:
TransformDataAttribute attr = Utility.GetPropertyAttribute<TransformDataAttribute>(pi);
if (attr != null)
{
object[] parms = new object[] { value, pi };
value = Utility.CallThisMethod(attr.TransformDataClass, attr.TransformDataMethod, parms);
}
pi.SetValue(t, value, null);
It works. I hate to depend on Reflection of embedded strings for classes and methods, and it just doesn't seem like good design, but sometimes you just have to get things done. If anyone has a more elegant way to do this I'd be glad to hear about it.
Related
I am trying to find a way to take a class's property and pass it to a method along with another variable to update the property based on conditions. For example
The class
public class MyClass{
public string? Prop1 { get; set; }
public string? Prop2 { get; set; }
public bool? Prop3 { get; set; }
public DateTime? Prop4 { get; set; }
... etc...
}
Test code I would like to get to work...:
var obj = new MyClass();
MyCheckMethod(ref obj.Prop1, someCollection[0,1]);
in the method:
private void MyCheckMethod(ref Object obj, string value)
{
if (!string.isnullorempty(value))
{
// data conversion may be needed here depending on data type of the property
obj = value;
}
}
I want to be able to pass any property of any class and update the property only after validating the value passed in the method. I was hoping I could do this with generics, but I haven't yet found a way to do so. Or if I am over complicating what I need to do.
The problem is that there may be a bit more to the validation of the passed in value than just a simple isnullorempy check.
I also thought about doing something like this:
private void MyCheckMethod(ref object obj, Action action)
Then I could do something like this:
...
MyCheckMethod(ref obj.Prop1, (somecollection[0,1]) => {
... etc....
})
So I am looking for some guidance on how to proceed.
updated info:
The incoming data is all in string format (this is how a 3rd party vendor supplies the data). The data is supplied via API call for the 3rd party product... part of their SDK. However in my class I need to have proper data types. Convert string values to datetime for dates, string values to int for int data types, etc... . The other caveat is that if there isnt a valid value for the data type then the default value of the property should be NULL.
Additional Information:
The incoming data is always in string format.
eg:
I have to update a boolean property.
The incoming value is "". I test to see if the string Value isNullOrEmpty. It is so I dont do anything to property.
The next property datatype is decimal.
The incoming value is "0.343".
I Test to see if the string value is NullorEmpty. It isnt so I can update the property once I do a convert etc.....
Hope this helps.
Thanks
Full solution after edits:
public static class Extensions
{
//create other overloads
public static void MyCheckMethodDate<TObj>(this TObj obj,Expression<Func<TObj,DateTime>> property, string value)
{
obj.MyCheckMethod(property, value, DateTime.Parse);
}
public static void MyCheckMethod<TObj,TProp>(this TObj obj,Expression<Func<TObj,TProp>> property, string value,Func<string, TProp> converter)
{
if(string.IsNullOrEmpty(value))
return;
var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
if(null != propertyInfo && propertyInfo.CanWrite)
{
propertyInfo.SetValue(obj, converter(value));
}
}
}
public class Obj
{
public object Prop1{get;set;}
public string Prop2{get;set;}
public DateTime Prop3{get;set;}
}
public class Program
{
public static void Main()
{
var obj = new Obj();
obj.MyCheckMethodDate(x=>x.Prop3, "2018-1-1");
Console.WriteLine(obj.Prop3);
}
}
You can pass a lambda expression:
void DoSomething<T>(Expression<Func<T>> property)
{
var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
}else{
var name = ((MemberExpression)property.Body).Member.Name;
var value = property.Compile();
//Do whatever you need to do
}
}
To use:
DoSomething(() => obj.Property1);
You can't pass a reference to an arbitrary property. A property is basically implemented as two methods, set_Property and get_Property, and there's no way to bundle these together.
One option is to have your checker function take delegates to access the property. For example:
private void MyCheckMethod(Func<string> getter, Action<string> setter)
{
var value = getter();
var newValue = value.ToUpper();
setter(value);
}
So now you would say something like this:
public class MyClass
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
var c = new MyClass();
MyCheckMethod(() => c.Prop1, value => c.Prop1 = value);
Use reflection with compiled expressions.
This performs better than reflection and a little bit slower than native code.
It's not type safe, but you can add runtime validation.
I want to implement this method from MOQ. (Little out of my depth here)
ISetup<T> Setup(Expression<Action<T>> expression);
public class Foo {
public string Bar { get; set; }
public int Baz { get; set; }
}
public class MyCoolClass
{
public ? Evaluate<Expression<Action>>(expression);
//I want to be able to access and test the value of Foo.Bar (see below)
}
public class ClientOfMyClass
{
public void UseTheMethod()
{
MyCoolClass myCool = new MyCoolClass();
bool result = myCool.Evaluate<Foo>(f => f.Bar);
}
}
Basically, I am trying to write a method that will allow the caller to specify a property on an object with an expression, and allow me to test the value of that property and do something with it.
You want to use an Expression<Func<>> parameter, and check that it contains a Body, and a Member of type PropertyInfo, and use GetValue() passing your object in.
public static void Evaluate<TObj,TProp>(
this TObj obj,
Expression<Func<TObj, TProp>> expr)
{
var prop = (expr.Body as MemberExpression)?.Member as PropertyInfo;
var val = prop?.GetValue(obj);
if (val != null) {
//Do something
}
}
Note that the above code requires the passed in lambda to point to a Property. If you want to handle Fields as well as Methods, they will come in as different types of Expressions, and you'll want to handle handle them slightly differently. For more context and usage, here's a Fiddle.
Edit: Updated to work with other property types.
I have a property of the "Form1" class called "InfoTest" that has some custom attributes that I want to access.
The code works fine, but is a bit unwieldy:
[Test("foo",15)]
public double InfoTest { get; set; }
public void RetrieveAttribute()
{
PropertyInfo field_info = typeof(Form1).GetProperty("InfoTest");
object[] custom_attributes = field_info.GetCustomAttributes(typeof(TestAttribute), false);
TestAttribute thisAttribute = (TestAttribute)custom_attributes[0];
Debug.WriteLine(thisAttribute.Info + "," + thisAttribute.TheValue);
}
I presume the answer is "No", but is there a simpler way of getting the attributes for InfoTest, that doesn't involve the `typeof(Form1).GetProperty("InfoTest")? I can't go (for example):
var runtimePropInfo = InfoTest.GetType().GetRuntimeProperties();
var propInfo = InfoTest.GetType().GetProperties();
...Which is essentially because it is trying to get the properties of a "double", not a "InfoTest" object.
Just relying on built-in functions, you can simplify it a bit by using an extension method on MemberInfo that can directly return a custom attribute.
PropertyInfo field_info = typeof(Form1).GetProperty("InfoTest");
TestAttribute thisAttribute = field_info.GetCustomAttribute<TestAttribute>(false);
Debug.WriteLine(thisAttribute.Info + "," + thisAttribute.TheValue);
That gets rid of an object array, and a type-cast.
If you are willing to use a custom extension method, you can use this one to simplify it to one function call. It has the benefit of being strongly typed, though it may not be as performant.
public static class ReflectionExtensions
{
public static TAttribute GetAttribute<TAttribute, TClass>(this TClass target, Expression<Func<TClass, object>> targetProperty) where TAttribute : Attribute
{
var lambda = (LambdaExpression) targetProperty;
var unaryExpression = (UnaryExpression) lambda.Body;
string name = ((MemberExpression) unaryExpression.Operand).Member.Name;
MemberInfo info = typeof(TClass).GetProperty(name);
return info.GetCustomAttribute<TAttribute>(false);
}
}
It can be used on anything (it's an extension of object) like this:
var attr = thing.GetAttribute<TestAttribute, Thing>(obj => obj.InfoTest);
It gets the TestAttribute from the InfoTest property of thing, which is an instance of the Thing class.
Thing is defined as:
public class Thing
{
[Test("foo", 15)]
public double InfoTest { get; set; }
}
I've got a class which represents a dynamic table which is going to be created in a sql database.
public class DynamicTable
{
public List<DynamicField> fieldNames { get; set; }
}
The DynamicField represents a pair of the field name and it's dataType:
public class DynamicField
{
public Type type {get;set;}
public String fieldName {get;set;}
public DynamicField(String fieldName, Type datatype)
{
this.fieldName = fieldName;
this.type = datatype;
}
}
My worries are about, that you could pass any Type you want but I would only need some specific datatypes like String, int, date,float.
How could I improve my code, that it's only possible to pass them?
Based on the idea to use the attribute type to correctly create the table in my sql database, is there a better way than using the System.Type type?
I guess you could put a constraint with Generic, like below. But I also guess it will be quite unusable in your dynamic scenario ( and it won't accept nullable types)
public class DynamicField
{
public String fieldName { get; set; }
private Type _type;
public DynamicField SetFieldType<T>() where T:struct
{
this._type = typeof(T);
return this;
}
public Type GetFieldType()
{
return this._type;
}
public DynamicField(String fieldName)
{
this.fieldName = fieldName;
}
}
You could then write something like :
var t = new DynamicTable();
t.Add(new DynamicField("ColumnA").SetFieldType<String>());
Maybe you could just check in the type setter that value.IsValueType is true, and throw an exception if false.
Hope this will help,
I have a customer class with a sub-class address
internal class Customer
{
public int id { get; set; }
public string name { get; set; }
[ObjectDefRelation(isSubClass = true)]
public Addressinformation Addressinformation { get; set; }
}
internal class Addressinformation
{
public string street { get; set; }
}
I have a Method to fill this object with data from a xml. Now I want to call this method recursive when its arrive the sub-class Addressinformation. How can I call my generic method with informations from PropertyInfo?
public static T ConvertXmlToClass<T>(XmlDocument xmlDocumentObjectDef, XmlNode xmlNode, ObjectDefRelationAttribute parentClass = null) where T : new()
{
ObjectDefRelationAttribute defRelationAttribute;
T xmlToClass = new T();
foreach (PropertyInfo field in xmlToClass.GetType().GetProperties())
{
foreach (Attribute attr in field.GetCustomAttributes(true))
{
defRelationAttribute = attr as ObjectDefRelationAttribute;
if (null != defRelationAttribute)
{
if (defRelationAttribute.isSubClass)
{
//
// here I need help to call the recursive method (XXX)
//
var subClass = Helper.ConvertXmlToClass<XXX>(xmlDocumentObjectDef, xmlNode, defRelationAttribute);
}
}
}
}
}
I used the best answer with some modification:
Type typeArguments = GetType(field.PropertyType.Namespace + "." + field.PropertyType.Name);
object value = typeof(Helper).GetMethod("ConvertXmlToClass").MakeGenericMethod(typeArguments).Invoke(null, new object[] {xmlDocumentObjectDef, xmlNode, defRelationAttribute});
It seems that you've got a function that converts Type names to Types, something like this:
Type GetType(string typeName)
{
return Type.GetType(typeName);
}
then you can call this method as:
object value = typeof(owningType).GetMethod("ConvertXmlToClass").MakeGenericMethod(GetType(typeName)).Invoke(xmlDocumentObjectDef, xmlNode, xmlToClass);
and Use PropertyInfo.SetValue() to set it on the property
If you want to stick with your current approach then you need to use reflection to built the generic method call from the field.PropertyType as described here: Reflection and generic types
However you could also consider changing your method to accept a Type as parameter instead of making a generic method (hint you can use Activator.CreateInstance(type) to instantiate an object).