Automapper, how to make this custom List<KeyValuePair> mapping generic? - c#

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
}
}

Related

Reflection: Read List-type properties of an object, containing another object

I am trying to serialize nested objects using reflection. I am able to do this fine for properties containing a single value, but I am having trouble with list type properties that contain another class.
In the code sample below I have a class Dish which contains a list of of Recipe classes as a property, which itself contains a list of Step classes.
I am able to get the PropertyInfo of the List property, but when I try to get the contents of it by invoking the get method, I get a simple object back, not a List of e.g. Steps:
var listObjects = property.GetGetMethod().Invoke(dish, null);
I managed to cast that into a List of objects like this:
List<object> listValues = ( listObjects as IEnumerable<object>).Cast<object>().ToList();
Now at least I can iterate over this List, but I cannot get the acutal properties of the original classes like the step description.
So I know the type of the List via property.PropertyType.GenericTypeArguments.First(), but its at runtime. I am thinking on how to perform a proper cast to transform my List<object> into a conrete type like List<Step>.
What I want to achieve: Serialize all property values of dish and all its attached Lists of objects.
I appreciate any ideas.
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var dish = new Dish(){
Recipes = new List<Recipe>(){
new Recipe(){
RecipeName = "Preparation",
Steps = new List<Step>(){
new Step(){
Description = "Prepare Stuff",
}
}
},
new Recipe(){
RecipeName = "Main Course",
Steps = new List<Step>(){
new Step(){
Description = "Do this",
},
new Step(){
Description = "Then do that",
}
}
}, }, };
var serializer = new Serializer();
serializer.SerializeDish(dish);
}
}
public class Serializer
{
public void SerializeDish(Dish dish)
{
var dishType = typeof (Dish);
var listProps = dishType.GetProperties().Where(x => (x.PropertyType.IsGenericType && x.PropertyType.GetGenericTypeDefinition() == typeof (List<>)));
foreach (var property in listProps)
{
var propertyGetMethod = property.GetGetMethod();
var listObjects = propertyGetMethod.Invoke(dish, null);
Console.WriteLine("Name:"+property.Name + " " + "List-Type:"+property.PropertyType.GenericTypeArguments.First());
//Here its getting fuzzy
List<object> listValues = ( listObjects as IEnumerable<object>).Cast<object>().ToList();
foreach ( var item in listValues ) {
Console.WriteLine(item);
}
}
}
}
public class Dish
{
public List<Recipe> Recipes {get;set;}
}
public class Recipe
{
public string RecipeName{get;set;}
public List<Step> Steps {get;set;}
}
public class Step
{
public string Description {get;set;}
}

how to generic Automapper Config

