AutoMapper, how to keep references between mapped objects? - c#

I am using AutoMapper to convert a UI model to POCOs that I later serialize to XML using a DataContractSerializer in order to preserve the references between them.
The problem comes that, when mapping, the references between those entities are lost.
The UI classes reference each other, but the mapping process makes new instances for every reference, so the original relations are broken :(
Let me explain:
I have 2 entities of type Person
Person
{
List<House> OwnedHouses
}
And these 2 objects
John
who owns
House1
Will
who also owns
House1
When AutoMapper maps each Person correctly, but when it also maps House1 as two different instances!!
So I have a two copies of House1. John owns his House1 (#1) and Will owns his House1 (#2).
They are not linked anymore.
Is there any way to keep the relations that originally existed?
Thanks.
EDITED: Actually what I have is this:
A Document contains a list of ChildDocuments. Each ChildDocument has a list of Designables (Rectangles, Lines, Ellipses…) and a especial designable called ChildDocumentAdapter that contains itself ANOOTHER ChildDocument. This is the trouble, it can reference another ChildDocument.

If I'm understanding the question, you're performing two separate mapping operations - one for John, another for Will.
#Sunny is right. AutoMapper is not designed to do this. Each call you make to Mapper.Map() is typically independent of any other. By using the same instance of the HouseListConverter, you get the benefit of caching all mapped houses in a dictionary. But you have to either register it globally or pass it as an option to the mapping calls you want grouped together. That's not just extra work, it's hiding a very important implementation detail deep within the converter.
If you map both John and Will in one operation, by putting them into a collection, the output would be what you want without the need for a custom converter or resolver.
It may be an easier alternative for other people with a similar problem.
public void MapListOfPeopleWithSameHouse()
{
Mapper.CreateMap<Person, PersonDTO>();
Mapper.CreateMap<House, HouseDTO>();
var people = new List<Person>();
var house = new House() { Address = "123 Main" };
people.Add(new Person() { Name = "John", Houses = new List<House>() { house } });
people.Add(new Person() { Name = "Will", Houses = new List<House>() { house } });
var peopleDTO = Mapper.Map<List<PersonDTO>>(people);
Assert.IsNotNull(peopleDTO[0].Houses);
Assert.AreSame(peopleDTO[0].Houses[0], peopleDTO[1].Houses[0]);
}

While Automapper is not designed with this in mind, it's powerful enough to let you do it, using custom type converters. You need to create your own converter from IList<House> to IList<HouseDto>, and inject it using a factory:
using System;
using System.Collections.Generic;
using AutoMapper;
using NUnit.Framework;
using SharpTestsEx;
namespace StackOverflowExample
{
public class House
{
public string Address { get; set; }
}
public class Person
{
public IList<House> OwnedHouse { get; set; }
}
public class HouseDto
{
public string Address { get; set; }
}
public class PersonDto
{
public IList<HouseDto> OwnedHouse { get; set; }
}
[TestFixture]
public class AutomapperTest
{
public interface IHouseListConverter : ITypeConverter<IList<House>, IList<HouseDto>>
{
}
public class HouseListConverter : IHouseListConverter
{
private readonly IDictionary<House, HouseDto> existingMappings;
public HouseListConverter(IDictionary<House, HouseDto> existingMappings)
{
this.existingMappings = existingMappings;
}
public IList<HouseDto> Convert(ResolutionContext context)
{
var houses = context.SourceValue as IList<House>;
if (houses == null)
{
return null;
}
var dtos = new List<HouseDto>();
foreach (var house in houses)
{
HouseDto mapped = null;
if (existingMappings.ContainsKey(house))
{
mapped = existingMappings[house];
}
else
{
mapped = Mapper.Map<HouseDto>(house);
existingMappings[house] = mapped;
}
dtos.Add(mapped);
}
return dtos;
}
}
public class ConverterFactory
{
private readonly IHouseListConverter resolver;
public ConverterFactory()
{
resolver = new HouseListConverter(new Dictionary<House, HouseDto>());
}
public object Resolve(Type t)
{
return t == typeof(IHouseListConverter) ? resolver : null;
}
}
[Test]
public void CustomResolverTest()
{
Mapper.CreateMap<House, HouseDto>();
Mapper.CreateMap<IList<House>, IList<HouseDto>>().ConvertUsing<IHouseListConverter>();
Mapper.CreateMap<Person, PersonDto>();
var house = new House {Address = "any"};
var john = new Person {OwnedHouse = new List<House> {house}};
var will = new Person { OwnedHouse = new List<House> { house } };
var converterFactory = new ConverterFactory();
var johnDto = Mapper.Map<PersonDto>(john, o=>o.ConstructServicesUsing(converterFactory.Resolve));
var willDto = Mapper.Map<PersonDto>(will, o=>o.ConstructServicesUsing(converterFactory.Resolve));
johnDto.OwnedHouse[0].Should().Be.SameInstanceAs(willDto.OwnedHouse[0]);
johnDto.OwnedHouse[0].Address.Should().Be("any");
}
}
}

Related

Use attributes to make headers more human readable with CSVHelper

I am trying to use CSVHelper to serialize a database that is constructed out of multiple classes like shown below. I would like to make the csv a bit more human readable by adding information on units (when appropriate) and by ordering the data so that the "Name" always appears first. The rest can come in whatever order.
I have a class like shown below.
[DataContract(IsReference = true)]
public class OpaqueMaterial : LibraryComponent
{
[DataMember]
[Units("W/m.K")]
public double Conductivity { get; set; } = 2.4;
[DataMember]
public string Roughness { get; set; } = "Rough";
}
[DataContract]
public abstract class LibraryComponent
{
[DataMember, DefaultValue("No name")]
public string Name { get; set; } = "No name";
}
To avoid writing seprarate read write functions for each class I am reading and writing with templated functions like given below:
public void writeLibCSV<T>(string fp, List<T> records)
{
using (var sw = new StreamWriter(fp))
{
var csv = new CsvWriter(sw);
csv.WriteRecords(records);
}
}
public List<T> readLibCSV<T>(string fp)
{
var records = new List<T>();
using (var sr = new StreamReader(fp))
{
var csv = new CsvReader(sr);
records = csv.GetRecords<T>().ToList();
}
return records;
}
That I then use in the code to read and write as such:
writeLibCSV<OpaqueMaterial>(folderPath + #"\OpaqueMaterial.csv", lib.OpaqueMaterial.ToList());
List<OpaqueMaterial> inOpaqueMaterial = readLibCSV<OpaqueMaterial>(folderPath + #"\OpaqueMaterial.csv");
The CSV output then looks like:
Conductivity, Roughnes, Name
2.4, Rough, No Name
I would like to come out as:
Name, Conductivity [W/m.K], Roughness
No Name, 2.4, Rough
I know that the reordering is possible using maps like:
public class MyClassMap : ClassMap<OpaqueMaterial>
{
public MyClassMap()
{
Map(m => m.Name).Index(0);
AutoMap();
}
}
I would like to make this abstract so that I dont have to apply a different mapping to every class. I was not able to find an example that could help with adding the custom headers. Any suggestions or help would be greatly appreciated.
You could create a generic version of ClassMap<T> that will automatically inspect the type T using reflection and then construct the mapping dynamically based on the properties it finds and based on the attributes that may or may not be attached to it.
Without knowing the CsvHelper library too well, something like this should work:
public class AutoMap<T> : ClassMap<T>
{
public AutoMap()
{
var properties = typeof(T).GetProperties();
// map the name property first
var nameProperty = properties.FirstOrDefault(p => p.Name == "Name");
if (nameProperty != null)
MapProperty(nameProperty).Index(0);
foreach (var prop in properties.Where(p => p != nameProperty))
MapProperty(prop);
}
private MemberMap MapProperty(PropertyInfo pi)
{
var map = Map(typeof(T), pi);
// set name
string name = pi.Name;
var unitsAttribute = pi.GetCustomAttribute<UnitsAttribute>();
if (unitsAttribute != null)
name = $"{name} {unitsAttribute.Unit}";
map.Name(name);
// set default
var defaultValueAttribute = pi.GetCustomAttribute<DefaultValueAttribute>();
if (defaultValueAttribute != null)
map.Default(defaultValueAttribute.Value);
return map;
}
}
Now, you just need to create a AutoMap<T> for every type T that you want to support.
I’ve added examples for a UnitsAttribute and the DefaultValueAttribute, that should give you an idea on how to proceed with more attributes if you need more.

Copy List<A> to List<B> when both types share some or all properties [duplicate]

Say I have one class that looks like this:
public class Person
{
public string Name {get; set;}
public int Number {get; set;}
}
And another that looks like this:
public class Dog
{
public string Name {get; set;}
public int Number {get; set;}
}
They are two different classes, but they happen to have the exact same elements (a string called Name and an int called Number)
Is there an easy way in C# to, say, if I had an instance of Person to then create an instance of Dog with the same Name and Number?
For example if I had:
Person person = new Person();
person.Name = "George";
person.Number = 1;
I know I can't simply go:
Dog dog = person;
Because they are two different types. But is there a way in C# to check "oh, if they have the same element, set the same elements of Dog to equal that of Person.
But I feel there has to be an easier way than doing something like:
dog.Name = person.Name;
dog.Number = person.Number;
Especially if the class has a LOT of elements. Also if anyone is wondering, these two different classes are in two different pieces of the API, so I can't simply make them related either.
You can use AutoMapper:
public Dog UsingAMR(Person prs)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Person, Dog>();
});
IMapper mapper = config.CreateMapper();
return mapper.Map<Person, Dog>(prs);
}
Then you can easily:
Person ps = new Person {Name = "John", Number = 25};
Dog dog = UsingAMR(ps);
Just don't forget to install AutoMapper first from the package manager console as mentioned in the reference:
From Tools menu click on NuGet Package Manager ==> Package Manager Console
Then type the following command:
PM> Install-Package AutoMapper
An object oriented approach.
public class Mammal
{
public Mammal(Mammal toCopy)
{
Name = toCopy.Name;
Number = toCopy.Number;
}
public string Name {get; set;}
public int Number {get; set;}
}
public class Person: Mammal
{
public Person(Mammal toCopy) {} /* will default to base constructor */
}
public class Dog: Mammal
{
public Dog(Mammal toCopy) {} /* will default to base constructor */
}
This will allow the following:
Person person = new Person();
person.Name = "George";
person.Number = 1;
Dog dog = new Dog(person);
Install AutoMapper package in your project.
As a best practice (for web applications) you can create new class (should derives from Profile) in your App_Start folder, that will contain all your mappings for your project.
namespace MyApp.App_Start
{
public class MyAppMapping : Profile
{
public MyAppMapping()
{
CreateMap<Person, Dog>();
//You can also create a reverse mapping
CreateMap<Dog, Person>();
/*You can also map claculated value for your destination.
Example: you want to append "d-" before the value that will be
mapped to Name property of the dog*/
CreateMap<Person, Dog>()
.ForMember(d => d.Days,
conf => conf.ResolveUsing(AppendDogName));
}
private static object AppendDogName(Person person)
{
return "d-" + person.Name;
}
}
}
Then Initialize your mapping inside the Application_Start method in Global.asax
protected void Application_Start()
{
Mapper.Initialize(m => m.AddProfile<MyAppMapping>());
}
You can now use the mappings that you have created
var dog = AutoMapper.Mapper.Map<Person, Dog>(person);
If you don't work with big generic list, you can do it using LinQ.
var persons = new List<Person>();
// populate data [...]
var dogs = persons.Select(p=>new Dog{Name=p.Name,Number=p.Number}).ToList();
It's easy to remember, and you can filter data previously.

Deserialize two slightly different JSON strings (same structure, different names) to the same class

My problem in short: I need to deserialize two big JSON strings to one class, but strings are little different. Here is the first one:
{
"persons": [
{
"age":30,
"name":"david",
"hobbies": [
{
"name":"tennis",
"hours":5
},
{
"name":"football",
"hours":10
}
]
},
{
"name":"adam",
"age":23,
"hobbies":[]
}
]
}
and the other one:
{
"person": [
{
"age":25,
"name":"dave",
"hobbies":[
{
"name":"Basketball",
"hours":5
},
{
"name":"football",
"hours":10
}
]
},
{
"name":"Steve",
"age":28,
"hobbies": []
}
]
}
You can see that one time it's "Persons", and the other time it's "Person". Is there any simple solution to this? I was thinking about creating two lists in my class
List<Person> person;
List<Person> persons;
and after deserialization combine then manually somehow. But there must be a simplier way.
By the way, this is not the exact code I need to deserialize. I just wanted to keep the main idea as simple as possible.
One simple solution is to use a single list in your class, but then add an alternate setter with the other name which references the same list. You can even make it private as long as you decorate it with a [JsonProperty] attribute. That way the public interface of your class will look normal, but it will still work with both JSON property names.
public class RootObject
{
[JsonProperty("persons")]
public List<Person> People { get; set; }
// This is visible to Json.Net and references the real list
[JsonProperty("person")]
private List<Person> Person
{
set { People = value; }
}
}
public class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("hobbies")]
public List<Hobby> Hobbies { get; set; }
}
public class Hobby
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("hours")]
public int Hours { get; set; }
}
Fiddle: https://dotnetfiddle.net/9Dw48J
EDIT
To address your comments: PopulateObject can be used to blindly augment an existing object with data from JSON, but it will not do the kind of merging you are looking for. It seems you are looking for a way to match people by name (and maybe age?) and then combine the hobbies for those people. For that you will need to write your own logic. I would suggest deserializing each list into separate instances of your RootObject class, then merging the data in a post-processing step. You can make a method on the RootObject class which will accept another RootObject to merge. Perhaps something like this:
public class RootObject
{
...
public void MergeWith(RootObject other)
{
if (other.People == null) return;
if (People == null) People = new List<Person>();
foreach (Person person in other.People)
{
// You may need to make changes here--
// How do you determine whether two people are the same?
Person existingPerson = People.FirstOrDefault(p => p.Name == person.Name &&
p.Age == person.Age);
if (existingPerson != null)
{
existingPerson.MergeWith(person);
}
else
{
People.Add(person);
}
}
}
}
And on the Person class...
public class Person
{
...
public void MergeWith(Person other)
{
if (other.Hobbies == null) return;
if (Hobbies == null) Hobbies = new List<Hobby>();
foreach (Hobby hobby in other.Hobbies)
{
Hobby existingHobby = Hobbies.FirstOrDefault(h => h.Name == hobby.Name);
if (existingHobby != null)
{
// You may need to make changes here--
// What do you do if two hobbies have the same name but different hours?
existingHobby.Hours += hobby.Hours;
}
else
{
Hobbies.Add(hobby);
}
}
}
}
To deserialize and merge you would then do:
var firstObj = JsonConvert.DeserializeObject<RootObject>(firstJson);
var secondObj = JsonConvert.DeserializeObject<RootObject>(secondJson);
firstObj.MergeWith(secondObj);
Fiddle: https://dotnetfiddle.net/8Fiwsd
Both contain a List<Person>. Deserialize them into two classes like normal, but just grab the List that each has and add them to the same List, as they contain the same information.
You can pass your 'different' strings into different JObjects. then take from both of them inner collections by different names (JObject allows that) and then, deserialize these collections into collections with one common c# class.
It looks like from Deserializing Partial JSON area, so, you can take a look this link.

