Mappings : use CopyTo from a nested object to feed a parent property - c#

I have these classes :
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
public IEnumerable<string> SearchField { get; set; }
}
public class B
{
public string Name { get; set; }
}
I work on an application that requires to copy into the property "SearchField" all the data that can be searched. I'm trying to copy in this field the value of the Name property of all the B children, but for the moment I didn't find a way to do that...
Any idea ? My current code :
map
.Properties(props => props
.Text(t => t.Name(n => n.SearchField))
.Text(t => t.Name(n => n.Name))
.Object<B>(c => c
.Name(n => n.Children)
.Properties(p => p
.Text(t => t.Name(b => b.Name)).CopyTo(a => ???????)
)
)
)

Related

How apply condition for multiple properties

Hello I am trying to use AutoMapper for a mapping. Let's imagine I have a domain class like that and a DTO one with the exact same structure.
public class Address
{
public int State { get; set; }
public string StateCode { get; set; }
public int City { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Phone { get; set; }
public Address Address { get; set; }
...
}
I want to construct a mapping that allows me only set the properties Phone and Address (and others, but for simplicity let's assume is only these two) conditionally depending on the value of the property age. Moreover, if the condition of the age property is met I want conditionally set the property StateCode only if the property State is not null. The logic is described below.
var personDto = new personDto();
personDto.Id = person.Id;
personDto.Name = person.Name;
personDto.Age = person.Age;
if(person.Age > 18)
{
personDto.Phone = person.Phone;
personDto.Address = new AddressDto();
personDto.Address.State = person.Address.State;
if (person.Address.State != null)
{
personDto.Address.StateCode = person.Address.StateCode;
}
personDto.Address.Street = person.Address.Street;
personDto.Address.ZipCode = person.Address.ZipCode;
}
I am trying to apply the same logic using Auto mapper, but I did not find in the documentation a way to do this.
CreateMap<Person, PersonDto>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Id))
.ForMember(d => d.Age , o => o.MapFrom(s => s.Age))
.ForMember(d => d.Name , o => o.MapFrom(s => s.Name ))
.ForMember(d => d.Phone , o =>
{
o.Condition(s => s.Age > 18); // I do not want repeat same condition for every property
o.MapFrom(s => s.Phone );
})
.ForMember(d => d.Address, o =>
{
o.Condition(s => s.Age > 18);
o.MapFrom(s => s.Address); // how can I nest the if condition of the State property here?
})
...
How can I apply the same nested logic here on Auto mapper? The whole point is how I can configure the mapping of several properties respecting the same condition and configure the nested condition as shown previously.

How to assign null for the object if object members have no value - automapper c#

I am using automapper in c#.
class A
{
public int Value { get; set; }
public string Code { get; set; }
public B? Details { get; set; }
}
class B
{
public int Id { get; set;}
public string Name { get; set; }
}
class C
{
public int Value { get; set; }
public string Code { get; set; }
public int? DetailId { get; set; }
public string? DetailName { get; set; }
}
And in automapper I used like below:
CreateMap<C, A>()
.ForPath(o => o.Details.Id, b => b.MapFrom(z => z.DetailId))
.ForPath(o => o.Details.Name, b => b.MapFrom(z => z.DetailName))
.ReverseMap();
When I using like the above mapping, I get like output as
"details ": {
"id": 0,
"name": ""
}
I need to get Details value as null instead of an object type if it's members have no value. i.e) DetailId and DetailName have no value. How to get this?
"details" : null
You can user Conditional Mapping
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<C, B>()
.ForMember(o => o.Id, b => b.MapFrom(z => z.DetailId))
.ForMember(o => o.Name, b => b.MapFrom(z => z.DetailName));
cfg.CreateMap<C, A>()
.ForMember(o => o.Details, b => b.MapFrom((c, a, obj, context) => !string.IsNullOrEmpty(c.DetailName) ? context.Mapper.Map<B>(c) : null))
.ReverseMap();
});
You could do this with an AutoMapper after map action.
Something like this:
CreateMap<C, A>()
.ForPath(o => o.Details.Id, b => b.MapFrom(z => z.DetailId))
.ForPath(o => o.Details.Name, b => b.MapFrom(z => z.DetailName))
.AfterMap((src, dest) =>
{
dest.Details = src.DetailId.HasValue && src.DetailName != null
? dest.Details
: null;
})
.ReverseMap());
You can use IValueResolver interface to achieve what you require. Documentation: http://docs.automapper.org/en/stable/Custom-value-resolvers.html
Also there's a similar question: Automapper Mapping Multiple Properties to Single Property
Configuration:
CreateMap<C, A>()
.ForMember(o => o.Details, b => b.MapFrom<DetailsValueResolver>())
.ReverseMap();
Implementation:
// Note: this does not cover ReverseMap() when you would try to convert A to C
public class DetailsValueResolver : IValueResolver<C, A, B>
{
// Runs every time you map C to A
public B Resolve(C source, A destination, B destMember, ResolutionContext context)
{
// Covers cases where you can get null or empty DetailName, as well as null or zero DetailId
if (!string.IsNullOrEmpty(source.DetailName) && source.DetailId > 0)
{
return new B { Id = (int)source.DetailId, Name = source.DetailName };
}
return null;
}
}
You also can omit explicitly setting strings and classes as nullable types with ? as you do here:
public B? Details { get; set; }
public string? DetailName { get; set; }
Because string type and any class is null by default.