I use Automapper version 4.2.0 and I all Action Methods I have setting Like this :
var attributeGroups = _attributeGroupService.AttributeGroupDropdown();
var mapconfiguration = new MapperConfiguration(cfg => cfg.CreateMap<AttributeGroup, AttributeGroupViewModel>());
var mapper = mapconfiguration.CreateMapper();
var result = mapper.Map(attributeGroups, new List<AttributeGroupViewModel>());
I dont repeat this codes in all actions , I want create a generic method and just pass data to it and map it ,to do this , I create a Method like this ?
public N mapping<T,K,M,N>(T resource, K destination,M model,N newModel)
{
var mapconfiguration = new MapperConfiguration(cfg => cfg.CreateMap<T, K>());
var mapper = mapconfiguration.CreateMapper();
var result = mapper.Map<M,N>(model, newModel);
return result;
}
and call it like this :
var ResultTest=mapping<AttributeGroup,AttributeGroupViewModel,attributeGroups,new List<AttributeGroupViewModel>()>();
but it doesnt complie .
how can I do it?
Short answer: you need to provide types in the generic parameter list and variables in the argument list of your function call. Also, you don't need the T resource, K destination arguments at all, since they are unused.
There is another problem with your approach, since using it is not really convenient. So I suggest that you provide two specialized methods with reduced complexity.
See the below complete example. It contains the method mapAnything<...>, which is a working equivalent to your mapping<...> method. The methods mapObject<...> and mapCollection<...> are the specialized methods I was talking about.
class TestResource
{
public int Testnumber { get; set; }
public string Testtext { get; set; }
}
class TestDestination
{
public string Testtext { get; set; }
}
class Program
{
// equivalent to what you tried to do - needs explicit generic parameters on call
static N mapAnything<T, K, M, N>(M model, N newModel)
{
var mapconfiguration = new MapperConfiguration(cfg => cfg.CreateMap<T, K>());
var mapper = mapconfiguration.CreateMapper();
var result = mapper.Map<M, N>(model, newModel);
return result;
}
// variant for object mapping, where generics can be implicitely inferred
static N mapObject<M, N>(M model, N newModel)
{
var mapconfiguration = new MapperConfiguration(cfg => cfg.CreateMap<M, N>());
var mapper = mapconfiguration.CreateMapper();
var result = mapper.Map<M, N>(model, newModel);
return result;
}
// variant for lists, where generics can be implicitely inferred
static ICollection<N> mapCollection<M, N>(IEnumerable<M> model, ICollection<N> newModel)
{
var mapconfiguration = new MapperConfiguration(cfg => cfg.CreateMap<M, N>());
var mapper = mapconfiguration.CreateMapper();
var result = mapper.Map<IEnumerable<M>, ICollection<N>>(model, newModel);
return result;
}
static void Main(string[] args)
{
var res1 = new TestResource() { Testnumber = 1, Testtext = "a" };
var res2 = new List<TestResource>();
for (int i = 0; i < 10; i++)
{
res2.Add(new TestResource() { Testnumber = i, Testtext = "test: " + i });
}
var mapped1 = mapObject(res1, new TestDestination());
var mapped2 = mapCollection(res2, new HashSet<TestDestination>());
var mapped3 = mapAnything<TestResource, TestDestination, TestResource, TestDestination>(res1, new TestDestination());
var mapped4 = mapAnything<TestResource, TestDestination, IEnumerable<TestResource>, List<TestDestination>>(res2, new List<TestDestination>());
}
}

Is it possible to map to null when a property on source is false?

I'm defining a mapping between a source class and a target class:
Mapper.CreateMap<Source, Target>();
I want the mapping to return null if a certain property on the Source is set to a particular value:
// If Source.IsValid = false I want the mapping to return null
Source s = new Source { IsValid = false };
Target t = Mapper.Map<Target>(s);
Assert.IsNull(t);
How do I configure AutoMapper to achieve that?
You can define your mapping like this:
Mapper.CreateMap<Source, Target>().TypeMap
.SetCondition(r => ((Source)r.SourceValue).IsValid);
EDIT
If you want your map to output a null object I'd recommend using two levels of mapping: the first level decides whether or not to return a null object by taking over mapping behavior with the ConvertUsing method, and if mapping is required it defers it to an inner mapping engine:
static void Main(string[] args)
{
var innerConfigurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
innerConfigurationStore.CreateMap<From, To>()
.ForMember(to => to.DestinationName, opt => opt.MapFrom(from => from.Active ? from.Name : (string)null));
var innerMappingEngine = new MappingEngine(innerConfigurationStore);
Mapper.CreateMap<From, To>()
.ConvertUsing(from => from.Active ? innerMappingEngine.Map<To>(from) : null)
;
WriteMappedFrom(new From() {Active = true, Name = "ActiveFrom"});
WriteMappedFrom(new From() {Active = false, Name = "InactiveFrom"});
}
static void WriteMappedFrom(From from)
{
Console.WriteLine("Mapping from " + (from.Active ? "active" : "inactive") + " " + from.Name);
var to = Mapper.Map<To>(from);
Console.WriteLine("To -> " + (to == null ? "null" : "not null"));
}
INITIAL ANSWER
It's quite simple to use a conditional mapping in Automapper, here is some sample code
public class From
{
public bool Active { get; set; }
public string Name { get; set; }
}
public class To
{
public string DestinationName { get; set; }
}
static void Main(string[] args)
{
Mapper.CreateMap<From, To>()
.ForMember(to => to.DestinationName, opt => opt.MapFrom(from => from.Active ? from.Name : (string)null));
WriteMappedFrom(new From() {Active = true, Name = "ActiveFrom"});
WriteMappedFrom(new From() {Active = false, Name = "InactiveFrom"});
}
static void WriteMappedFrom(From from)
{
Console.WriteLine("Mapping from " + (from.Active ? "active" : "inactive") + " " + from.Name);
var to = Mapper.Map<To>(from);
Console.WriteLine("To -> " + (to.DestinationName ?? "null"));
}
There are other conditional mappings in automapper but they seem to work only on convention-based mappings. You could however look into the documentation a bit more to confirm

