Use PropertyDescriptor for mapping

Jan 30, 2010 at 12:17 PM

Hi,

I looked code of DefaultMapConfig and it use ReflectionUtils.GetPublicFieldsAndProperties. So, we always work with PropertyInfo, FieldInfo, MethodInfo. But i have object with virtual properties. For exampe:

class A : ICustomTypeDescriptor
{
  public int Id {get;set;}
  public string Name {get;set;}
  public IEnumerable<CustomFields> {get;}
  #region ICustomTypeDescriptor implementation
  ...
  public PropertyDescriptorCollection GetProperties()
  {
    // Return the original PropertyDescriptor for real properties
    // Create PropertyDescriptor for each CustomField
  }
  ...
  #endregion
}

I show this object in Data grid. Now i need to copy this object to:

  1. To normal object (with real properties)
  2. To object  with virtual properties (this is object like my, but it has more complex structure)

Questions:

  1. Can i implement operations for reading and writing by using PropertyDescriptor? I want to have universal mapper. In other words i should be able to use delegates like these:
    • For getter:
      (obj, member) =>
      {
        return TypeDescriptor.GetProperties(item.GetType())
          .Cast<PropertyDescriptor>()
          .First(p => p.Name == member.MemberInfo.Name)
          .GetValue(object);
      }
      
    • For setter:
      (obj, member, value) =>
      {
        TypeDescriptor.GetProperties(item.GetType())
          .Cast<PropertyDescriptor>()
          .First(p => p.Name == member.MemberInfo.Name)
          .SetValue(object, value);
      }
      
  2. Universal mapper is cool. But now i want to solve my problem ASAP. I know which types i will map. And i can implement MapConfig for each pair. Can you give me a hint how i can create operations with delegates. I will use direct access through  IEnumerable<CustomFields>?
Coordinator
Jan 30, 2010 at 4:57 PM
Edited Jan 30, 2010 at 4:57 PM

Hi Dimitry,

 

You can use EmitMapper as universal mapper with your delegates as getters, converters and setters. So, there are 2 ways.

1) You can create a mapping configurator "from scratch". Take a look on the following source code: http://emitmapper.codeplex.com/sourcecontrol/changeset/view/42128?projectName=emitmapper#731174  Here is an example how to create custom mapping configurator. Custom mapping configurator implements interface "IMappingConfigurator". The main method is "GetMappingOperations" which returns an array of mapping operations. Each mapping operation represents an atomic action: take a value from source, convert it to the destination type and write result to the destination. There are the following mapping operation types:

  • ReadWriteSimple - it is a basic operation wich is executed over simple fields. This operation performs primitive values copying from source to destination.
  • SrcReadOperation - this operation reads source value identified by "Source" property but doesn't write it to the destination and instead of this it calls your delegate (Setter, delegate type: void ValueSetter(object obj, object value, object state)) which should somehow process the value. This mapping operation is usefull when you copy fields from an ordinary object to some custom destination.
  • DestWriteOperation - this operation calls yous delegate (Getter, delegate type: delegate ValueToWrite<T> ValueGetter<T>(object value, object state);) to retrieve value and then it writes the value to the destination. This operation is usefull when you want to copy fields from some custom store to an object.

2) You can reate a mapping configurator derived from class "DefaultMapConfig" or "MapConfigBase" and override the method "GetMappingOperations" (which returns mapping operations which were described above). In that case you can use method "FilterOperations" to tune result mapping operations according to the configuration methods which were called for this mapping configurator (i mean methods like "NullSubstitution", "IgnoreMembers" and so on).

MapConfigBase
Jan 30, 2010 at 8:06 PM

Thanks for link. I see i wrote the same code but i used DbReaderMappingConfig.cs as example (i unloaded EmitMapper.Mvc.Net from VS at the begging).

Both configurations (DbReaderMappingConfig.cs and FormCollectionMapConfig.cs) generate DestWriteOperation operations. According to your explanation i should create both operations (SrcReadOperation; DestWriteOperation). How can i combine these operations? I will try to do this tomorrow. My plan to generate Src and Dst operations in GetMappingOperations() and debug how mapper will use them.

Coordinator
Jan 30, 2010 at 8:54 PM
Edited Jan 30, 2010 at 9:02 PM

If I understand you right the task is that you have to read an object from some store and save it there, don't you? If I'm right, you have to create 2 configurators:

