rI know two tools : Value injector and Automapper. Each of them is currently supporting one of the feature, that i realy need. :) Background: i'm using stored procedure to load data from DB. relations 1 to many are handled as XML properties. Let's consider following example
public class AutorDto
{
public string Name { get; set; }
public List<Post> Posts { get; set; }
public ICity City { get; set; }
}
public interface ICity
{
string Name { get; set; }
string Code { get; set; }
}
public class CityDto
{
public string Name { get; set; }
public string Code { get; set; }
}
public class PostDto
{
public DateTime Date { get; set; }
public string Content { get; set; }
}
I have stored procedure, that will return me this structure in following schema :
public class Autor_From_Stored_Procedure
{
public string Name { get; set; }
public string Posts { get; set; }
public string CityName { get; set; }
public string CityCode { get; set; }
}
In order to map Autor_From_Stored_Procedure to AutorDto we must handle two topics:
Deserializing from XML (i've managed to handle this problem using automapper. I've used this topic : Automapper to create object from XML) and this article : http://www.codeproject.com/Articles/706992/Using-AutoMapper-with-Complex-XML-Data
Unflattening. It seems, that AutoMapper is not supporting convention based unflattening. Is, that true ? I saw, that there is posibility to declare some string (e.g. "City") as Unflattened object and everything should work - but value injector offers this as a convetion based standard:
objectToIll.InjectFrom < UnflatLoopInjection>(object) - without neccessity to declare which properties [names in specific] would be deflattened) Is this also possible with automapper ?
If not, then maybe i should focus on value injector. If so - the problem from the 1) point is still valid (hopefully it is solved as easly as in automapper)
Penny for your thoughts!
##Update
I've changed Dto definitions adding interface to City (as this is my case)
I will post anwser to my question - maybe it will help someone. I've moved from Automapper to Valueinjecter. Xml binding was done using custom AddMap() and using XmlSerializer (no magic here)
But it turns out, that there will be problem with Interface on "Autor" class (and deflattening) You cannot simply create interface instance and this was the problem. I've modified slightly valueinjecter Tunnelier class to handle such case.
Firstly i've tried to cover this using somehow convention (if you find property ISomething, cut "I" add "Dto" But it is not elegant solution. So i ended up with custom : Inteface+Class mapping (so i'm pointing out : if you see ISomething interface, create instance of SomethingDto) Here is the modified code (maybe this could be added to valueinjecter implementation ?) The main "Mapper" class is not partial so i've defined new class for those mappings:
namespace Omu.ValueInjecter
{
public partial class MapperActivations
{
public static ConcurrentDictionary<Type, Type> InterfaceActivations = new ConcurrentDictionary<Type, Type>();
public static void AddInterfaceActivation<Interface, Class>()
{
InterfaceActivations.AddOrUpdate(typeof(Interface), typeof(Class), (key, oldValue) => typeof(Class));
}
}
}
Here are modified valueInjected classes:
public static class TunnelierCustom
{
public static PropertyWithComponent Digg(IList<string> trail, object o)
{
var type = o.GetType();
if (trail.Count == 1)
{
return new PropertyWithComponent { Component = o, Property = type.GetProperty(trail[0]) };
}
var prop = type.GetProperty(trail[0]);
var val = prop.GetValue(o);
if (val == null)
{
if (prop.PropertyType.IsInterface)
{
if (MapperActivations.InterfaceActivations.ContainsKey(prop.PropertyType))
{
val = Activator.CreateInstance(MapperActivations.InterfaceActivations[prop.PropertyType]);
}
else
{
throw new Exception("Unable to create instance of: " + prop.PropertyType.Name + ". Are you missing InterfaceActivations bidning? Please add it using MapperActivations.AddInterfaceActivation<Interface, Class>() statement");
}
}
else
{
val = Activator.CreateInstance(prop.PropertyType);
}
prop.SetValue(o, val);
}
trail.RemoveAt(0);
return Digg(trail, val);
}
public static PropertyWithComponent GetValue(IList<string> trail, object o, int level = 0)
{
var type = o.GetType();
if (trail.Count == 1)
{
return new PropertyWithComponent { Component = o, Property = type.GetProperty(trail[0]), Level = level };
}
var prop = type.GetProperty(trail[0]);
var val = prop.GetValue(o);
if (val == null) return null;
trail.RemoveAt(0);
return GetValue(trail, val, level + 1);
}
}
/// <summary>
/// performs flattening and unflattening
/// first version of this class was made by Vadim Plamadeala ☺
/// </summary>
public static class UberFlatterCustom
{
public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target, Func<string, PropertyInfo, bool> match, StringComparison comparison)
{
var trails = TrailFinder.GetTrails(flatPropertyName, target.GetType().GetProps(), match, comparison, false).Where(o => o != null);
return trails.Select(trail => TunnelierCustom.Digg(trail, target));
}
public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target, Func<string, PropertyInfo, bool> match)
{
return Unflat(flatPropertyName, target, match, StringComparison.Ordinal);
}
public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target)
{
return Unflat(flatPropertyName, target, (upn, pi) => upn == pi.Name);
}
public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source, Func<string, PropertyInfo, bool> match)
{
return Flat(flatPropertyName, source, match, StringComparison.Ordinal);
}
public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source, Func<string, PropertyInfo, bool> match, StringComparison comparison)
{
var trails = TrailFinder.GetTrails(flatPropertyName, source.GetType().GetProps(), match, comparison).Where(o => o != null);
return trails.Select(trail => TunnelierCustom.GetValue(trail, source));
}
public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source)
{
return Flat(flatPropertyName, source, (up, pi) => up == pi.Name);
}
}
public class UnflatLoopCustomInjection : ValueInjection
{
protected override void Inject(object source, object target)
{
var sourceProps = source.GetType().GetProps();
foreach (var sp in sourceProps)
{
Execute(sp, source, target);
}
}
protected virtual bool Match(string upn, PropertyInfo prop, PropertyInfo sourceProp)
{
return prop.PropertyType == sourceProp.PropertyType && upn == prop.Name;
}
protected virtual void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
{
tp.SetValue(target, sp.GetValue(source));
}
protected virtual void Execute(PropertyInfo sp, object source, object target)
{
if (sp.CanRead)
{
var endpoints = UberFlatterCustom.Unflat(sp.Name, target, (upn, prop) => Match(upn, prop, sp)).ToArray();
foreach (var endpoint in endpoints)
{
SetValue(source, endpoint.Component, sp, endpoint.Property);
}
}
}
}
And this is example use :
MapperActivations.AddInterfaceActivation<ICity, City>();
Mapper.AddMap<Auto_From_Stored_Procedure, AutorDto>(src =>
{
var res = new User();
res.InjectFrom<UnflatLoopCustomInjection>(src);
res.Posts = Mapper.Map<XElement, List<Posts>>(src.PostsXml , "PostDto"); //this is mapping using XmlSerializer, PostsXml is XElement.Parse(Posts) in Autor_From_Stored_Procedure.
return res;
});
new version has been released 3.1
you can now specify an activator parameter for the UnflatLoopInjection
have a look at this unit test
Related
I am creating a project that will manage app configurations. It will be very generic, reusable across different apps (with different config models on each) and very flexible - including the ability to create/save/store/read/merge partial configurations from multiple sources.
Without getting too much into details, here's an example of what I need to do.
I have a class like below:
public class TestConfigModel
{
public int SomeIntValue { get; set; }
public string SomeStringValue { get; set; }
public TestConfigSubsection Subsection { get; set; }
}
public class TestConfigSubsection
{
public System.DayOfWeek SomeSubsectionEnumValue { get; set; }
public Guid SomeSubsectionGuidValue { get; set; }
}
I need to dynamically generate a version of this model that has all properties nullable (unless they already take a null):
public class TestConfigModelNullable
{
public int? SomeIntValue { get; set; }
public string SomeStringValue { get; set; } // already takes a null
public TestConfigSubsection Subsection { get; set; } // already takes a null
}
public class TestConfigSubsectionNullable
{
public System.DayOfWeek? SomeSubsectionEnumValue { get; set; }
public Guid? SomeSubsectionGuidValue { get; set; }
}
Example use:
I have a default (complete) config like so:
var aConfigInstance = new TestConfigModel()
{
SomeIntValue = 3,
SomeStringValue = "hey",
Subsection = new TestConfigSubsection()
{
SomeSubsectionEnumValue = DayOfWeek.Thursday,
SomeSubsectionGuidValue = Guid.Parse("{2C475019-5AAC-43C6-AC87-21947A40E3B7}")
}
};
Now, I need to be able to create, serialize, store and later deserialize and operate on a partial configuration model, like below:
var aPartialConfigInstance = new TestConfigModelNullable()
{
SomeIntValue = 4,
Subsection = new TestConfigSubsection()
{
SomeSubsectionEnumValue = DayOfWeek.Monday
}
};
... with all missing properties null. If I try to do the same with the original class, all the other non-nullable fields will receive default values and that would be bad (how do I tell if int value of 0 is intended or not? Maybe it makes sense for the consumer app).
However, I'm new to reflection in general and not sure how to approach this. Your help would be much appreciated :)
Recall that we don't know the model ahead of time.
I happened to implement several similar mechanisms in several different flavors. Implementing an "automagical" mechanism implies quite a bit of heavy lifting.
Here I wouldn't suggest generating separate nullable versions of the models. Instead, I would opt for making all model properties Optional<T>, which is like Nullable<T> but works for reference types as well. In this way, partial models will be represented with the same types as "base" models.
Such an approach will save the complexity of code generation (T4, Roslyn, CodeDom, or Reflection.Emit -- all these imply a lot of effort, including plugging them into the build process).
In addition, in either approach, a "merging" logic must be implemented which applies a partial model over a "base" one. In code generation approach, the merge logic can be generated as part of the nullable models. In Optional<T> approach, it can be either hard-coded or implemented in generic way with runtime Reflection (not Reflection.Emit). The hard-coded way appears to be the easiest, but for large number of models and properties, runtime Reflection may be a better fit.
How it will look
The models would look like this:
public class TestConfigModel
{
public Optional<int> SomeIntValue { get; set; }
public Optional<string> SomeStringValue { get; set; }
public Optional<TestConfigSubsection> Subsection { get; set; }
}
With the implicit conversion operators of Optional<T>, you'll be able to initialize section values as normally:
var config = new TestConfigModel {
SomeIntValue = 123,
SomeStringValue = "ABC",
Subsection = new TestConfigSubsection {
SomeSubsectionEnumValue = DayOfWeek.Thursday
}
};
Generic merging logic can be implemented by introducing an Apply method to Optional<T>:
Optional<T> Apply(Optional<T> partial, Func<T, T, Optional<T>> merge = null)
Every model will have to implement its own ApplyXxxx() method that will be passed in the merge parameter, like this:
public class TestConfigModel
{
// ...properties
public Optional<TestConfigModel> ApplyModel(TestConfigModel partial)
{
SomeIntValue = SomeIntValue.Apply(partial.SomeIntValue);
SomeStringValue = SomeStringValue.Apply(partial.SomeStringValue);
Subsection = Subsection.Apply(
partial.Subsection,
merge: (left, right) => left.ApplySubsection(right));
return this;
}
}
public class TestConfigSubsection
{
// ...properties
public Optional<TestConfigSubsection> ApplySubsection(TestConfigSubsection partial)
{
SomeSubsectionEnumValue = SomeSubsectionEnumValue.Apply(partial.SomeSubsectionEnumValue);
SomeSubsectionGuidValue = SomeSubsectionGuidValue.Apply(partial.SomeSubsectionGuidValue);
return this;
}
}
Optional<T>
Built-in implementation of Optional<T> is planned for C# 8, but it can be implemented easily (mostly similar to Nullable<T>).
public interface IOptional
{
bool HasValue { get; }
object Value { get; }
}
public struct Optional<T> : IOptional
{
private readonly bool _hasValue;
private readonly T _value;
public Optional(T value)
{
_value = value;
_hasValue = true;
}
public bool HasValue => _hasValue;
object IOptional.Value => Value;
public T Value
{
get
{
if (!_hasValue)
{
throw new InvalidOperationException("has no value");
}
return _value;
}
}
public T GetValueOrDefault() => _value;
public T GetValueOrDefault(T defaultValue)
{
if (!_hasValue)
{
return defaultValue;
}
return _value;
}
public bool IsNullValue => _hasValue && ReferenceEquals(_value, null);
public override bool Equals(object other)
{
if (other is Optional<T> otherOptional)
{
if (_hasValue != otherOptional.HasValue)
{
return false;
}
if (_hasValue)
{
return CompareValue(otherOptional.Value);
}
return true;
}
return false;
}
bool CompareValue(object otherValue)
{
if (_value == null)
{
return (otherValue == null);
}
return _value.Equals(otherValue);
}
public override int GetHashCode()
{
if (_hasValue || ReferenceEquals(_value, null))
{
return 0;
}
return _value.GetHashCode();
}
public override string ToString()
{
if (!_hasValue || ReferenceEquals(_value, null))
{
return "";
}
return _value.ToString();
}
public Optional<T> Apply(Optional<T> partial, Func<T, T, Optional<T>> merge = null)
{
if (!_hasValue && partial.HasValue)
{
return partial;
}
if (_hasValue && partial.HasValue)
{
if (ReferenceEquals(_value, null))
{
return partial.Value;
}
if (!ReferenceEquals(partial.Value, null))
{
if (merge != null)
{
return merge(_value, partial.Value);
}
throw new InvalidOperationException("both values exist and merge not provided");
}
}
return this;
}
public static implicit operator Optional<T>(T value)
{
return new Optional<T>(value);
}
public static explicit operator T(Optional<T> value)
{
return value.Value;
}
}
Serialization
The last thing left is to teach the serializers to handle Optional<T>. For instance, Newtonsoft.Json would require a custom JsonConverter. Below isn't a complete implementation, but it demonstrates the approach:
public class OptionalConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Optional<>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// TODO: implement properly
// roughly the approach is like this:
var hasValue = reader.ReadAsBoolean().GetValueOrDefault();
var innerValue = hasValue
? serializer.Deserialize(reader, objectType.GetGenericArguments([0])
: null;
return Activator.CreateInstance(
objectType,
innerValue != null ? new[] {innerValue} : new object[0]);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is IOptional optional)
{
// TODO: implement writing
}
}
}
// Just for convenience
public Type CreateNullableTypeFrom<T>()
{
return CreateNullableTypeFrom(typeof(T));
}
public Type CreateNullableTypeFrom(Type typeToConvert)
{
// Get the AssemblyName where the type is defined
AssemblyName assembly = typeToConvert.Assembly.GetName();
AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run);
ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule(assembly.Name);
TypeBuilder typeBuilder = dynamicModule.DefineType(typeToConvert.Name + "Nullable");
// Loop through the properties
foreach(PropertyInfo property in typeToConvert.GetProperties())
{
// If property is value type, it can't be null
if(property.PropertyType.IsValueType)
{
// Create a nullable type for the property
typeBuilder.DefineProperty(property.Name, property.Attributes, typeof(Nullable<>).MakeGenericType(property.PropertyType), Type.EmptyTypes);
}
// The property can be null
else
{
// Create a similar property
typeBuilder.DefineProperty(property.Name, property.Attributes, property.PropertyType, Type.EmptyTypes);
}
}
// Finally, create the type
Type convertedType = typeBuilder.CreateType();
Console.WriteLine(convertedType.Name);
// Note: to access the properties of the converted type through reflection,
// use GetRuntimeProperties method, not GetProperties, since GetProperties
// will return an empty array because the type was created an runtime
return convertedType;
}
Ive got what I think may be an unusual problem (Ive searched around a lot for an answer, but I dont think Ive found one).
I have messages that are read from a queue and depending on the message type contains a payload that needs to be deserialized into a concrete c# class. This needs to eventually be concrete (I cant use generics the whole way) because Im using Expression Trees to perform Evaluations on the classes that arrive from the queue.
The base class looks like this:
public abstract class BaseRuleMessage<T>
{
public abstract Func<T, bool> CompileRule(Rule r, T msg);
public T Deserialize(ClientEventQueueMessage message)
{
return JsonConvert.DeserializeObject<T>(message.Payload);
}
public BaseRuleMessage()
{
RulesCompleted = new List<int>();
}
public IEnumerable<Rule> FilterRules(RuleGroup ruleGroup)
{
return ruleGroup.Rules.Where(item =>
!RulesCompleted.Any(r => r.Equals(item.Id)));
}
I implement the base class like this:
public class UiTransactionUpdate : BaseRuleMessage<UiTransactionUpdate>
{
public override Func<UiTransactionUpdate, bool> CompileRule(Rule r, UiTransactionUpdate msg)
{
var expression = Expression.Parameter(typeof(UiTransactionUpdate));
Expression expr = BuildExpr(r, expression, msg);
return Expression.Lambda<Func<UiTransactionUpdate, bool>>(expr, expression).Compile();
}
public Guid TransactionId { get; set; }
public Guid GroupId { get; set; }
public decimal StatusValue { get; set; }
I then do something like this to call:
switch (message.MessageType)
{
case "UI_UPDATE":
{
message.Payload = RemoveNullGroupIdReference(jsonPayload, message.Payload);
var deserializedMessage = new UiTransactionUpdate().Deserialize(message);
deserializedMessage.RulesCompleted = deserializedMessage.RulesCompleted ?? new List<int>();
foreach (var rule in deserializedMessage.FilterRules(ruleGroup))
{
What I really want to know is how can I create a factory (or can I?) to be able to define the implementation of the base class in such a way that I can return a concrete class to use for my expression tree evaluations without having to repeat all the calling code for each type.
I avoided using dynamic but this meant that I had pass the object around as an object. I prefer not to use dynamic but in this case, casting objects at run-time may not be any better.
I also had to change the code so that instead of returning a Func<T, bool>, there is a method that would execute the Func. This was to avoid referring to the generic class. I'm not sure if you actually need the Func in your actual implementation.
I had to create a new base class that wasn't generically typed.
// Horrible name, do change it to something more appropriate
public abstract class BaseBaseRuleMessage
{
public IList<int> RulesCompleted { get; set; }
public IEnumerable<Rule> FilterRules(RuleGroup ruleGroup)
{
return ruleGroup.Rules.Where(item =>
!RulesCompleted.Any(r => r.Equals(item.Id)));
}
public BaseBaseRuleMessage DeserializeToBaseBaseRuleMessage(ClientEventQueueMessage message)
{
return (BaseBaseRuleMessage) DeserializeToType(message);
}
protected abstract object DeserializeToType(ClientEventQueueMessage message);
public abstract bool ExecuteRule(Rule rule, object msg);
}
Updated the BaseRuleMessage to derive from BaseBaseRuleMessage (and moved some properties to the base class.
public abstract class BaseRuleMessage<T> : BaseBaseRuleMessage
where T : BaseRuleMessage<T>
{
public abstract Func<T, bool> CompileRule(Rule r, T msg);
protected override object DeserializeToType(ClientEventQueueMessage message)
{
return JsonConvert.DeserializeObject(message.Payload, typeof(T));
}
protected BaseRuleMessage()
{
RulesCompleted = new List<int>();
}
public override bool ExecuteRule(Rule rule, object msg)
{
var message = (T) msg;
if (message == null)
{
throw new InvalidOperationException();
}
return CompileRule(rule, message).Invoke(message);
}
}
The concrete class is basically the same. I've implemented my own BuildExpr to make sure the code can compile.
public class UiTransactionUpdate : BaseRuleMessage<UiTransactionUpdate>
{
public override Func<UiTransactionUpdate, bool> CompileRule(Rule r, UiTransactionUpdate msg)
{
var expression = Expression.Parameter(typeof(UiTransactionUpdate));
Expression expr = BuildExpr(r, expression, msg);
return Expression.Lambda<Func<UiTransactionUpdate, bool>>(expr, expression).Compile();
}
public Guid TransactionId { get; set; }
public Guid GroupId { get; set; }
public decimal StatusValue { get; set; }
private Expression BuildExpr(Rule rule, ParameterExpression parameterExpression, UiTransactionUpdate message)
{
var transactionIdProperty = Expression.Property(parameterExpression, "TransactionId");
var value = Expression.Constant(rule.TransactionId);
return Expression.Equal(transactionIdProperty, value);
}
}
To use it:
var messageTypeToTypeMap = new Dictionary<string, Func<BaseBaseRuleMessage>>
{
{"UI_UPDATE", () => new UiTransactionUpdate()}
};
var factoryFunc = messageTypeToTypeMap[message.MessageType];
message.Payload = RemoveNullGroupIdReference(jsonPayload, message.Payload);
var ruleMessage = factoryFunc.Invoke();
var deserializedMessage = ruleMessage.DeserializeToBaseBaseRuleMessage(message);
deserializedMessage.RulesCompleted = deserializedMessage.RulesCompleted ?? new List<int>();
foreach (var rule in deserializedMessage.FilterRules(ruleGroup))
{
var isTrue = deserializedMessage.ExecuteRule(rule, deserializedMessage);
}
I have two very simple objects:
public class CategoryDto
{
public string Id { get; set; }
public string MyValueProperty { get; set; }
}
public class Category
{
public string Id { get; set; }
[MapTo("MyValueProperty")]
public string Key { get; set; }
}
When mapping a Category to a CategoryDto with AutoMapper, I would like the following behavior:
The properties should be mapped as usual, except for those that have the MapTo attribute. In this case, I have to read the value of the Attribute to find the target property. The value of the source property is used to find the value to inject in the destination property (with the help of a dictionary). An example is always better that 1000 words...
Example:
Dictionary<string, string> keys =
new Dictionary<string, string> { { "MyKey", "MyValue" } };
Category category = new Category();
category.Id = "3";
category.Key = "MyKey";
CategoryDto result = Map<Category, CategoryDto>(category);
result.Id // Expected : "3"
result.MyValueProperty // Expected : "MyValue"
The Key property is mapped to the MyValueProperty (via the MapTo Attribute), and the assigned value is "MyValue", because the source property value is "MyKey" which is mapped (via dictionary) to "MyValue".
Is this possible using AutoMapper ? I need of course a solution that works on every object, not just on Category/CategoryDto.
I finally (after so many hours !!!!) found a solution.
I share this with the community; hopefully it will help someone else...
Edit: Note that it's now much simpler (AutoMapper 5.0+), you can do like I answered in this post: How to make AutoMapper truncate strings according to MaxLength attribute?
public static class Extensions
{
public static IMappingExpression<TSource, TDestination> MapTo<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
Type sourceType = typeof(TSource);
Type destinationType = typeof(TDestination);
TypeMap existingMaps = Mapper.GetAllTypeMaps().First(b => b.SourceType == sourceType && b.DestinationType == destinationType);
string[] missingMappings = existingMaps.GetUnmappedPropertyNames();
if (missingMappings.Any())
{
PropertyInfo[] sourceProperties = sourceType.GetProperties();
foreach (string property in missingMappings)
{
foreach (PropertyInfo propertyInfo in sourceProperties)
{
MapToAttribute attr = propertyInfo.GetCustomAttribute<MapToAttribute>();
if (attr != null && attr.Name == property)
{
expression.ForMember(property, opt => opt.ResolveUsing(new MyValueResolve(propertyInfo)));
}
}
}
}
return expression;
}
}
public class MyValueResolve : IValueResolver
{
private readonly PropertyInfo pInfo = null;
public MyValueResolve(PropertyInfo pInfo)
{
this.pInfo = pInfo;
}
public ResolutionResult Resolve(ResolutionResult source)
{
string key = pInfo.GetValue(source.Value) as string;
string value = dictonary[key];
return source.New(value);
}
}
This should be fairly straightforward using an implementation of IValueResolver and the ResolveUsing() method. You basically just need to have a constructor for the resolver that takes in the property info (or if you wanna be fancy that takes in a lambda expression and resolves the property info similar to How to get the PropertyInfo of a specific property?. Though I haven't tested it myself I imagine the following would work:
public class PropertyBasedResolver : IValueResolver
{
public PropertyInfo Property { get; set; }
public PropertyBasedResolver(PropertyInfo property)
{
this.Property = property;
}
public ResolutionResult Resolve(ResolutionResult source)
{
var result = GetValueFromKey(property, source.Value); // gets from some static cache or dictionary elsewhere in your code by reading the prop info and then using that to look up the value based on the key as appropriate
return source.New(result)
}
}
Then to set up that mapping you need to do:
AutoMapper.Mapper.CreateMap<Category, CategoryDto>()
.ForMember(
dest => dest.Value,
opt => opt.ResolveUsing(
src =>
new PropertyBasedResolver(typeof(Category.Key) as PropertyInfo).Resolve(src)));
Of course that is a pretty gross lamda and I would suggest that you clean it up by having your property resolver determine the property on the source object it should look at based on the attribute/property info so you can just pass a clean new PropertyBasedResolver(property) into the ResolveUsing() but hopefully this explains enough to put you on the right track.
Lets assume I have the following classes
public class foo
{
public string Value;
}
public class bar
{
public string Value1;
public string Value2;
}
You can pass a lambda to ResolveUsing:
.ForMember(f => f.Value, o => o.ResolveUsing(b =>
{
if (b.Value1.StartsWith("A"));)
{
return b.Value1;
}
return b.Value2;
}
));
I am thinking about making a custom attribute so that when we are using multiple data readers [SqldataReader] on different objects/tables, we could use the attribute to get the type of the property, and the "columnName" of the property. This way, we could then have a method that takes the data reader as a param, and from there could reflect the attributes to read in the columns. An example of what is currently being done is below, and then an example of what I am trying to accomplish. The problem I am having, is how to manage how to tell it what the (Type) is.
private static App GetAppInfo(SqlDataReader dr)
{
App app = new App();
app.ID = MCCDBUtility.GetDBValueInt(dr, "APPLICATION_ID");
app.Name = MCCDBUtility.GetDBValueString(dr, "APPNAME");
app.Desc = MCCDBUtility.GetDBValueString(dr, "APPDESCRIPTION");
app.Version = MCCDBUtility.GetDBValueString(dr, "APP_VERSION");
app.Type = MCCDBUtility.GetDBValueString(dr, "APPLICATIONTYPEID");
app.AreaName = MCCDBUtility.GetDBValueString(dr, "AREANAME");
return app;
}
What I am thinking though, so if I had a class for example like so:
[DataReaderHelper("MethodNameToGetType", "ColumnName")]
public string APPNAME {get;set;}
How could I go about this?
Fist of all, this is possible and if you like I could add a code sample.
But: This is not a good idea.
Why, you ask?
First - DataReader provides you with a method GetSchemaTable() which contains a property DataType which is a System.Type object. So basically you could create a MCCDBUtility.GetValue(dr, "columnName") that does the logic for your.
Second - What about you have a int property on your object but your datareader returns a decimal. For that case you can use Convert.ChangeType(value, type)
If you combine that you can achive what you want with
instance.Id = MCCDBUtility.GetValue<int>(dr, "columnName")
public T GetValue<T>(IDataReader reader, string columnName)
{
object value GetValue(reader, columnName);
return Convert.ChangeType(value, typeof(T));
}
private object GetValue(IDataReader reader, string columnName)
{
var schmema = reader.GetSchemaTable();
var dbType = typeof(object);
foreach(DataRowView row in schema.DefaultView)
if (row["columnName"].ToString().Equals(columnName, StringComparer.OrdinalIgnoreCase))
return row["ColumnType"];
if (dbType.Equals(typeof(int))
return GetInt(reader, columnName)
... // you get the point
else
return GetObject(reader, columnName);
}
And Third - Don't do this anyway there are great tools for mapping your query to your business objects. I don't want to name them all but a very lightweight and easy to understand is Dapper.NET, give it a try. https://github.com/StackExchange/dapper-dot-net
In combination with https://github.com/tmsmith/Dapper-Extensions you can easily map your database queries to your pocos
Update
As promised, here is the code for implementing on your own. Just create a Visual Studio Test project, insert the code and let it run. For readablity I omitted the unused IReadReader interface implementations, so you have to let intellisense create them for you.
Run the test and enjoy.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var values = new Dictionary<string, object>();
values.Add("ProductId", 17);
values.Add("ProductName", "Something");
values.Add("Price", 29.99M);
var reader = new FakeDataReader(values);
var product1 = new Product();
reader.SetValue(product1, p => p.Id);
reader.SetValue(product1, p => p.Name);
reader.SetValue(product1, p => p.Price);
Assert.AreEqual(17, product1.Id);
Assert.AreEqual("Something", product1.Name);
Assert.AreEqual(29.99M, product1.Price);
var product2 = new Product();
reader.SetAllValues(product2);
Assert.AreEqual(17, product2.Id);
Assert.AreEqual("Something", product2.Name);
Assert.AreEqual(29.99M, product2.Price);
}
}
public class Product
{
[Mapping("ProductId")]
public int Id { get; set; }
[Mapping("ProductName")]
public string Name { get; set; }
public decimal Price { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MappingAttribute : Attribute
{
public MappingAttribute(string columnName)
{
this.ColumnName = columnName;
}
public string ColumnName { get; private set; }
}
public static class IDataReaderExtensions
{
public static void SetAllValues(this IDataReader reader, object source)
{
foreach (var prop in source.GetType().GetProperties())
{
SetValue(reader, source, prop);
}
}
public static void SetValue<T, P>(this IDataReader reader, T source, Expression<Func<T, P>> pe)
{
var property = (PropertyInfo)((MemberExpression)pe.Body).Member;
SetValue(reader, source, property);
}
private static void SetValue(IDataReader reader, object source, PropertyInfo property)
{
string propertyName = property.Name;
var columnName = propertyName;
var mapping = property.GetAttribute<MappingAttribute>();
if (mapping != null) columnName = mapping.ColumnName;
var value = reader.GetValue(reader.GetOrdinal(columnName));
var value2 = Convert.ChangeType(value, property.PropertyType);
property.SetValue(source, value2, null);
}
}
public static class ICustomFormatProviderExtensions
{
public static T GetAttribute<T>(this ICustomAttributeProvider provider)
{
return (T)provider.GetCustomAttributes(typeof(T), true).FirstOrDefault();
}
}
public class FakeDataReader : IDataReader
{
private Dictionary<string, object> values;
public FakeDataReader(Dictionary<string, object> values)
{
this.values = values;
}
public int GetOrdinal(string name)
{
int i = 0;
foreach (var key in values.Keys)
{
if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
i++;
}
return -1;
}
public object GetValue(int i)
{
return values.Values.ToArray()[i];
}
}
I wrote a class that allows a derivate to specify which of its properties can be lazy loaded. The code is:
public abstract class SelfHydratingEntity<T> : DynamicObject where T : class {
private readonly Dictionary<string, LoadableBackingField> fields;
public SelfHydratingEntity(T original) {
this.Original = original;
this.fields = this.GetBackingFields().ToDictionary(f => f.Name);
}
public T Original { get; private set; }
protected virtual IEnumerable<LoadableBackingField> GetBackingFields() {
yield break;
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
LoadableBackingField field;
if (this.fields.TryGetValue(binder.Name, out field)) {
result = field.GetValue();
return true;
} else {
var getter = PropertyAccessor.GetGetter(this.Original.GetType(), binder.Name);
result = getter(this.Original);
return true;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value) {
LoadableBackingField field;
if (this.fields.TryGetValue(binder.Name, out field)) {
field.SetValue(value);
return true;
} else {
var setter = PropertyAccessor.GetSetter(this.Original.GetType(), binder.Name);
setter(this.Original, value);
return true;
}
}
}
And a derivate class:
public class SelfHydratingPerson : SelfHydratingEntity<IPerson> {
private readonly IDataRepository dataRepository;
public SelfHydratingDerivate(IDataRepository dataRepository, IPerson person)
: base(person) {
this.dataRepository = dataRepository
}
protected override IEnumerable<LoadableBackingField> GetBackingFields() {
yield return new LoadableBackingField("Address", () => this.dataRepository.Addresses.Get(this.Original.AddressID));
}
}
This works perfectly fine for getting and settings property values, but I get a either a RuntimeBinderException when I implicitly cast or an InvalidCastException with an explicitly cast SelfHydratingEntity back to T.
I know that you can override the DynamicObject.TryConvert method, but I'm wondering what exactly to put in this method. I've read a lot about duck typing today, and have tried out several libraries, but none of them work for this particular scenario. All of the libraries I've tried today generate a wrapper class using Reflection.Emit that makes calls to "get_" and "set_" methods and naturally use reflection to find these methods on the wrapped instance. SelfHydratingEntity of course doesn't have the "get_" and "set_" methods defined.
So, I'm wondering if this kind of thing is even possible. Is there any way to cast an instance of SelfHydratingEntity to T? I'm looking for something like this:
var original = GetOriginalPerson();
dynamic person = new SelfHydratingPerson(new DataRepository(), original);
string name = person.Name; // Gets property value on original
var address = person.Address; // Gets property value using LoadableBackingField registration
var iPerson = (IPerson)person;
- or -
var iPerson = DuckType.As<IPerson>(person);
Have you seen this Duck Typing project. It looks pretty good. I have just found a great example from Mauricio. It uses the Windsor Castle dynamic proxy to intercept method calls
Using the code from Mauricio the following code works like a dream
class Program
{
static void Main(string[] args)
{
dynamic person = new { Name = "Peter" };
var p = DuckType.As<IPerson>(person);
Console.WriteLine(p.Name);
}
}
public interface IPerson
{
string Name { get; set; }
}
public static class DuckType
{
private static readonly ProxyGenerator generator = new ProxyGenerator();
public static T As<T>(object o)
{
return generator.CreateInterfaceProxyWithoutTarget<T>(new DuckTypingInterceptor(o));
}
}
public class DuckTypingInterceptor : IInterceptor
{
private readonly object target;
public DuckTypingInterceptor(object target)
{
this.target = target;
}
public void Intercept(IInvocation invocation)
{
var methods = target.GetType().GetMethods()
.Where(m => m.Name == invocation.Method.Name)
.Where(m => m.GetParameters().Length == invocation.Arguments.Length)
.ToList();
if (methods.Count > 1)
throw new ApplicationException(string.Format("Ambiguous method match for '{0}'", invocation.Method.Name));
if (methods.Count == 0)
throw new ApplicationException(string.Format("No method '{0}' found", invocation.Method.Name));
var method = methods[0];
if (invocation.GenericArguments != null && invocation.GenericArguments.Length > 0)
method = method.MakeGenericMethod(invocation.GenericArguments);
invocation.ReturnValue = method.Invoke(target, invocation.Arguments);
}
}
impromptu-interface
https://github.com/ekonbenefits/impromptu-interface
Can static cast interfaces onto objects derived from DynamicObject.