Protobuf-net deserializing after defining inheritance

I'm using protobuf-net version 2.0.0.640 to serialize some data as shown below.
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public interface ITestMessage
{
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrder : ITestMessage
{
public int Amount { get; set; }
}
[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class MyOrderWrapper
{
public MyOrder Order { get; set; }
}
[TestMethod]
public void TestOrderSerialize()
{
var order = new MyOrder() {Amount = 10};
var orderWrapper = new MyOrderWrapper() { Order = order };
using (var file = File.Create("C:\\temp\\order.bin"))
{
Serializer.Serialize<MyOrderWrapper>(file, orderWrapper);
}
}
Now, If I declare an inheritance dependency between 'ITestMessage' & 'MyOrder' via code using:
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
I get the following error when trying to deserialize my prevously saved data.
"No parameterless constructor found for ITestMessage".
[TestMethod]
public void TestOrderDeserialize()
{
RuntimeTypeModel.Default[typeof(ITestMessage)].AddSubType(2, typeof(MyOrder));
MyOrderWrapper orderWrapper;
using (var file = File.OpenRead("C:\\temp\\order.bin"))
{
orderWrapper = Serializer.Deserialize<MyOrderWrapper>(file);
}
}
Can someone please explain why this would happen when 'MyOrderWrapper' does not reference anything higher than 'MyOrder' in the inheritance hirarchy.
Also, why it works when I explictly include '[ProtoInclude(2, typeof(MyOrder))]' on 'ITestMessage'
Thanks
Basically, that is a breaking change as far as the serializer is concerned - at the wire layer, neither "classes" nor "interfaces" exist, so in terms of storage, this is akin to changing the base-type of the class; when serializing, the root type was MyOrder - and during deserialization the root type is ITestMessage. This will not make it happy.
Basically: you can't do that.

How to tell AutoMapper to use "pass by reference?"

By default automapper creates a new object based on the destination's type:
public void Doit( Person personMissingStuff )
{
PersonTemplate template = _personDao.GetPersonTemplate(1);
Mapper.CreateMap<PersonTemplate, Person>();
Person basePerson = Mapper.Map<Person>( template );
Mapper.CreateMap<Person, Person>();
Person completePerson =
Mapper.Map<Person, Person>( basePerson, personMissingStuff );
...
}
Instead of getting a completePerson I just get a basePerson again. How do I tell AutoMapper to run the mappings by reference instead of by value?
Mapper.Map(source, dest) actually returns the destination object, in your case it'll be personMissingStuff.
With that said, assuming that you want to fill in only the null properties in the destination, you need to configure the mapping properly, and not map when the destination property has value.
The following sample does exactly this for class properties. For value properties, probably you need to do additional configuration. The example uses NUnit and SharpTestsEx:
[TestFixture]
public class LoadIntoInstance
{
public class Template
{
public string Name { get; set; }
}
public class Person
{
public string Name { get; set; }
public string OtherData { get; set; }
}
[Test]
public void Should_load_into_instance()
{
Mapper.CreateMap<Template, Person>()
.ForMember(d=>d.OtherData, opt=>opt.Ignore());
Mapper.CreateMap<Person, Person>()
.ForAllMembers(opt=>opt.Condition(ctx=>ctx.DestinationValue==null));
Mapper.AssertConfigurationIsValid();
var template = new Template {Name = "template"};
var basePerson = Mapper.Map<Person>(template);
var noNamePerson = new Person {OtherData = "other"};
var result = Mapper.Map(basePerson, noNamePerson);
result.Should().Be.SameInstanceAs(noNamePerson);
result.Satisfy(r =>
r.Name == "template" &&
r.OtherData == "other");
}
}
Just use traditional shallow cloning...
Person completePerson = basePerson.MemberwiseClone();
This should keep the reference types and clone the value types.
MSDN Link

Categories