public class Store2ObjectMappingConfigurator : DefaultMapConfig
{
	public override IMappingOperation[] GetMappingOperations(Type from, Type to)
	{
		return
			FilterOperations(
				from,
				to,
				ReflectionUtils
				.GetPublicFieldsAndProperties(to)
				.Select(
					m =>
					(IMappingOperation)new DestWriteOperation
					{
						Destination = new MemberDescriptor(m),
						Getter =
							(ValueGetter<object>)
							(
								(value, state) => ValueToWrite<object>.ReturnValue(
									((IDictionary<string, object>)value)[m.Name]
								)
							)

					}
				)
			).ToArray();
	}
}

public class Object2StoreMappingConfigurator : DefaultMapConfig
{
	public override IMappingOperation[] GetMappingOperations(Type from, Type to)
	{
		return
			FilterOperations(
				from,
				to,
				ReflectionUtils
				.GetPublicFieldsAndProperties(from)
				.Select(
					m =>
					(IMappingOperation)new SrcReadOperation
					{
						Source = new MemberDescriptor(m),
						Setter = (obj, value, state) => 
							((IDictionary<string, object>)obj)[m.Name] = value
					}
				)
			).ToArray();
	}
}

public class TestCustomMappingConfigObj
{
	public string field1;
	public int field2;
	public bool field3;
}

[Test]
public void TestCustomMappingConfig()
{
	var store = new Dictionary<string, object>();
	var mapperStore2Object = 
		ObjectMapperManager
		.DefaultInstance
		.GetMapper<Dictionary<string, object>, TestCustomMappingConfigObj>(
			new Store2ObjectMappingConfigurator()
		);
	var mapperObject2Store =
		ObjectMapperManager
		.DefaultInstance
		.GetMapper<TestCustomMappingConfigObj, Dictionary<string, object>>(
			new Object2StoreMappingConfigurator()
		);

	store["field1"] = "test1";
	store["field2"] = 1;
	store["field3"] = true;

	TestCustomMappingConfigObj obj1 = mapperStore2Object.Map(store);
	Assert.AreEqual("test1", obj1.field1);
	Assert.AreEqual(1, obj1.field2);
	Assert.AreEqual(true,	obj1.field3);

	obj1.field1 = "test2";
	obj1.field2 = 2;
	obj1.field3 = false;
	store.Clear();
	mapperObject2Store.Map(obj1, store);

	Assert.AreEqual("test2", store["field1"]);
	Assert.AreEqual(2, store["field2"]);
	Assert.AreEqual(false,	 store["field3"]);
}

 

Jan 31, 2010 at 11:46 AM
Edited Jan 31, 2010 at 2:48 PM

Thanks for example. That is what i have already implemented (Object to Store). But in my last reply i asked about mapping Store to Store.

class CustomField
{
  public string FieldName {get;set;}
  public FieldType FieldType {get;set;}
  public object Value {get;set;}
}
class Store1
{
  public int ItemId {get;set;}
  public string Name {get;set;}
  public IEnumerable<CustomField> CustomFields {get;}
}
class Store2
{
  public int Id {get;set;}
  public string Subject {get;set;}
  public IEnumerable<CustomField> CustomFields {get;}
}

I should map Store1 to Store2. User can setup properties mapping in app.config. An example:

  • ItemId -> Id
  • Name -> Subject
  • PriorityId -> Priority (this is custom field)
  • Duration-> Work (this is custom field)

Thereby i have properties names list, but i have to use delegate for reading and writing operation. That is why i asked how to combine SrcReadOperation and DestWriteOperation. I want to read value by delegate and write value by delegate. ObjectToStore is acceptable for me now. But i'm looking for restrictions of your library :)

Just FYI about my aim. I have properties list for source and destination object. But both objects don't have real properties. My UI (datagrid, propeditor, etc) uses PropertyDescriptors of component model (PropertyDescriptors is one more data access layer under my objects). And i'm trying to use this DAL in emit mapper.

Coordinator
Jan 31, 2010 at 12:28 PM

If you already have both delegates for getter and setter then I don't see why you can't call directly Setter(Getter())? You are trying to find too complex solution. EmitMapper is reasonable when source or destination or both of them have real fields and properties. Otherwise it is overkill. It is rather simpler to write code like this:

 

foreach (var customField in source.CustomFields)
{
	destination.CustomFields[propertiesMap[customField.FieldName]].Value = customField.Value;
}

 

is not it? :) At least this code is understandable for everybody, you can debug it easyly. So, my advise is to use EmitMapper for real properties and simpler solutions when them are possible.

Jan 31, 2010 at 3:05 PM

You are right, i'm trying to implement complex scenario. I used mapper for DTO, and now i try to use mapper everywhere :).

Is not easy to just copy CustomFields. Because real property can be mapped to CustomField, and CustomField to real property.

Thanks for your help and code snippets. This is enough for me.