I am trying to map a Dictionary to a POCO with the builtin mapper and apply an additional transformation on strings with a global ValueTransformer:
class MyPoco
{
public string StringValue { get; set; }
}
The mapper call:
var source = new Dictionary<string, object>
{
{ "StringValue", "abc" }
};
var mapper = new MapperConfiguration(cfg =>
{
cfg.ValueTransformers.Add<string>(dest => dest + "!!!");
})
.CreateMapper();
var poco = mapper.Map<MyPoco>(source);
Assert.Equal("abc!!!", poco.StringValue);
After the mapping call the poco.StringValue equals to 'abc' and the configured ValueTransfomer is never called. Am I missing something?
Related
I would like AutoMapper to map same object instances Source to the same instances of Target.
I'm sure that AutoMapper can be configured to do this, but how? I need to convert an object graph A to B, keeping the references between the mapped objects.
For example, this fails:
[Fact]
public void Same_instances_are_mapped_to_same_instances()
{
var configuration = new MapperConfiguration(config => config.CreateMap<Source, Target>());
var mapper = configuration.CreateMapper();
var source = new Source();
var list = mapper.Map<IEnumerable<Target>>(new[] { source, source, source });
list.Distinct().Should().HaveCount(1);
}
public class Source
{
}
public class Target
{
}
It would like to make this test pass.
Also, please notice that the mapper should "remember" instances mapped during the current Map call.
That's the nearest you can get:
public static class Program
{
public static void Main()
{
var config = new MapperConfiguration(conf => conf.CreateMap<Source, Target>().PreserveReferences());
var mapper = config.CreateMapper();
var source = new Source();
var list = new[] { source, source, source };
var firstRun = mapper.Map<IEnumerable<Target>>(list);
var secondRun = mapper.Map<IEnumerable<Target>>(list);
// Returns two items
var diffs = firstRun.Concat(secondRun).Distinct();
foreach (var item in diffs)
{
Console.WriteLine(item.Id);
}
}
}
public class Source
{
public Guid Id { get; } = Guid.NewGuid();
}
public class Target
{
public string Id { get; set; }
}
This means, that you only get the same item back in case of each call to mapper.Map(), but if you call the mapper multiple times, you'll get back new items for each call (which makes sense, otherwise the mapper had to hold references to all given and created instances over it's whole lifetime, which could lead to some serious memory problems).
This question already exists:
How to solve Npgsql.NpgsqlOperationInProgressException: A command is already in progress
Closed 3 years ago.
I'm using my Map method to create DTO object from my context class Company and it looks like this:
private CompDTO Map(Company company)
{
return new CompDTO()
{
Id = company.Id,
Title = company.Title,
ParentCompanyId = company.ParentCompanyId,
};
}
CompDTO looks like this:
public class CompDTO
{
public long Id { get; set; }
public string Title { get; set; }
public long? ParentCompanyId { get; set; }
public bool HasChildrens { get; set; }
}
I'm using it like this, basically receiving list of companies and calling another Map method which would create DTO object from my company objects and main issue for me is that Company class does not contain HasChildrens property, so I have to populate it somehow, and I couldn't do it where I'm maping other props because there I dont have access to a companies list.
private IEnumerable<CompDTO> Map(IEnumerable<Company> companies)
{
// Mapping all properties except HasChildrens because it does not exist in Company object so I decided to map it later
var result = companies.Select(c => Map(c));
// Here I wanted to return all previously mapped objects + I would like to add to each object HasChildren property, but obliviously my syntax is not good:
return result.Select(c => new { c, c.HasChildrens = companies.Any(cc => cc.ParentCompanyId == c.Id) });
}
I'm retrieving error: Invalid anonymous type declarator.
I've tried to add HasChildrens like this also:
return result.Select(c => {c.HasChildrens = companies.Any(cc => cc.ParentCompanyId == c.Id)});
But still issues..
Basically I simply want to add HasChildrens for each my Mapped DTO and return it as it was added in Map method.
Any kind of help would be great!
Thanks
The return type of your method private IEnumerable<CompDTO> Map(IEnumerable<Company> companies) is IEnumerable<CompDTO>
So the issue is that you're returning an anonymous type rather than the expected CompDTO
Change return result.Select(c => new { ... }
to
return result.Select(c => new CompDTO {
Id = ...
Title = ...
ParentCompanyId = ...
HasChildrens = ...
})
EDIT:
The actual question is:
How do I set the property HasChildrens in the CompDTO while converting from db classes to dto classes
I'd say that the most common way to solve that is to pass in the value while converting:
private CompDTO Map(Company company, bool hasChildrens) {
return new CompDTO()
{
Id = company.Id,
Title = company.Title,
ParentCompanyId = company.ParentCompanyId,
HasChildrens = hasChildrens
};
}
You could also iterate the result after the fact and set the HasChildrens like so: (I wouldn't recommend this)
foreach(dto in result) {
dto.HasChildrens = ...
}
You could also insert the logic of obtaining the HasChildrens value inside of the Map method: (I wouldn't recommend this either)
private CompDTO Map(Company company, IEnumerable<Company> companies) {
return new CompDTO()
{
Id = company.Id,
Title = company.Title,
ParentCompanyId = company.ParentCompanyId,
HasChildrens = companies
.Any(c => c.ParentCompanyId == company.Id)
};
}
I've got the following mapping working with for key value pairs based on construct using:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<KeyValuePair<MenuTable, List<RoleTable>>,
KeyValuePair<Menu, List<Role>>>()
.ConstructUsing(x =>
new KeyValuePair<Menu, List<Role>>(
Mapper.Map<Menu>(x.Key),
Mapper.Map<List<Role>>(x.Value)
)
);
});
So that I can call it like this:
return Mapper.Map<List<KeyValuePair<Menu, List<Role>>>>(results);
However, this means I need to do this for any time I have such kind of query results. Which could be over 100, and they all use the default flattening for mapping, just in a keyvaluepair collection. How can I make this generic? I haven't quite gotten a grasp on generics in Automapper. The documentation confuses me. I don't see how to declare generic variables.
You actually need no mapping at all:
Generic lists are supported by default: See the sample/fiddle & http://docs.automapper.org/en/stable/Lists-and-arrays.html
KeyValuePair<,> objects can be mapped by Automapper as long as the Key and Value objects themselves can be mapped: See the sample/fiddle
The only mapping that could be usefull is a cfg.CreateMap<MenuTable, Menu>().ForMember(...) in case a property mismatches.
Fiddle: https://dotnetfiddle.net/1UILC3
Sample:
// Sample classes
public class MenuTable
{
public string MenuProp1 {get;set;}
}
public class RoleTable
{
public string RoleProp1 {get;set;}
}
public class Menu
{
public string MenuProp1 {get;set;}
}
public class Role
{
public string RoleProp1 {get;set;}
}
public class Program
{
public static void Main()
{
// Mapper config
Mapper.Initialize(cfg => {});
// sample Data
var menuTable1 = new MenuTable() {MenuProp1="Menu1"};
var menuTable2 = new MenuTable() {MenuProp1="Menu2"};
var roleTable1 = new RoleTable() {RoleProp1="Role1"};
var roleTable2 = new RoleTable() {RoleProp1="Role2"};
// Map by property name
var target = Mapper.Map<Menu>(menuTable1);
Console.WriteLine("Map by property by name: " + target.MenuProp1);
Console.WriteLine();
// result: "Map by property by name: Menu1"
// Map KeyValuePair
var kvpSource = new KeyValuePair<MenuTable, RoleTable>(menuTable1, roleTable1);
var kvpTarget = Mapper.Map<KeyValuePair<Menu, Role>>(kvpSource);
Console.WriteLine("Map KeyValuePair: " + kvpTarget.Key.MenuProp1 + " - " + kvpTarget.Value.RoleProp1);
Console.WriteLine();
// result: "Map KeyValuePair: Menu1 - Role1"
// Map List
var listSource = new List<MenuTable>() {menuTable1, menuTable2};
var listTarget = Mapper.Map<List<Menu>>(listSource);
foreach(var item in listTarget)
{
Console.WriteLine("Map List:" + item.MenuProp1);
}
Console.WriteLine();
// result:
// Map List:Menu1
// Map List:Menu2
// Combination
var combinedSource = new List<KeyValuePair<MenuTable, List<RoleTable>>>()
{
new KeyValuePair<MenuTable, List<RoleTable>>(menuTable1, new List<RoleTable>(){roleTable1}),
new KeyValuePair<MenuTable, List<RoleTable>>(menuTable2, new List<RoleTable>(){roleTable2})
};
var combinedTarget = Mapper.Map<List<KeyValuePair<Menu, List<Role>>>>(combinedSource);
foreach(var item in combinedTarget)
{
Console.WriteLine("Combined: " + item.Key.MenuProp1 + " - " + item.Value.First().RoleProp1);
}
// result:
// Combined: Menu1 - Role1
// Combined: Menu2 - Role2
}
}
My correct index path is POST: /foo/_search but below code hits POST: /foo/bar/_search.
var node = new Uri("http://elasticsearch-server.com:9200");
var settings = new ConnectionSettings(node);
settings.DefaultIndex("foo");
var client = new ElasticClient(settings);
var response = client.Search<Bar>(s => s
.Query(q => q.Term(o => o.userName, "test"))
);
// POCO for response fields
public class Bar
{
public int userId { get; set; }
public string userName { get; set; }
public DateTime createdTime { get; set; }
}
Above code response returns below message;
Valid NEST response built from a successful low level call on POST: /foo/bar/_search
How can I set search path correctly?
Trial 1
When I omitted settings.DefaultIndex("foo"); line, it throws ArgumentException as below, but when I set DefaultIndex(), Search<T> uses T name as a second path.
ArgumentException: Index name is null for the given type and no default index is set. Map an index name using ConnectionSettings.MapDefaultTypeIndices() or set a default index using ConnectionSettings.DefaultIndex().
Trial 2
Refer to the documentation,
var settings = new ConnectionSettings(node)
.MapDefaultTypeIndices(m => m.Add(typeof(Bar), "foo"));
Above code returns same result in response.
Valid NEST response built from a successful low level call on POST: /foo/bar/_search
A large proportion of the Elasticsearch API exposed through NEST is in a strongly typed fashion, including .Search<T>(); with this endpoint, both "index" and "type" will be inferred from T, but sometimes you might want to set a different value to that which is inferred. In these cases, you can call additional methods on the search fluent API (or search object, if using the object initializer syntax) to override the inferred values
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(pool)
.DefaultIndex("foo");
var client = new ElasticClient(connectionSettings);
// POST http://localhost:9200/foo/bar/_search
// Will try to deserialize all _source to instances of Bar
client.Search<Bar>(s => s
.MatchAll()
);
// POST http://localhost:9200/foo/_search
// Will try to deserialize all _source to instances of Bar
client.Search<Bar>(s => s
.AllTypes()
.MatchAll()
);
// POST http://localhost:9200/_search
// Will try to deserialize all _source to instances of Bar
client.Search<Bar>(s => s
.AllTypes()
.AllIndices()
.MatchAll()
);
connectionSettings = new ConnectionSettings(pool)
.InferMappingFor<Bar>(m => m
.IndexName("bars")
.TypeName("barbar")
);
client = new ElasticClient(connectionSettings);
// POST http://localhost:9200/bars/barbar/_search
// Will try to deserialize all _source to instances of Bar
client.Search<Bar>(s => s
.MatchAll()
);
// POST http://localhost:9200/bars/_search
// Will try to deserialize all _source to instances of Bar
client.Search<Bar>(s => s
.AllTypes()
.MatchAll()
);
// POST http://localhost:9200/_all/barbar/_search
// Will try to deserialize all _source to instances of Bar
client.Search<Bar>(s => s
.AllIndices()
.MatchAll()
);
// POST http://localhost:9200/_search
// Will try to deserialize all _source to instances of Bar
client.Search<Bar>(s => s
.AllIndices()
.AllTypes()
.MatchAll()
);
}
public class Bar
{
public int userId { get; set; }
public string userName { get; set; }
public DateTime createdTime { get; set; }
}
You may add other parameters in your search lambda expression var response = client.Search<Bar>(s => s.Index("indexName").Query(q => q.Term(o => o.userName, "test")));
I was newbie in ElasticSearch and didn't know about _type.
I set identical _type name to POCO class name, and it works as I expected.
So we can say, {index}/{type} is what path expression is.
I need to map an object to another one using AutoMapper. The tricky question is how can I access an instance of the mapper (instance of IMapper) inside of the mapping configuration or inside of a custom type converter?
The code below does not work, however it is an example of what I would like to achieve - please notice the mapper.Map calls and assume that mappings Customer => CustomerDto and Customer => DetailedCustomerDto are defined.
var config = new MapperConfiguration(
cfg => cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing(o => {
return o.Type == 1
? mapper.Map<Customer, CustomerDto>(o.Customer)
: mapper.Map<Customer, DetailedCustomerDto>(o.Customer)
})
);
The client part is:
var mapper = config.CreateMapper();
var orderDto = mapper.Map<Order, OrderDto>(order);
The simplified version of objects I want to map is:
public class Order
{
public int Type { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public long Id { get; set; }
public string Name { get; set; }
}
public class OrderDto
{
public CustomerDto Customer { get; set; }
}
public class CustomerDto
{
public long Id { get; set; }
}
public class DetailedCustomerDto : CustomerDto
{
public string Name { get; set; }
}
As you see from the code above, based on the value of Order.Type, the mapper should map the property Order.Customer to different targets. As one target (DetailedCustomerDto) inherits from the other one (CustomerDto) it becomes a bit tricky.
Please notice that usage of the obsolete and deprecated static method Mapper.Map is NOT an option.
As of AutoMapper 8.0 and up
The answer below for 5.1.1 still applies, but note that the use of ResolveUsing has been replaced with an overload of MapFrom, but the signature has otherwise remained consistent.
As of AutoMapper 5.1.1
You can get to the mapper using another overload of ResolveUsing with four parameters, fourth of which is ResolutionContext (context.Mapper):
var config = new MapperConfiguration(
cfg => {
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Customer, DetailedCustomerDto>();
cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing((order, orderDto, i, context) => {
return order.Type == 1
? context.Mapper.Map<Customer, CustomerDto>(order.Customer)
: context.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
}));
});
var orderTypeOne = new Order();
orderTypeOne.Type = 1;
orderTypeOne.Customer = new Customer() {
Id = 1
};
var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));
var orderTypeTwo = new Order();
orderTypeTwo.Type = 2;
orderTypeTwo.Customer = new Customer() {
Id = 1
};
dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));
Prior to AutoMapper 5.1.1
You can get to the mapper using another overload of ResolveUsing with two parameters, first of which is ResolutionResult (result.Context.Engine.Mapper):
var config = new MapperConfiguration(
cfg => {
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Customer, DetailedCustomerDto>();
cfg.CreateMap<Order, OrderDto>()
.ForMember(dst => dst.Customer, src => src.ResolveUsing((result, order) => {
return order.Type == 1
? result.Context.Engine.Mapper.Map<Customer, CustomerDto>(order.Customer)
: result.Context.Engine.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
}));
});
var orderTypeOne = new Order();
orderTypeOne.Type = 1;
orderTypeOne.Customer = new Customer() {
Id = 1
};
var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));
var orderTypeTwo = new Order();
orderTypeTwo.Type = 2;
orderTypeTwo.Customer = new Customer() {
Id = 1
};
dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));
In addition to Evk's great answer, which helped me, if you need to do a mapping inside a mapping inside a config/profile that requires a custom constructor (i.e. the type has no default constructor), the following will work in v5.2.0:
CreateMap<Models.Job, Models.API.Job>(MemberList.Source);
CreateMap<StaticPagedList<Models.Job>, StaticPagedList<Models.API.Job>>()
.ConstructUsing((source, context) => new StaticPagedList<Models.API.Job>(
context.Mapper.Map<List<Models.Job>, List<Models.API.Job>>(source.ToList()),
source.PageNumber,
source.PageSize,
source.TotalItemCount));
In this example I'm mapping the X.PagedList custom collection type of one object type onto an equivalent collection of another object type. The first parameter to the lamdba expression is your source object, the second is your ResolutionContext from which you can access a mapper instance to map from.
I'm using Automapper 9 and the answers above didn't work for me.
Then for resolve my problem that is like yours I use .afterMap, like that:
public class AutoMapperOrder : Profile
{
public AutoMapperOrder()
{
CreateMap<Customer, CustomerDto>()
//...
CreateMap<Customer, DetailedCustomerDto>()
//...
CreateMap<Order, OrderDto>()
.AfterMap((src, dest, context) => {
dest.Customer = src.Type == 1
? context.Mapper.Map<Customer, CustomerDto>(src.Customer)
: context.Mapper.Map<Customer, DetailedCustomerDto>(src.Customer)
}
}
}
}
I hope to help somebody.