I have object structure as below
public class Source
{
public Connection Connection { get; set;}
}
public class Connection
{
public string Id { get; set;
public IEnumerable<Distributor> Distributors { get; set;
}
public class Distributor { get; set; }
{
public DistributorType Type {get; set;}
public string X { get; set; }
public string Y { get; set; }
}
public class Destination
{
public Distribution Distribution { get; set;
}
public class Distribution
{
public string Id { get; set;}
public string X { get; set; }
public string Y { get; set; }
}
I would like to map the Source to Destination for the property Distribution. The mapping is as below
Source.Connection.Distributors.FirstOrDefault().X => Destination.Distribution.X
Source.Connection.Distributors.FirstOrDefault().Y => Destination.Distribution.Y
Source.Connection.Id => Destination.Distribution.Id
I have tried using the Custom Resolver but no luck
public class CustomDistributorResolver : IValueResolver<Source, Destination, Distribution >
{
public Distribution Resolve(Source source, Destination destination, Distribution destMember, ResolutionContext context)
{
var result = source.Connection.Distributors.FirstOrDefault(x => x.DistributorType =="ABC");
if (result == null) return null;
return new Distribution
{
Id = source.Connection?.Id,
X = result.X,
Y = result.Y
};
}
}
Mapping Pofile
CreateMap<Source, Destination>()
.ForMember(d => d.Distribution, opt => opt.MapFrom( new CustomDistributorResolver()));
I always get Distribution value as NULL.
I am not sure what I am doing wrong here on mapping.
-Alan-
I am not very sure what you will deal with the IEnumerable. So I use linq to approach.
var id = Source.Connection.Id;
var distributions = Source.Connection.Distributors.Select(m=> new Distribution()
{
Id = id,
X = m.X,
Y = m.Y,
});
Automapper would like :
CreateMap<Source, Destination>()
.ForMember(dest => dest.Distribution.Id ,opt => opt.MapFrom(src => src.Connection.Id))
.ForMember(dest => dest.Distribution.X, opt => opt.MapFrom(src => src.Connection.Distributors.FirstOrDefault().X))
.ForMember(dest => dest.Distribution.Y, opt => opt.MapFrom(src => src.Connection.Distributors.FirstOrDefault().Y));
You can use a type converter
private class DestinationConverter : ITypeConverter<Source, Destination>
{
public Destination Convert(Source source,
Destination destination,
ResolutionContext context)
{
var result = source.Connection.Distributors.FirstOrDefault(x => x.Type == "ABC");
if (result == null) return null;
destination = new Destination();
destination.Distribution = new Distribution
{
Id = source.Connection?.Id,
X = result.X,
Y = result.Y
};
return destination;
}
}
and register the converter
CreateMap<Source, Destination>().ConvertUsing<DestinationConverter>();
Related
I want my union LINQ query to be evaluated on server side with EF Core.
There're entities:
public class Entity1
{
public int Id { get; set; }
public List<StringWithStyle> Names { get; set; } = new List<StringWithStyle>();
}
public class Entity2
{
public int Id { get; set; }
public StringWithStyle Name { get; set; }
}
public class StringWithStyle
{
public string Text { get; set; }
public bool IsBold { get; set; }
public bool IsItalic { get; set; }
public bool IsUpperCase { get; set; }
}
Their properties are stored in DbContext as json string using Value conversion:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity1>()
.HasKey(e => e.Id);
modelBuilder.Entity<Entity1>()
.Property(e => e.Names)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<StringWithStyle>>(v, (JsonSerializerOptions)null)
,
new ValueComparer<List<StringWithStyle>>(
(arr1, arr2) => arr1.Count() == arr2.Count() && !arr1.Except(arr2).Any(),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())))
);
modelBuilder.Entity<Entity2>()
.HasKey(e => e.Id);
modelBuilder.Entity<Entity2>()
.Property(e => e.Name)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<StringWithStyle>(v, (JsonSerializerOptions)null)
,
new ValueComparer<StringWithStyle>(
(val1, val2) => val1.Equals(val2),
c => c.GetHashCode())
);
}
I need to show both entities in one grid. So, I use such a query:
var entities1 = from e1 in dbContext.Set<Entity1>()
select new GridModel
{
Id = e1.Id,
IsFirst = true,
Names = e1.Names,
Name = default
};
var entities2 = from e2 in dbContext.Set<Entity2>()
select new GridModel
{
Id = e2.Id,
IsFirst = false,
Name = e2.Name,
Names = default
};
var grid = entities1.Union(entities2).ToList();
And it throws an Exception:
System.InvalidOperationException : Unable to translate set operation after client projection has been applied. Consider moving the set operation before the last 'Select' call.
Is it possible to to get such a query that is evaluating on server side?
*** UPDATE ***
There's GridModel class:
public class GridModel
{
public int Id { get; set; }
public bool IsFirst { get; set; }
public List<StringWithStyle> Names { get; set; }
public StringWithStyle Name { get; set; }
}
I have a business logic object User with ValueObject field UserId. I need to translate expression tree with User to similar tree with DbUser using AutoMapper.
Here is my setup:
public static void Main(string[] args)
{
// create new AutoMapper
var mapperConfiguration = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<UserId, string>().ConvertUsing(u => u.ToString());
cfg.CreateMap<string, UserId>().ConvertUsing(u => UserId.FromString(u));
cfg.CreateMap<User, DbUser>()
.ForMember(dbu => dbu.Id, opt => opt.MapFrom(u => u.Id.ToString()));
cfg.CreateMap<DbUser, User>()
.ForMember(u => u.Id, opt => opt.MapFrom(dbu => UserId.FromString(dbu.Id)));
cfg.AddExpressionMapping();
}
);
mapperConfiguration.AssertConfigurationIsValid();
IMapper mapper = mapperConfiguration.CreateMapper();
// map some expression
var idToFind = new UserId(Guid.NewGuid());
Expression<Func<User, bool>> bllExpression = x => x.Id == idToFind;
var dbExpression = mapper.MapExpression<Expression<Func<DbUser, bool>>>(bllExpression);
Console.WriteLine(dbExpression.ToString());
}
User and DbUser:
public class User
{
public UserId Id { get; set; }
public string Name { get; set; }
}
public class DbUser
{
public string Id { get; set; }
public string Name { get; set; }
}
UserId:
public class UserId
{
public Guid Id { get; }
public UserId(Guid id) => Id = id;
public override string ToString()
{
return Id.ToString();
}
public static UserId FromString(string value)
{
return new UserId(Guid.Parse(value));
}
public static bool operator ==(UserId a, UserId b)
{
return a?.Id == b?.Id;
}
public static bool operator !=(UserId a, UserId b)
{
return a?.Id != b?.Id;
}
}
So, when I convert those expressions I get this result:
x => (FromString(x.Id) == 799e50f4-339f-4207-819e-194bbe206ae2)
But I cannot use this expression as database query e.g. in EntityFramework or MongoDb. Because there is no FromString function. Instead I need to compare already flattened values, in this case string:
x => (x.Id == "799e50f4-339f-4207-819e-194bbe206ae2")
How can I get this expression?
Link to .NET Fiddle
Given the following classes and some example data, is there a way to take an instance of Foo and create a collection of Bar which would grab Foo.Id?
public class CreditsAndDebits
{
public decimal Credits { get; set; }
public decimal Debits { get; set; }
}
public class Foo
{
public int Id { get; set; }
public Dictionary<int, CreditsAndDebits> CreditsAndDebitsByYear { get; set; }
}
public class Bar
{
public int FooId { get; set; }
public int Year { get; set; }
public decimal Increase { get; set; }
public decimal Decrease { get; set; }
}
Example data:
var dGrohl = new Foo
{
Id = 13,
CreditsAndDebitsByYear = new Dictionary<int, CreditsAndDebits>() {
{ 2019, new CreditsAndDebits { Credits = 100, Debits = 40 } } ,
{ 2020, new CreditsAndDebits { Credits = 80, Debits = 20 } } }
};
Using the following configuration, I can map the CreditsAndDebitsByYear dictionary to a collection of "Bars" but I want to have Bar.FooId populated with the Id value from dGrohl and can't for the life of me figure out how to do it...
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<KeyValuePair<int, CreditsAndDebits>, Bar>()
.ForMember(dest => dest.Year, opt => opt.MapFrom(src => src.Key))
.ForMember(dest => dest.Increase, opt => opt.MapFrom(src => src.Value.Credits))
.ForMember(dest => dest.Decrease, opt => opt.MapFrom(src => src.Value.Debits));
});
var mapper = new AutoMapper.Mapper(config);
var bars = mapper.Map<IEnumerable<KeyValuePair<int, CreditsAndDebits>>, IEnumerable<Bar>>(dGrohl.CreditsAndDebitsByYear);
Based on #LucianBargaoanu's comment, I was able to get this to work by doing the following:
// Not necessary but for consistency
public const string FOO_ID_KEY = "FOO_ID_KEY";
var dGrohl = new Foo
{
Id = 13,
CreditsAndDebitsByYear = new Dictionary<int, CreditsAndDebits>() {
{ 2019, new CreditsAndDebits { Credits = 100, Debits = 40 } } ,
{ 2020, new CreditsAndDebits { Credits = 80, Debits = 20 } } }
};
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<KeyValuePair<int, CreditsAndDebits>, Bar>()
// Added following line:
.ForMember(dest => dest.FooId, opt => opt.MapFrom((src, dest, destMember, context) => context.Items[FOO_ID_KEY]))
.ForMember(dest => dest.Year, opt => opt.MapFrom(src => src.Key))
.ForMember(dest => dest.Increase, opt => opt.MapFrom(src => src.Value.Credits))
.ForMember(dest => dest.Decrease, opt => opt.MapFrom(src => src.Value.Debits));
});
var mapper = new AutoMapper.Mapper(config);
// added the opt=>opt.Items part below
var bars = mapper.Map<IEnumerable<KeyValuePair<int, CreditsAndDebits>>, IEnumerable<Bar>>(dGrohl.CreditsAndDebitsByYear, opt=>opt.Items[FOO_ID_KEY] = dGrohl.Id);
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
I am trying to do some conditional mapping, and all the documentation and questions I have read through doesn't seem to cover this particular conditional. I was hoping someone here would have the experience or know how on the best approach to this.
I am mapping an object that has two properties. However, I do not want to map EITHER property, if a specific property is of a value. To visualize this:
foreach(var object in objectB) {
If (object.propertyA == "SomeValue")
continue;
else
Mapper.Map<ObjectA>(object);
}
however, I want the AutoMapper equivalent of the above statement. So something more like:
cfg.CreateMap<ObjectB, ObjectA>()
.ForMember(dest => dest.PropertyA, m => m.Condition(source => source.PropertyA != "SomeValue"))
.ForMember(dest => dest.PropertyB, m => m.Condition(source => source.PropertyA != "SomeVAlue" ? source.PropertyB : ignore))
But the above version obviously does not work.
Thank you in advance for your assistance.
Could be achieved using conditional mapping, see Documentation(http://docs.automapper.org/en/latest/Conditional-mapping.html) for details. To cover arrays filtering case I created a custom type converter which is a little bit tricky (see http://docs.automapper.org/en/stable/Custom-type-converters.html). The updated example is below
using AutoMapper;
using System;
using System.Collections.Generic;
namespace ConsoleAppTest2
{
class Program
{
static void Main(string[] args)
{
Mapper.Initialize(cfg => {
//Using specific type converter for specific arrays
cfg.CreateMap<Foo[], FooDto[]>().ConvertUsing(new ArrayFilterTypeConverter<Foo[], FooDto[], Foo, FooDto>(
(src, dest) => (src.Age > 0)
));
cfg.CreateMap<Foo, FooDto>()
.ForMember(dest => dest.Age, opt => opt.Condition(src => (src.Age >= 0)))
.ForMember(dest => dest.CurrentAddress, opt =>
{
opt.Condition(src => (src.Age >= 0));
opt.MapFrom(src => src.Address);
});
});
var foo = new Foo() { Name = "Name", Address = "Address", Age = -1 };
var fooDTO = new FooDto();
var fooArray = new Foo[] {
new Foo() { Name = "Name1", Address = "Address1", Age = -1 },
new Foo() { Name = "Name2", Address = "Address2", Age = 1 },
new Foo() { Name = "Name3", Address = "Address3", Age = 1 }
};
var fooDTOArray = Mapper.Map<Foo[], FooDto[]>(fooArray); //get 2 elements instead of 3
Mapper.Map(foo, fooDTO);
//The result is we skipped Address and Age properties becase Age is negative
Console.ReadLine();
}
}
public class ArrayFilterTypeConverter<TSourceArray, TDestArray, TSource, TDest> : ITypeConverter<TSourceArray, TDestArray>
{
private Func<TSource, TDest, bool> filter;
public ArrayFilterTypeConverter(Func<TSource, TDest, bool> filter)
{
this.filter = filter;
}
public TDestArray Convert(TSourceArray source, TDestArray destination, ResolutionContext context)
{
var sourceArray = source as TSource[];
List<TDest> destList = new List<TDest>();
var typeMap = context.ConfigurationProvider.ResolveTypeMap(typeof(TSource), typeof(TDest));
foreach (var src in sourceArray)
{
var dest = context.Mapper.Map<TSource, TDest>(src);
if (filter(src, dest))
destList.Add(dest);
}
// Little hack to cast array to TDestArray
var result = (TDestArray)(object)destList.ToArray();
return result;
}
}
internal class FooDto
{
public string Name { get; set; }
public string CurrentAddress { get; set; }
public int Age { get; set; }
}
internal class Foo
{
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
}