I have a configuration that does a reverse map to unflatten the source object into a destination object, however, if the source object property is read-only it is automatically ignored and I have to manually map it.
Some of my source objects have up to 100 fields, creating manual mappings like this feels cumbersome and I'm unsure why this behavior exists in auto mapper.
See below code snippet for a working example of what I am describing.
using System;
using AutoMapper;
namespace automappertests
{
class Source
{
public int Field1Suffix1 { get; set; }
public int Field1Suffix2 { get; set; }
public int Field2Suffix1 { get; set; }
// !!Attention!!
// This is a readonly property with a private setter
public int Field2Suffix2 => Field2Suffix1;
}
class Destination
{
public Wrapper Field1 { get; set; }
public Wrapper Field2 { get; set; }
}
class Wrapper
{
public int Suffix1 { get; set; }
public int Suffix2 { get; set; }
public static Wrapper New(int suffix1, int suffix2) =>
new Wrapper
{
Suffix1 = suffix1,
Suffix2 = suffix2
};
}
class DestinationProfile : Profile
{
public DestinationProfile()
{
CreateMap<Destination, Source>()
.ReverseMap()
// !!Attention!!
// Why do I need to have an explicit ForMember MapFrom for the readonly property?
.ForMember(m => m.Field2, o => o.MapFrom(src => Wrapper.New(src.Field2Suffix1, src.Field2Suffix2)));
}
}
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => { cfg.AddProfile<DestinationProfile>(); });
var mapper = config.CreateMapper();
var source = new Source()
{
Field1Suffix1 = 1,
Field1Suffix2 = 2,
Field2Suffix1 = 3,
};
var destination = mapper.Map<Source, Destination>(source);
Console.WriteLine($"Field1.Suffix1 = {destination.Field1.Suffix1}");
Console.WriteLine($"Field1.Suffix2 = {destination.Field1.Suffix2}");
Console.WriteLine($"Field2.Suffix1 = {destination.Field2.Suffix1}");
Console.WriteLine($"Field2.Suffix2 = {destination.Field2.Suffix2}");
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
}
}
Output without .ForMember:
Field1.Suffix1 = 1
Field1.Suffix2 = 2
Field2.Suffix1 = 3
Field2.Suffix2 = 0
Output with .ForMember:
Field1.Suffix1 = 1
Field1.Suffix2 = 2
Field2.Suffix1 = 3
Field2.Suffix2 = 3
Actually you have no setter. Add an explicit private setter.
public int Field2Suffix2 { get => Field2Suffix1; private set => Field2Suffix1 = value; }
Related
I try Automapper and it is very nice but is it possible map two OuterDto source to OuterModel destination with same object InnerDeto like in code? How can I do that dest1.Inner and dest2.Inner after map has same instance? What I know, I think it is not possible. What do you think? Thanks for help me
public class OuterDto
{
public int Value { get; set; }
public InnerDto Inner { get; set; }
}
public class InnerDto
{
public int OtherValue { get; set; }
}
public class OuterModel
{
public int Value { get; set; }
public InnerModel Inner { get; set; }
}
public class InnerModel
{
public int OtherValue { get; set; }
}
public class test
{
public test()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<InnerDto, InnerModel>().ReverseMap();
cfg.CreateMap<OuterDto, OuterModel>().ReverseMap();
});
config.AssertConfigurationIsValid();
InnerDto innerSource = new InnerDto { OtherValue = 15 };
var source1 = new OuterDto
{
Value = 1,
Inner = innerSource
};
var source2 = new OuterDto
{
Value = 2,
Inner = innerSource
};
var mapper = config.CreateMapper();
source1.Inner.OtherValue = 20;
var dest1 = mapper.Map<OuterDto, OuterModel>(source1);
var dest2 = mapper.Map<OuterDto, OuterModel>(source2);
dest1.Inner.OtherValue = 1000;
//Result:
//dest1.Inner.OtherValue = 1000
//dest2.Inner.OtherValue = 20
//Expected Result:
//dest1.Inner.OtherValue = 1000
//dest2.Inner.OtherValue = 1000
}
}
I'm not sure, but try to instanciate OuterModel before calling Map method
//...
var mapper = config.CreateMapper();
source1.Inner.OtherValue = 20;
var dest1 = new OuterModel();
mapper.Map(source1, dest1);
mapper.Map(source2, dest1);
dest1.Inner.OtherValue = 1000;
NOTE: I haven't tested my code, it's just to give food for thought
I have a set of POCO facets with ranges, that I have received from a client. I need to convert these ultimately into an AggregationDictionary. I cannot figure out the syntax for creating a dynamic set of aggregations (possilbly of type RangeAggregationDescriptor) and need help with this.
My POCO objects are below:
public class TypedFacets
{
public string Name { get; set; }
public string Field { get; set; }
public IReadOnlyCollection<Range> RangeValues { get; set; } = new List<Range>();
public int Size { get; set; }
}
public class Range
{
public string Name { get; set; }
public double? From { get; set; }
public double? To { get; set; }
}
The Nest generation looks like below:
var facets = new List<TypedFacets>()
{
new TypedFacets()
{
Name = "potatoRange",
Field = "potatoRange",
RangeValues = new List<Range>()
{
new Range()
{
From = 0,
To = null,
Name = "chips"
},
new Range()
{
From = 1,
To = null,
Name = "crisps"
}
}
}
};
var aggregations = new AggregationContainerDescriptor<Template>();
facets.Where(f => f.RangeValues.Any()).ToList().ForEach(f =>
{
var rad = new RangeAggregationDescriptor<Template>();
f.RangeValues.ToList().ForEach(rangeValue =>
{
rad = rad.Ranges(rs => rs.From(rangeValue.From).To(rangeValue.To).Key(rangeValue.Name));
});
// this line doesn't work and needs to change
aggregations.Range(f.Name, r => r
.Field(f.Field).Ranges(rs => rad.Ranges));
});
return ((IAggregationContainer)aggregations).Aggregations;
I'm not sure how to fix the above. Any help would be appreciated.
I eventually found the solution for this. You can create the dynamic ranges as per below
private Func<AggregationRangeDescriptor, IAggregationRange>[] CreateRangeRanges(TypedFacets rangedAgg)
{
var rangeRanges = new List<Func<AggregationRangeDescriptor, IAggregationRange>>();
rangedAgg.RangeValues.ToList().ForEach(rangeValue =>
{
rangeRanges.Add(rs => rs.From(rangeValue.From).To(rangeValue.To).Key(rangeValue.Name));
});
return rangeRanges.ToArray();
}
And then assing them like below
facets.Where(f => f.RangeValues.Any()).ToList().ForEach(f =>
{
aggregations.Range(f.Name, r => r
.Field(f.Field).Ranges(CreateRangeRanges(f)));
});
I am having the following models
Source:
public class Opportunity
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid QuotationId { get; set; }
public int? QuotationNumber { get; set; }
public int? QuotationVersionNumber { get; set; }
}
Target:
public class OpportunityDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<QuotationDto> Quotations { get; set; }
}
public class QuotationDto
{
public Guid Id { get; set; }
public int Number { get; set; }
public int VersionNumber { get; set; }
}
The data I would fetch from my database would be flat as the Opportunity model and my api is exposing the OpportunityDto model.
so, in my auto-mapper configuration, I have the following code:
services
.AddSingleton(new MapperConfiguration(cfg =>
{
cfg.CreateMap<OpportunityDto, Opportunity>().ReverseMap();
cfg.CreateMap<QuotationDto, Quotation>().ReverseMap();
}).CreateMapper())
what I want to achive is a list of unique opportunities and each opportunity would have a nested member which would have the list of the quotations.
how can I make the automapper perform this grouping? right now the Quotations member of the opportunityDto returned from the api is always empty.
AutoMapper Configuration
You can do something like the following:
public static void InitialiseMapper()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<IEnumerable<Opportunity>, OpportunityDto>()
.ForMember(x => x.Id, x => x.MapFrom(y => y.FirstOrDefault().Id))
.ForMember(x => x.Name, x => x.MapFrom(y => y.FirstOrDefault().Name))
.ForMember(x => x.Quotations,
x => x.MapFrom(y => Mapper.Map<IEnumerable<Opportunity>, IEnumerable<QuotationDto>>(y).ToArray()))
;
cfg.CreateMap<Opportunity, QuotationDto>()
.ForMember(x => x.Id, x => x.MapFrom(y => y.QuotationId))
.ForMember(x => x.Number, x => x.MapFrom(y => y.QuotationNumber))
.ForMember(x => x.VersionNumber, x => x.MapFrom(y => y.QuotationVersionNumber))
;
});
}
Test
Which then successfully knows how to map as demonstrated by the following test:
[TestMethod]
public void TestMethod1()
{
var oppo1Guid = Guid.NewGuid();
var opportunities = new List<Opportunity>
{
new Opportunity
{
Id = oppo1Guid,
Name = "Mikeys Oppurtunity",
QuotationId = Guid.NewGuid(),
QuotationNumber = 169,
QuotationVersionNumber = 80,
},
new Opportunity
{
Id = oppo1Guid,
Name = "Mikeys Oppurtunity",
QuotationId = Guid.NewGuid(),
QuotationNumber = 170,
QuotationVersionNumber = 20,
}
};
var dtos = Mapper.Map<IEnumerable<Opportunity>, OpportunityDto>(opportunities);
var json = JsonConvert.SerializeObject(dtos, Formatting.Indented);
Console.WriteLine(json);
}
And the output is
Test Output
{
"Id": "623c17df-f748-47a2-bc7e-35eb124dbfa3",
"Name": "Mikeys Oppurtunity",
"Quotations": [
{
"Id": "ad8b31c2-6157-4b7f-a1f2-9f8cfc1474b7",
"Number": 169,
"VersionNumber": 80
},
{
"Id": "515aa560-6a5b-47da-a214-255d1815e153",
"Number": 170,
"VersionNumber": 20
}
]
}
#Afflatus I would give you the idea that you can follow. I am assuming that you are using AspNetCore based on your services variable.
You can create an extension method like this, to later make a call on your ConfigurationServices like services.RegisterMappingsWithAutomapper():
public static IServiceCollection RegisterMappingsWithAutomapper(this IServiceCollection services)
{
var mapperConfig = AutoMapperHelper.InitializeAutoMapper();
services.AddScoped<IMapper>(provider => new Mapper(mapperConfig));
return services;
}
The InitializeAutoMapper is below:
public static class AutoMapperHelper
{
public static MapperConfiguration InitializeAutoMapper()
{
//Load here all your assemblies
var allClasses = AllClasses.FromLoadedAssemblies();
MapperConfiguration config = new MapperConfiguration(cfg =>
{
if (allClasses != null)
{
//here normally I add another Profiles that I use with reflection, marking my DTOs with an interface
cfg.AddProfile(new MappingModelsAndDtos(allClasses));
cfg.AddProfile(new MyCustomProfile());
}
});
return config;
}
}
now you need to implement the Profile, in this case MyCustomProfile
public class ModelProfile : Profile
{
public ModelProfile()
{
//put your source and destination here
CreateMap<MySource, MyDestination>()
.ConvertUsing<MySourceToMyDestination<MySource, MyDestination>>();
}
}
you need to implement MySourceToMyDestination class then.
Bellow is an example of the code of how I am using it in my projects
public class ApplicationModel2ApplicationDto : ITypeConverter<ApplicationModel, ApplicationDto>
{
public ApplicationDto Convert(ApplicationModel source, ApplicationDto destination, ResolutionContext context)
{
var mapper = context.Mapper;
try
{
destination = new ApplicationDto
{
ApplicationId = source.ApplicationId,
ApplicationName = source.ApplicationName,
Documents = mapper.Map<IEnumerable<DocumentDto>>(source.Documents),
Tags = mapper.Map<IEnumerable<TagInfoDto>>(source.TagInfos)
};
}
catch
{
return null;
}
return destination;
}
}
Hope this helps
Let me explain, I have a model list in which I have a little more than a thousand parameters, so I have to fill the list with some variables, the thing is that I don't want to do this:
list.Add(new Model{
name1= value,
name2= value,
.....
name1000=value
});
I have an array that contains the names of the parameters in the list, so I was wondering if is possible to use that array of the names and in a loop get the variables fill in, something like this:
list.Add(new Model{
//a loop?
array[0]= value
});
Thanks.
You can achieve this using reflection. Code below
public class ModelFactory
{
private IDictionary<string, PropertyInfo> propertiesInfo { get; set; }
public ModelFactory()
{
this.propertiesInfo = typeof(Model)
.GetProperties()
.ToDictionary(p => p.Name, p => p);
}
public Model Create(string[] propertiesToInitialize, dynamic value)
{
var model = new Model();
foreach (var propertyName in propertiesToInitialize)
{
if (this.propertiesInfo.ContainsKey(propertyName))
{
var property = this.propertiesInfo[propertyName];
property.SetValue(model, value);
}
}
return model;
}
}
Model to initialize
public class Model
{
public int MyProperty1 { get; set; }
public int MyProperty2 { get; set; }
public int MyProperty3 { get; set; }
public int MyProperty4 { get; set; }
public int MyProperty5 { get; set; }
}
Usage
public void Test()
{
var propertiesToInitialize = new string[] { "MyProperty1", "MyProperty2", "MyProperty4" };
var modelFactory = new ModelFactory();
var list = new List<Model>();
list.Add(modelFactory.Create(propertiesToInitialize, 500));
Console.WriteLine("MyProperty1 " + list[0].MyProperty1); // 500
Console.WriteLine("MyProperty2 " + list[0].MyProperty2); // 500
Console.WriteLine("MyProperty3 " + list[0].MyProperty3); // 0
Console.WriteLine("MyProperty4 " + list[0].MyProperty4); // 500
Console.WriteLine("MyProperty5 " + list[0].MyProperty5); // 0
}
However as already mentioned in comments, please reconsider your model design because model with these many properties is not optimal.
I've been working on using reflection but its very new to me still. So the line below works. It returns a list of DataBlockOne
var endResult =(List<DataBlockOne>)allData.GetType()
.GetProperty("One")
.GetValue(allData);
But I don't know myType until run time. So my thoughts were the below code to get the type from the object returned and cast that type as a list of DataBlockOne.
List<DataBlockOne> one = new List<DataBlockOne>();
one.Add(new DataBlockOne { id = 1 });
List<DataBlockTwo> two = new List<DataBlockTwo>();
two.Add(new DataBlockTwo { id = 2 });
AllData allData = new AllData
{
One = one,
Two = two
};
var result = allData.GetType().GetProperty("One").GetValue(allData);
Type thisType = result.GetType().GetGenericArguments().Single();
Note I don't know the list type below. I just used DataBlockOne as an example
var endResult =(List<DataBlockOne>)allData.GetType() // this could be List<DataBlockTwo> as well as List<DataBlockOne>
.GetProperty("One")
.GetValue(allData);
I need to cast so I can search the list later (this will error if you don't cast the returned object)
if (endResult.Count > 0)
{
var search = endResult.Where(whereExpression);
}
I'm confusing the class Type and the type used in list. Can someone point me in the right direction to get a type at run time and set that as my type for a list?
Class definition:
public class AllData
{
public List<DataBlockOne> One { get; set; }
public List<DataBlockTwo> Two { get; set; }
}
public class DataBlockOne
{
public int id { get; set; }
}
public class DataBlockTwo
{
public int id { get; set; }
}
You might need something like this:
var endResult = Convert.ChangeType(allData.GetType().GetProperty("One").GetValue(allData), allData.GetType());
Just guessing, didn't work in C# since 2013, please don't shoot :)
You probably want something like this:
static void Main(string[] args)
{
var one = new List<DataBlockBase>();
one.Add(new DataBlockOne { Id = 1, CustomPropertyDataBlockOne = 314 });
var two = new List<DataBlockBase>();
two.Add(new DataBlockTwo { Id = 2, CustomPropertyDatablockTwo = long.MaxValue });
AllData allData = new AllData
{
One = one,
Two = two
};
#region Access Base Class Properties
var result = (DataBlockBase)allData.GetType().GetProperty("One").GetValue(allData);
var oneId = result.Id;
#endregion
#region Switch Into Custom Class Properties
if (result is DataBlockTwo)
{
var thisId = result.Id;
var thisCustomPropertyTwo = ((DataBlockTwo)result).CustomPropertyDatablockTwo;
}
if (result is DataBlockOne)
{
var thisId = result.Id;
var thisCustomPropertyOne = ((DataBlockOne)result).CustomPropertyDataBlockOne;
}
#endregion
Console.Read();
}
public class AllData
{
public List<DataBlockBase> One { get; set; }
public List<DataBlockBase> Two { get; set; }
}
public class DataBlockOne : DataBlockBase
{
public int CustomPropertyDataBlockOne { get; set; }
}
public class DataBlockTwo : DataBlockBase
{
public long CustomPropertyDatablockTwo { get; set; }
}
public abstract class DataBlockBase
{
public int Id { get; set; }
}