.Nest Fluent Dsl Aggregations

I am using Elasticsearch .Nest.
I need to create a search query that will return fileProperties objects but only of the latest version of each fileProperties.
Each FileId have many versions. So what I need is to do a groupBy FileId and get the whole document object with the Max version for each FileId.
This my class:
public class FileProperties
{
public string FileName { get; set; }
public string FileId { get; set; }
public long? Version { get; set; }
public string FileType { get; set; }
public Dictionary<string,string> DynamicProperties { get; set; }
}
and my code:
var searchResponse = client.Search<FileProperties>(s => s.AllIndices().Size(100)
.Query(q =>
q.Match(m => m
.Field(f => f.FileName)
.Query(fileId))
&& q.Match(m => m
.Field(f => f.Version)
.Query(version))).Aggregations(fstAgg => fstAgg
.Terms("file", f => f
.Field(z => z.FileId.Suffix("keyword"))
.ExecutionHint(TermsAggregationExecutionHint.GlobalOrdinals)
.Aggregations(sums => sums
.Max("version", son => son
.Field(f4 => f4.Version )
)
)
) ) );
But the results only show the key and the max value for that key.
what I want is the full document object if that is possible...
Any ideas?

copy_to object property field to main document field

I have my models as:
class Entity
{
public List<Prop> PropList { get; set; }
public List<string> Props { get; set; }
// others
}
class Prop
{
public string Name { get; set; }
public List<string> Tags { get; set; }
}
I want to copy all Prop's.Names in PropList to List<string> Props so that they are include in _source.
Is that possible with elasticsearch copy_to?
I tried the following, which didn't work,
.Mappings(des => des
.AutoMap()
.Properties(propsDes => propsDes
.Object<Prop>(objDes => objDes
.Name(propDes => propDes.PropList)
.AutoMap()
.Properties(objPropsDes => objPropsDes
.Text(s => s
.Name(e => e.Name)
.CopyTo(fieldDes => fieldDes.Field("props"))
)
)
)
)
)

Automapper Project.To breaks db IQueryable

I have entities:
[Table("Representatives")]
public class Representative
{
[Column("RepID"), Key]
public int Id { get; set; }
[Column("MemID")]
public int MemberId { get; set; }
public virtual Member Member { get; set; }
}
[Table("Members")]
public class Member
{
[Column("MemID"), Key]
public int Id { get; set; }
[ForeignKey("MemberId")]
public virtual ICollection<Representative> Representatives { get; set; }
}
where Representatives relate to Member as many to one. I want to retrieve some representatives with their parent members and project it all to some view model. I've configured automapper like this:
Mapper.CreateMap<Representative, RepresentativeViewModel>()
.ForMember(m => m.RepId, a => a.MapFrom(x => x.Id))
.ForMember(m => m.MemberModel, a => a.MapFrom(x => x.Member));
Mapper.CreateMap<Member, MemberViewModel>()
.ForMember(m => m.MemId, a => a.MapFrom(x => x.Id))
.ForMember(m => m.OrganizationName, a => a.MapFrom(x => x.Name));
If I run this code:
var prj = ctx.Representatives.Where(r => r.Id == 1).Project().To<RepresentativeViewModel>();
then I get expression tree that contains func invocation (debugged):
...
.Lambda #Lambda2<System.Func`2[ConsoleApplication1.Representative,ConsoleApplication1.RepresentativeViewModel]>(ConsoleApplication1.Representative $var1)
{
.New ConsoleApplication1.RepresentativeViewModel(){
Name = $var1.Name,
RepId = $var1.Id,
MemberModel = .Invoke (.Lambda #Lambda3<System.Func`2[ConsoleApplication1.Member,ConsoleApplication1.MemberViewModel]>)($var1.Member)
}
}
.Lambda #Lambda3<System.Func`2[ConsoleApplication1.Member,ConsoleApplication1.MemberViewModel]>(ConsoleApplication1.Member $var2)
{
.New ConsoleApplication1.MemberViewModel(){
MemId = $var2.Id,
OrganizationName = $var2.Name
}
}
...
so for some reason builder invokes Func instead of calling Expression. Do you have ideas why it's so? How can I make my mapper build correct expression and SQL query?

Categories

Resources