Automapper - Map List to object based on value in existing destination - automapper

I am trying to take an already existing object and use automapper to map another property from a List into it based on a key already in the destination.
Given the following snippet below, Resolve state description will receive only the StateCode, and I need to map the StateName from the list of States into the object.
public Location ResolveStateDescription(Location location)
{
var stateList = new List<State> {
new State { StateCode = "CA", StateName = "California" },
new State { StateCode = "CO", StateName = "Colorado" },
new State { StateCode = "NV", StateName = "Nevada" },
};
var mappedLocation = _mapper.Map(stateList, location);
return mappedLocation;
}
public class State
{
public string StateCode { get; set; }
public string StateName { get; set; }
}
public class Location
{
public string City { get; set; }
public string StateCode { get; set; }
public string StateName { get; set; }
}
I have already done so using a Custom Resolver and the Profile below. I am wondering if there is a simpler way to do this without so much overhead.
public class StateMapProfile : Profile
{
public StateMapProfile()
{
CreateMap<List<State>, Location>()
.ForMember (dest => dest.StateName, opt => opt.MapFrom<StateCodeResolver>());
}
}
public class StateCodeResolver : IValueResolver<IEnumerable<State>, Location, string>
{
public string Resolve(IEnumerable<State> source, Location destination, string destMember,
ResolutionContext context)
{
return source
.Where(x => x.StateCode.Equals(destination.StateCode))
.Select(x => x.StateName).FirstOrDefault();
}
}

Related

How can i execute filter from our JSON filter JSON?