how to return value in self defined type in neo4jclient

I'm new to neo4jclient and i don't know how to return self defined type in neo4jclient.
i have the following cypher:
var result = client.Cypher
.Match("(u:User)-[:" + FriendRelation + "]->(friend:User)")
.Return((user, friend) => new RelationInDB(user.As<User>().id, friend.As<User>().id)).Results;
i want to return all id pair that have friend relation and i want to store two id in self defined class--RelationInDB, but i don't know how to write Return(i know the return above is wrong)
Can anyone help?
OK, there are 2 things you'll find which are causing you issues:
You can't return into the constructor of a class, you need to use a default constructor and property setting
Your return statement has (user, friend) => but your match statement has (u:User). Your return statement should be: (u, friend) =>
To that end:
var results = client
.Cypher
.Match("(u:User)-[:" + FriendRelation + "]->(friend:User)")
.Return((u, friend) => new RelationInDB{UserId = u.As<User>().Id, FriendId = friend.As<User>().Id}).Results;
My RelationInDB class is defined as:
public class RelationInDB
{
public int FriendId { get; set; }
public int UserId { get; set; }
public RelationInDB() { }
}
If you can't change your RelationInDB class and you need to use it as is, you will have to use an anonymous type for your return:
var results = client
.Cypher
.Match("(u:User)-[:" + FriendRelation + "]->(friend:User)")
.Return((u, friend) => new {UserId = u.As<User>().Id, FriendId = friend.As<User>().Id}).Results;
and parse afterwards with something like:
var relsInDb = results.Select(result => new RelationInDB(result.UserId, result.FriendId));

Select template

Is it possible to make a template for SELECT in a LINQ query? Right now I have 6 methods that uses the exact same SELECT, i would like to use a template if possible.
This is the code I'm using, when I want to make a change to the select I have to change the same thing at so many places in my code.
result = query.Select(b => new
{
route_id = b.b.route_id,
name = b.b.name,
description = b.b.description,
distance = b.b.distance,
distance_to_route = (int)b.distance_to_from_me,
departure_place = b.b.departure_place,
arrival_place = b.b.arrival_place,
owner = b.b.user.username,
average_rating = b.avg_rating,
is_favorite = b.is_favorite,
date = b.b.date,
attributes = b.b.route_attributes.Select(c =>
c.route_attribute_types.attribute_name),
coordinates = b.b.coordinates.Select(c =>
new coordinateToSend { sequence = c.sequence,
lat = c.position.Latitude,
lon = c.position.Longitude })
});
Here is a simple example of one way you could do this:
In your example, you're converting the source type to an anonymous type. You could create a class to represent your converted/result type, for example:
public class ResultClass
{
public string ResultPropA { get; set; }
}
For examples sake, lets say the following was the definition of your source class:
public class SourceClass
{
public string SourcePropA { get; set; }
}
Now that you have type definitions for your source and result objects, you can create an extension method to convert a collection of your source class to a collection of your result class:
public static class SourceToResultRepository
{
public static IEnumerable<ResultClass> ConvertSourceToResult
(this IEnumerable<SourceClass> source)
{
return source.Select(s => new ResultClass
{
ResultPropA = s.SourcePropA
//Add all other property transformations here
});
}
}
And here is an example of how you could use it wherever you need to perform the transformation:
//Extension usage:
var result = Database.Source.ConvertSourceToResult();
//Direct usage:
var result = SourceToResultRepository.ConvertSourceToResult(Database.Source);

Categories