I'm trying to map an array of objects containing source classes, which has to be mapped to an array of objects containing destination classes. But It does work out of the box for my code.
class Class1ChildClass
{
public int Value { get; set; }
}
class Class1
{
// This array contains classes of type Class1ChildClass
public object[] ClassesAsObjects { get; set; }
}
class Class2ChildClass
{
public int Value { get; set; }
}
class Class2
{
// This array should contain classes of type Class2ChildClass
public object[] ClassesAsObjects { get; set; }
}
The straight-forward way in mind mind would be this code:
var cl1 = new Class1
{
ClassesAsObjects =
new object[] {
new Class1ChildClass
{
Value = 999
}
}
};
var config =
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Class1ChildClass, Class2ChildClass>();
cfg.CreateMap<Class1, Class2>();
}
);
var mapper = config.CreateMapper();
var cl2 = mapper.Map<Class2>(cl1);
Whatever I do, I always get an array of Class1ChildClass in the destination class Class2.
I tried to use ForMember with no success.
I do not know why you want to use Automapper in this case instead of just manually doing the mapping. From my understanding, Automapper looks at the types and their properties, and does its magic with those.
However in your case, there is not much to leverage Automapper. You are using object which has no Value property. You want to map members of object[] but actually want the content to be somehow converted from one (object sub-)class to another.
But if you want to use Automapper, you can use the AfterMap to map the ClassesAsObjects member:
var config =
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<Class1, Class2>()
.AfterMap((src, dest) =>
{
dest.ClassesAsObjects = src.ClassesAsObjects
.Select(x =>
{
return (x is Class1ChildClass)
? (object)(new Class2ChildClass { Value = (x as Class1ChildClass).Value })
: null;
}).ToArray();
});
}
);
According to this: https://github.com/AutoMapper/AutoMapper/wiki/Lists-and-arrays
AutoMapper still requires explicit configuration for child mappings, as AutoMapper cannot "guess" which specific child destination mapping to use.
So you may need to add something like this:
Mapper.Initialize(c=> {
c.CreateMap<Class1, Class2>()
.Include<Class1ChildClass, Class2ChildClass>();
c.CreateMap<Class1, Class2>();
});
Related
My question is very similar to the one asked here, but I was unable to find a solution based on the provided answers. I must be missing something that's right around the corner.
I have a custom converter that allows me to do the following:
cfg.CreateMap<Container<int>, int>().ConvertUsing(new ContainerConverter<Container<int>, int>());
But int being not the only type argument, is there a concise way to express:
cfg.CreateMap<Container<T>, T>().ConvertUsing(new ContainerConverter<Container<T>, T>());
Without having to do:
cfg.CreateMap<Container<int>, int>().ConvertUsing(new ContainerConverter<Container<int>, int>());
cfg.CreateMap<Container<long>, long>().ConvertUsing(new ContainerConverter<Container<long>, long>());
...
For every T in use?
In my situation, properties of Container<T> and T are in turn members of classes A1, A2... and B1, B2.... The mapping is performed by calling
B dest = mapper.Map<A, B>(source);
Thank you in advance.
If you don't mind the boxing, you can use a generic converter.
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Container<>), typeof(object)).ConvertUsing(typeof(ContainerConverter<>));
});
config.AssertConfigurationIsValid();
//config.BuildExecutionPlan(typeof(Destination), typeof(Source)).ToReadableString().Dump();
var mapper = config.CreateMapper();
mapper.Map<int>(new Container<int>{ Value = 12 }).Dump();
}
public class ContainerConverter<T> : ITypeConverter<Container<T>, object>
{
public object Convert(Container<T> source, object destination, ResolutionContext c)
{
return source.Value;
}
}
public class Container<T>
{
public T Value { get; set; }
}
I want to create a class to be serialized. However I want the Order-attribute to be explicitely set on every member within my class. So I wrote this code:
public void Process(CodeNamespace code, XmlSchema schema)
{
var types = code.Types.Cast<CodeTypeDeclaration>().Where(x => !x.IsEnum);
foreach (var type in types)
{
foreach(var member in type.Members.Cast<CodeTypeMember>().Select((x, i) => new { Item = x, Order = i }))
{
member.Item.CustomAttributes.Add(new CodeAttributeDeclaration("XmlElementAttribute", ???);
}
}
}
I don´t know how to set the named argument Order to a valid value. I already tried new[] { Order = member.Order } but apparently this doesn´t work at all.
So what I want is something that creates this code:
public class MyClass
{
[XmlElement("MyProp", Order = 0)]
public int Prop1 { get; set; }
}
The solution is quite simple. I compared the process with XmlElement-attributes that were automatically added and noticed, that the attributes name is not XmlElementAttribute but System.Xml.Serialization.XmlElementAttribute. Furthermore - as the Order-parameter is an argument of the XmlElementAttribute-constructor we have to add it as CodeAttributeArgument:
var attr = new CodeAttributeDeclaration("System.Xml.Serialization.XmlElementAttribute");
attr.Arguments.Add(new CodeAttributeArgument("Order", new CodePrimitiveExpression(member.Order)));
member.Item.CustomAttributes.Add(attr);
I defined the following four classes
public class Source
{
public object Value { get; set; }
}
public class Destination
{
public object Value { get; set; }
}
public class SourceDataType
{
//...
}
public class DestinationDataType
{
//...
}
I configured the Automapper as follows:
AutoMapper.Mapper.CreateMap<Source, Destination>();
AutoMapper.Mapper.CreateMap<SourceDataType, DestinationDataType>();
Then in my code, I create a Source object with SourceDateType value, and try to map it to a Destination object.
var source = new Source { Value = new SourceDataType() };
var destination = AutoMapper.Mapper.Map<Source, Destination>(source);
Console.WriteLine(destination.GetType());
Console.WriteLine(destination.Value.GetType());
The destination type is as expected. I was hoping Automapper would pick up on the mapping between the SourceDataType and DestinationDataType and map the SourceDataType instant to a DestinationDataType instant. However, the destination object was given a SourceDataType value instead.
I also tried DynamicMap, but achieved the same result.
var destination = AutoMapper.Mapper.DynamicMap<Source, Destination>(source);
var destination = AutoMapper.Mapper.DynamicMap<Destination>(source);
Is there a way to configure Automapper to dynamically map the inner classes?
This is solvable, although if more and more types got added to the mix, it will become unmaintainable and complex. Anyway, for your situation, this will work:
[Test]
public void CustomMapping()
{
//arrange
Mapper.CreateMap<Source, Destination>()
.ForMember(d=>d.Value, opt=>opt.ResolveUsing(ResolveValue));
Mapper.CreateMap<SourceDataType, DestinationDataType>();
var source = new Source { Value = new SourceDataType() };
//act
var destination = Mapper.Map<Source, Destination>(source);
//assert
destination.Value.Should().Be.OfType<DestinationDataType>();
}
private object ResolveValue(ResolutionResult result)
{
var source = result.Context.SourceValue as Source;
if (result.Context.IsSourceValueNull || source == null || !(source.Value is SourceDataType))
{
return null;
}
var sourceValue = source.Value as SourceDataType;
return result.Context.Engine.Map<DestinationDataType>(sourceValue);
}
There's two problems here:
Firstly, you do not define any relationship between SourceDataType and DestinationDataType so there's no way for AutoMapper to know how to convert those types.
Secondly, the two properties are of type object and (as far as I know) AutoMapper won't convert based on the dynamic type.
You might be able to solve this issue by using a Custom Type Converter; however, I have reservations about this due to the property being of type object.
Anyway, you can try adding something based on the following mapping:
Mapper.CreateMap<object, object>().ConvertUsing(src =>
{
if (src is SourceDataType)
return new DestinationDataType(); // Put your conversion code here.
else
return src;
});
Like I said, I don't like this because object will match all property types.
I have a an object called FooObject:
public class FooObject
{
public string Test1 {get; set;}
public List<FooSubObject> SubTest1 {get; set;}
}
For later in the example, I also have a DifferenceFooObject:
public class DifferenceFooObject
{
public string SharedTest1 { get; set; }
public List<FooSubObject> SubTest1 {get; set;}
}
For later in the example, I also have a FooSubObject. FooObject has a property SubTest1 that contains a typed collection of this type:
public class FooSubObject
{
public string Test2 { get; set; }
}
I have a method that accepts a typed collection of FooObject. In this method, I need to calculate if any of the properties between the FooObjects within the typed collection parameter, have equal properties.
public DifferenceFooObject RunPropComparison(List<FooObject> foos)
{
DifferenceFooObject difference = new DifferencFooObject();
FooObject forComparison = foos.FirstOrDefault();
IEnumerable<FooObject> intersectCollection = foos.Skip(1);
// maybe do this using the first to compare the rest? No clue
}
I do not know the most efficient way to complete the above method. It is further complicated, at least IMHO, that the calculation has to take into account the properties of objects in collections that are a property of FooObject (looping through the properties of FooSubObject).
Here is the requested in/out:
List<FooObject> foos = new List<FooObject>();
FooObject obj = new FooObject();
obj.Test1= "Test1";
obj.SubTest1 = new List<FooSubObject>();
FooSubObject obj2 = new FooSubObject();
obj2.Test2 = "Test2";
obj.SubTest1.Add(obj2);
FooObject obj3 = new FooObject();
obj3.Test1= "Test1";
obj3.SubTest1 = new List<FooSubObject>();
FooSubObject obj4 = new FooSubObject();
obj4.Test2 = "Test3";
obj3.SubTest1.Add(obj2);
That's what would go in, ideally it would return that Test1 is the same across the board.
Best as I can tell, this is what you're looking for:
public IList<DifferenceFooObject> RunPropComparison(List<FooObject> foos)
{
var differences = new List<DifferenceFooObject>();
foreach (var group in foos.GroupBy(x => x.Test1))
{
var difference = new DifferenceFooObject();
difference.SharedTest1 = group.Key;
difference.SubTest1 = group.SelectMany(x => x.SubTest1).ToList();
differences.Add(difference);
}
return differences;
}
Or if you add the following constructor:
public DifferenceFooObject(string sharedTest1, IEnumerable<FooSubObject> subTest1)
{
this.SharedTest1 = sharedTest1;
this.SubTest1 = subTest1.ToList();
}
Then you can make this code shorter:
public IList<DifferenceFooObject> RunPropComparison(List<FooObject> foos)
{
return foos.GroupBy(x => x.Test1)
.Select(g => new DifferenceFooObject(g.Key, g.SelectMany(x => x.SubTest1)))
.ToList();
}
I don't think there is an especially efficient way of doing this. You will need to rely heavily on Reflection using the getProperties method to get at the values of the object properties...
You could look into using FasterFlect (http://fasterflect.codeplex.com/) which has better performance over standard .Net reflection...
Check out this library. It compares two objects and tells you the different properties http://comparenetobjects.codeplex.com/documentation
I have the following query:
// Type T is constrained to a class that contains "ID" property
// propertiesToQuery is a list constructed based on type T
var set = AppContext.Set<T>();
var result = set.SelectMany(x => propertiesToQuery.Select(p => new { x.ID, Value = x.GetType().GetProperty(p.Name).GetValue(x) })
.Where(p => p.Value != null)
.Select(p => new SearchIndexItem
{
Key = p.Value.ToString(),
Url = Url.Action("Edit", type.Name, new { p.ID }),
Type = type
}));
Now because linq to entities does not allow to use PropertyInfo in queries, I need to run a ToList() on the set in order to execute the query on the db first and then perform the desired SelectMany().
This queries more than it needs to from the database which will be a problem when there's a lot of data (queried columns are of type string, others can be blobs and these are the ones I don't want to pull the data from)
So the question is how can I limit the columns queried from the db based on a list constructed at runtime?
I was trying to create an expression tree, and pass it to the Select() method on the set, but the problem was with creating the anonymous type, which can be different depenging on type T.
Your observation here:
the problem was with creating the anonymous type, which can be different depenging on type T
is accurate; it is hugely problematic to construct the result columns at runtime. The only simply way to do that is to make it look like you are populating a projection of a type that has all the members, for example the equivalent of:
// where our list is "Foo", "Bar":
x => new SomeType {
Foo = x.Foo,
Bar = x.Bar
// but other SomeType properties exist, but are't mapped
}
The obvious contender would be the entity type, so you are partially mapping a set of Customer rows to Customer objects - but most ORMs won't let you do that: if the projection is an entity type they want the entire type (i.e. x => x). You might be able to create a second version of the entity type that is a regular POCO/DTO but isn't part of the entity model, i.e.
Customer x => new CustomerDto {
Foo = x.Foo,
Bar = x.Bar
}
which you can do as part of Expression.MemberInit at runtime. Example:
class Foo
{
public string A { get; set; }
public int B { get; set; }
public DateTime C { get; set; }
}
class FooDto
{
public string A { get; set; }
public int B { get; set; }
public DateTime C { get; set; }
}
class Program
{
static void Main(string[] args)
{
var data = new[] { new Foo { A = "a", B = 1, C = DateTime.Now}}
.AsQueryable();
var mapped = PartialMap<Foo, FooDto>(data, "A", "C").ToList();
}
static IQueryable<TTo> PartialMap<TFrom, TTo>(
IQueryable<TFrom> source, params string[] members)
{
var p = Expression.Parameter(typeof(TFrom));
var body = Expression.MemberInit(Expression.New(typeof(TTo)),
from member in members
select (MemberBinding)Expression.Bind(
typeof(TTo).GetMember(member).Single(),
Expression.PropertyOrField(p, member))
);
return source.Select(Expression.Lambda<Func<TFrom, TTo>>(body, p));
}
}
in the output, A and C have values, but B does not.