I have a vue3 datagrid and I want to fill the data in this grid with filter by API. At the same time, I want to send the filter fields in the grid to the API as JSON and execute them according to this filter on the API side. How can I do this with AutoQuery?
[Route("/GetConnectors", "POST")]
public class GetConnectors : QueryDb<Connector>
{
public string Id { get; set; }
public string PageParameterJson { get; set; }
}
public class Connector
{
[PrimaryKey]
[AutoIncrement]
public long PKey { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class PageParameters
{
public string Field { get; set; }
public string Operand { get; set; }
public string Value { get; set; }
public string Type { get; set; }
}
It's an example PageParameter JSON;
[
{
"Field":"Name",
"Operand":"cn"//Contains
"Value":"test",
"Type":"string"
},
{
"Field":"Id",
"Operand":"eq"//Equal
"Value":"2",
"Type":"string"
}
]
public async Task<object> Any(GetConnectors query)
{
using var db = AutoQuery.GetDb(query, base.Request);
var filters = query.PageParameters.FromJson<List<PageParameter>>();
//How can I execute query with for CreateQuery?
var q = AutoQuery.CreateQuery(query, Request, db);
var sql = q.PointToSqlString();
return await AutoQuery.ExecuteAsync(query, q, base.Request, dbConnection);
}
Best Regards
i can't execute dynamic filters from server-side datagrid
The AutoQuery.CreateQuery returns OrmLite's Typed SqlExpression which has a number of filtering options inc .Where(), .And(), .Or(), etc.
So you should be able to populate it with something like:
foreach (var filter in filters)
{
var type = filter.Type switch
{
"string" => typeof(string),
_ => throw new NotSupportedException($"Type {filterType}")
};
var value = filter.Value.ConvertTo(type);
if (filter.Operand == "eq")
{
q.And(filter.Field + " = {0}", value)
}
else if (filter.Operand == "cn")
{
q.And(filter.Field + " LIKE {0}", $"%{value}%")
}
else throw new NotSupportedException(filter.Operand);
}
Note: I've rewritten API to be async as you should never block on async methods.

Automapper map inside nested Enumerable properties

I would like to use automapper to map nested enumerable properties in existing enumerable nested properties.
Here is the problem simplify, i have Entities objects, they implement IEntity Interface, and Models objects, they implment IModel interfaces.
I would like to update easily and in a smart way IEntity object with IModel object
Example :
public class PersonEntity : IEntity
{
public int Id { get; set; }
public string Name { get; set }
public IEnumerable<AddressEntity> Addresses { get; set }
}
public class AddressEntity : IEntity
{
public int Id { get; set; }
public string Street { get; set }
public string entityField { get; set }
}
public class PersonModel : IModel
{
public int Id { get; set; }
public string Name { get; set }
public IEnumerable<AddressModel> Addresses { get; set }
}
public class AddressModel : IModel
{
public int Id { get; set; }
public string Street { get; set }
}
If i have these two instance og PersonEntity and AddressEntity :
var personModel = new PersonModel()
{
Id = 1,
Name = "Name",
Addresses = new List<AddressModel>() {
new AdressModel() { Id = 1, Street = "Street B" }
}
var person = new PersonEntity()
{
Id = 1,
Name = "Name",
Addresses = new List<AddressEntity>() {
new AdressEntity() { Id = 1, Street = "StreetA", EntityField ="entityValue" }
}
How can i map by Id, the Addresses element, to only erase the StreetA value by StreetB value, and keep existing entityField value intact ?
Thank you for help !

Generic Automapper function with custom convension for underscored properties

I simply need to map some auto generated classes from database to domain/viewmodels classes. The autogenerated class may have names like customer_id that I want to be mapped with CustomerId. Somehow I want to register my own convention with auto mapper. I have started with following code however the mapped object properties are not populated:
// Generic method that should map source to target
public static TTarget MapWithUnderScoreConvension(TSource source, TTarget target)
{
Mapper.Initialize(cfg=> cfg.AddProfile<AutoMapperUnderScoreProfile>());
Mapper.CreateMap<TSource, TTarget>();
var mapped = Mapper.Map(source, target);
return mapped;
}
// New underscore profile
public class AutoMapperUnderScoreProfile : Profile
{
public AutoMapperUnderScoreProfile()
{
Mapper.Initialize(configuration => configuration.CreateProfile("UnderScoreProfile", UnderScoreProfile));
Mapper.AssertConfigurationIsValid();
}
private void UnderScoreProfile(IProfileExpression profile)
{
profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
profile.DestinationMemberNamingConvention = new SourceUnderScoreNamingConvension();
}
}
// Convention class
public class SourceUnderScoreNamingConvension : INamingConvention
{
private readonly string _separatorCharacter="_";
private readonly Regex _splittingExpression = new Regex(#"[\p{Lu}0-9]+(?=_?)");
public Regex SplittingExpression { get { return _splittingExpression;} private set{} }
public string SeparatorCharacter { get { return _separatorCharacter; } private set{} }
}
// Test cases
[TestMethod()]
public void Test_Map_Db_Generated_Class_To_Model()
{
var dbGenerated = new QuestionTypeFromDb()
{
QuestionType_Description = "1",
QuestionType_Id = 1,
QuestionType_Is_Default = true,
QuestionType_Is_TimeBased = true,
QuestionType_Sequence = 1,
QuestionType_Time_In_Seconds = 1
};
var mappedObject = AutoMapperHelper<QuestionTypeFromDb, QuestionType>
.MapWithUnderScoreConvension(dbGenerated, new QuestionType());
mappedObject.QuestionTypeId.Should().Be(dbGenerated.QuestionType_Id);
mappedObject.QuestionTypeDescription.Should().Be(dbGenerated.QuestionType_Description);
mappedObject.TimeInSeconds.Should().Be(dbGenerated.QuestionType_Time_In_Seconds);
mappedObject.QuestionTypeSequence.Should().Be(dbGenerated.QuestionType_Sequence);
}
public class TestQuestionWithAnswerType
{
public int QuestionTypeId { get; set; }
public string QuestionTypeDescription { get; set; }
public int QuestionTypeSequence { get; set; }
public bool QuestionTypeIsTimeBased { get; set; }
public int? QuestionTypeTimeInSeconds { get; set; }
public bool QuestionTypeIsDefault { get; set; }
}
any comments will be appreciated.
Update
I have found that the following workaround works:
I simply replaced used this -> to replace 'underscore' with nothing (Mapper.Initialize(c => c.ReplaceMemberName("_", ""));
public static TTarget MapWithUnderScoreConvension(TSource source, TTarget target)
{
Mapper.Initialize(c => c.ReplaceMemberName("_", ""));
//Mapper.Initialize(cfg => cfg.AddProfile<AutoMapperUnderScoreProfile>());
Mapper.CreateMap<TSource, TTarget>();
var mapped = Mapper.Map(source, target);
return mapped;
}
Your regex needs to be changed to : [\p{L}}0-9]+(?=_?)
This will take care of Customer_Id, CUSTOMER_ID, customer_id
The {L} unicode category includes Lu, Lt, Ll, Lm and Lo characters as mentioned here.
Answer is added in the Update section of the question. Basically the solution for me was very simple -> Mapper.Initialize(c => c.ReplaceMemberName("_", ""));

Can AutoMapper implicitly flatten this mapping?

I am trying to map between two lists of objects. The source type has a complex property of type A; the destination type is a flattened subset of type A plus an additional scalar property that is in the source type.
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Source
{
public A MyA { get; set; }
public int SomeOtherValue { get; set; }
}
public class Destination
{
public string Name { get; set; }
public int SomeOtherValue { get; set; }
}
If it's not clear, I'd like Source.MyA.Name to map to Destination.Name and Source.SomeOtherValue to map to Destination.SomeOtherValue.
In reality, type A has a dozen or so properties, about which 80% map over to properties of the same name in Destination. I can get things to work if I explicitly spell out the mappings in CreateMap like so:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.MyA.Name));
The downside here is I want to avoid having to add a ForMember line for each of A's properties that need to get copied over to Destination. I was hoping I could do something like:
CreateMap<Source, Destination>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.MyA));
But if I try the above I get a runtime error when the mapping is registered: "Custom configuration for members is only supported for top-level individual members on a type."
Thanks
create mappings between A and Destination, and Source and Destination, and then use AfterMap() to use first mapping in second
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
.AfterMap((s, d) => Mapper.Map<A, Destination>(s.MyA, d));
then use it like this:
var res = Mapper.Map<Source, Destination>(new Source { SomeOtherValue = 7, MyA = new A { Id = 1, Name = "SomeName" } });
As a workaround you can use custom type converter with additional property in the destination type to avoid recursion.
[TestFixture]
public class MapComplexType
{
[Test]
public void Map()
{
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>().ConvertUsing(new TypeConvertor());
var source = new Source
{
MyA = new A
{
Name = "Name"
},
SomeOtherValue = 5
};
var dest = new Destination();
Mapper.Map(source, dest);
Assert.AreEqual(dest.Name, "Name");
}
}
public class TypeConvertor : ITypeConverter<Source, Destination>
{
public Destination Convert(ResolutionContext context)
{
var destination = (Destination) context.DestinationValue;
if (!((Destination)context.DestinationValue).IsMapped || destination == null)
{
destination = destination ?? new Destination();
destination.IsMapped = true; // To avoid recursion
Mapper.Map((Source)context.SourceValue, destination);
destination.IsMapped = false; // If you want to map the same object few times
}
Mapper.Map(((Source)context.SourceValue).MyA, destination);
return (Destination)context.DestinationValue;
}
}
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Source
{
public A MyA { get; set; }
public int SomeOtherValue { get; set; }
}
public class Destination
{
public string Name { get; set; }
public int SomeOtherValue { get; set; }
// Used only for mapping purposes
internal bool IsMapped { get; set; }
}
Try this,
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
.ForMember(destination => destination.Name, options => options.MapFrom(source => Mapper.Map<A, Destination>(source.MyA).Name));
var objSource = new Source { SomeOtherValue = 7, MyA = new A { Id = 1, Name = "SomeName" } };
var result = Mapper.Map<Source, Destination>(objSource);

ValueInjecter question

After working with AutoMapper I came across ValueInjecter on this site. I am trying it out but I am stuck on what is probably a very simple scenario.
But before I dig into the code sample, does anyone know if ValueInjecter works in a Medium-Trust web environment? (Like Godaddy?)
Ok, onto the code! I have the following models:
public class NameComponent
{
public string First { get; set; }
public string Last { get; set; }
public string MiddleInitial { get; set; }
}
public class Person
{
public NameComponent Name { get; set; }
}
that I want to map to the following DTO:
public class PersonDTO : BaseDTO
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { NotifyPropertyChanged(() => FirstName, ref _firstName, value); }
}
private string _middleInitial;
public string MiddleInitial
{
get { return _middleInitial; }
set { NotifyPropertyChanged(() => MiddleInitial, ref _middleInitial, value); }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { NotifyPropertyChanged(() => LastName, ref _lastName, value); }
}
}
So when I want to Map from Model to DTO I need a Model.Name.First -> DTO.FirstName
and when going from DTO to Model I need FirstName -> Name.First. From my understanding this is not a simple Flatten/UnFlatten, because the words also reverse themselves, ie: FirstName <--> Name.First. So First and Last names could use the same kind of rule, but what about MiddleInitial? Model.Name.MiddleInitial -> DTO.MiddleInitial.
I see there are some plugins, but none of them seem to do what I would want. Has anyone else come across this scenario?
the basic idea is that I match the Name with the FirstName, I take this as a pivot point, and in the method that usually sets the value to just one (FirstName) property I set it to 3 properties - that's for the FromNameComp
in the ToNameComp i match the same properties but I take the value from 3 and create one and set it
public class SimpleTest
{
[Test]
public void Testit()
{
var p = new Person { Name = new NameComponent { First = "first", Last = "last", MiddleInitial = "midd" } };
var dto = new PersonDTO();
dto.InjectFrom<FromNameComp>(p);
Assert.AreEqual(p.Name.First, dto.FirstName);
Assert.AreEqual(p.Name.Last, dto.LastName);
Assert.AreEqual(p.Name.MiddleInitial, dto.MiddleInitial);
var pp = new Person();
pp.InjectFrom<ToNameComponent>(dto);
Assert.AreEqual(dto.LastName, pp.Name.Last);
Assert.AreEqual(dto.FirstName, pp.Name.First);
Assert.AreEqual(dto.MiddleInitial, pp.Name.MiddleInitial);
}
public class FromNameComp : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == "Name" && c.SourceProp.Type == typeof(NameComponent)
&& c.TargetProp.Name == "FirstName"
&& c.SourceProp.Value != null;
}
protected override object SetValue(ConventionInfo c)
{
dynamic d = c.Target.Value;
var nc = (NameComponent)c.SourceProp.Value;
//d.FirstName = nc.First; return nc.First does this
d.LastName = nc.Last;
d.MiddleInitial = nc.MiddleInitial;
return nc.First;
}
}
public class ToNameComponent : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.TargetProp.Name == "Name" && c.TargetProp.Type == typeof(NameComponent)
&& c.SourceProp.Name == "FirstName";
}
protected override object SetValue(ConventionInfo c)
{
dynamic d = c.Source.Value;
var nc = new NameComponent { First = d.FirstName, Last = d.LastName, MiddleInitial = d.MiddleInitial };
return nc;
}
}
public class NameComponent
{
public string First { get; set; }
public string Last { get; set; }
public string MiddleInitial { get; set; }
}
public class Person
{
public NameComponent Name { get; set; }
}
public class PersonDTO
{
public string FirstName { get; set; }
public string MiddleInitial { get; set; }
public string LastName { get; set; }
}
}
But before I dig into the code sample,
does anyone know if ValueInjecter
works in a Medium-Trust web
environment? (Like Godaddy?)
it doesn't use reflection.emit so it should work

Resources