I want a entity that filters the list entity within the entity.
I have entities with common structure like below
public class FoodMenuT
{
public int Id {get;set;}
public int LanguageId {get;set;}
public string Name {get;set;}
}
public class FoodMenu
{
public int Id {get;set;}
public IEnumerable<FoodMenuT> FoodMenuTList {get;set;}
}
public static Expression<Func<FoodMenu, IEnumerable<FoodMenuT>>> LanguageFilter()
{
return e => e.FoodMenuT.Where(x => x.LanguageId == 1);
}
What I want to do is to do this in a generic way.
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>()
{
}
Thank you in advance for your helps.
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>()
{
var headerInfo = ServiceTool.ServiceProvider.GetService<IHeaderInfo>();
var entity = typeof(T);
var ttEntity = typeof(TT);
var propertyInfo = entity.GetProperty(ttEntity.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
var arg = Expression.Parameter(entity, "e");
var property = Expression.Property(arg, propertyInfo!.Name);
//e=>e.FoodMenuT
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
var propertyInfo2 = ttEntity.GetProperty("LanguageId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
var arg2 = Expression.Parameter(ttEntity, "x");
var property2 = Expression.Property(arg2, propertyInfo2!.Name);
var languageId = Expression.Constant(headerInfo!.LanguageId);
var equalLanguage = Expression.Equal(property2, languageId);
//x=>x.LanguageId==1
var selector2 = Expression.Lambda<Func<TT, bool>>(equalLanguage, arg2);
I couldn't combine "selector" and "selector2" with "where".
var lambda = Expression.Lambda<Func<T, IEnumerable<TT>>>(null, arg);
return lambda;
}
I solved the problem with my own means
public interface ITEntity<TT>
{
public List<TT> Translate { get; set; }
}
public interface ILEntity
{
public int LanguageId { get; set; }
}
public class FoodMenu : ITEntity<FoodMenuT>
{
public virtual List<FoodMenuT> Translate { get; set; } = new();
}
public class FoodMenuT : ILEntity
{
public int LanguageId { get; set; }
}
public static Expression<Func<T, IEnumerable<TT>>> LanguageFilter<T, TT>() where T : ITEntity<TT> where TT : ILEntity
{
return e => e.Translate.Where(x => x.LanguageId == 1);
}
Related
I have the following classes:
public class Person
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("profession")]
public Profession Profession { get; set; }
[JsonProperty("hobbies")]
public List<string> Hobbies { get; set; }
}
public class Profession
{
[JsonProperty("name")]
public string ProfessionName { get; set; }
[JsonProperty("industry")]
public string Industry { get; set; }
[JsonProperty("salary")]
public string AverageSalary { get; set; }
[JsonProperty("activities")]
public List<WorkActivity> WorkActivities { get; set; }
}
public class WorkActivity
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("rooms")]
public List<string> Rooms { get; set; }
}
public class PropertiesVisitor : ExpressionVisitor
{
private readonly Expression param;
public List<string> Names { get; } = new List<string>();
public PropertiesVisitor(Expression parameter)
{
param = parameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == param)
{
Names.Add(node.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
}
return base.VisitMember(node);
}
}
I call the methods in my app like this:
private static List<string> FindPropertyNames<T>(Expression<Func<T, object>> e)
{
var visitor = new PropertiesVisitor(e.Parameters[0]);
visitor.Visit(e);
return visitor.Names;
}
public static void Main(string[] args)
{
var names = FindPropertyNames<Person>(x => new { x.Age, x.Country, x.Profession.AverageSalary, x.Hobbies });
Console.WriteLine(string.Join(" ", names));
}
It works fine, except for one minor detailed - it doesn't check for nested properties. The output is the following: age country profession hobbies. I want it to be age country profession.salary hobbies.
I've been trying to fix the issue using a different approach, but I am unable to do so fully. I've tried the following:
public static MemberExpression GetMemberExpression(Expression e)
{
if (e is MemberExpression)
{
return (MemberExpression)e;
}
else if (e is LambdaExpression)
{
var le = e as LambdaExpression;
if (le.Body is MemberExpression)
{
return (MemberExpression)le.Body;
}
else if (le.Body is UnaryExpression)
{
return (MemberExpression)((UnaryExpression)le.Body).Operand;
}
}
return null;
}
public static string GetPropertyPath<T>(Expression<Func<T, object>> expr)
{
var path = new StringBuilder();
MemberExpression me = GetMemberExpression(expr);
do
{
if (path.Length > 0)
{
path.Insert(0, ".");
}
path.Insert(0, me.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
me = GetMemberExpression(me.Expression);
}
while (me != null);
return path.ToString();
}
It kind of does the job - but it can't take more than one property.
I call it like this:
var x = GetPropertyPath<Person>(p => p.Profession.AverageSalary);
Console.WriteLine(x);
I want to be able to send multiple properties, as in the first version. Also, I am unsure how to pass the following person.Profession.WorkActivities.Rooms as a parameter, because it is a list. I want to get profession.activities.rooms as output for it.
I would write a method that takes an Expression and returns all the members in the chain:
public static IEnumerable<MemberExpression> MemberClauses(this Expression expr) {
if (expr is not MemberExpression mexpr) {
yield break;
}
foreach (var item in MemberClauses(mexpr.Expression)) {
yield return item;
}
yield return mexpr;
}
Then, you can unwrap an entire method chain and LINQ to get the JSON property names:
public class PropertiesVisitor : ExpressionVisitor {
private readonly Expression param;
public List<string> Names { get; } = new List<string>();
public PropertiesVisitor(Expression parameter) => param = parameter;
[return: NotNullIfNotNull("node")]
public override Expression Visit(Expression node) {
var chain = node.MemberClauses().ToList();
if (chain.Any() && chain.First().Expression == param) {
var name = string.Join(".", chain.Select(
mexpr => mexpr.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName
));
Names.Add(name);
return node;
} else {
return base.Visit(node);
}
}
}
and you can then get the property path as follows:
public static class Functions {
public static List<string> GetPropertyPath<T>(Expression<Func<Person, T>> expr) {
var visitor = new PropertiesVisitor(expr.Parameters[0]);
visitor.Visit(expr);
return visitor.Names;
}
}
var names = Functions.GetPropertyPath(p => new { p.Age, p.Country, p.Profession.AverageSalary, p.Hobbies });
foreach (var name in names) {
Console.WriteLine(name);
}
This question already has answers here:
Recursively Get Properties & Child Properties Of A Class
(5 answers)
Closed 2 years ago.
I am trying to write an universal search to use for all objects.
I have this code, which is working fine to search in just one object's properties, but I would also like to search also in properties in related objects.
Eg. I have these Models/Objects
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address{ get; set; }
public ICollection<Contract> Contracts { get; set; }
}
public class Contract
{
public int Id { get; set; }
public DateTime From{ get; set; }
public DateTime To{ get; set; }
public string Comment{ get; set; }
public int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
and I want to search if any of properties contains some a string eg. "Peter", I will call it this way:
string searchString = "Peter";
var customers = db.Customers
.Include(x => x.Contracts)
.WhereAnyPropertiesOfSimilarTypeContains(searchString);
this code will check if any properties of 'Customer' contains string "Peter".
But I would also need to check if the related model 'Contract' contains "Peter.
public static class EntityHelper
{
public static IQueryable<TEntity> WhereAnyPropertiesOfSimilarTypeContains<TEntity, TProperty>(this IQueryable<TEntity> query, TProperty value)
{
var param = Expression.Parameter(typeof(TEntity));
var predicate = PredicateBuilder.False<TEntity>(); //--- True to equal
var entityFields = GetEntityFieldsToCompareTo<TEntity, TProperty>();
foreach (var fieldName in entityFields)
{
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var predicateToAdd = Expression.Lambda<Func<TEntity, bool>>(
Expression.Call(
Expression.PropertyOrField(param, fieldName), method,
Expression.Constant(value)), param);
predicate = predicate.Or(predicateToAdd); //--- And to equal
}
return query.Where(predicate);
}
// TODO: You'll need to find out what fields are actually ones you would want to compare on.
// This might involve stripping out properties marked with [NotMapped] attributes, for
// for example.
public static IEnumerable<string> GetEntityFieldsToCompareTo<TEntity, TProperty>()
{
Type entityType = typeof(TEntity);
Type propertyType = typeof(TProperty);
var fields = entityType.GetFields()
.Where(f => f.FieldType == propertyType)
.Select(f => f.Name);
var properties = entityType.GetProperties()
.Where(p => p.PropertyType == propertyType)
.Select(p => p.Name);
return fields.Concat(properties);
}
}
Thanks.
After reread the question. I don't know what are you trying, but here I put the idea I have what are you looking for.
public class Customer : AbstractEntity
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public ICollection<Contract> Contracts { get; set; }
}
public class Contract : AbstractEntity
{
//what property here can be string "Peter"? Comments?
//what are you trying?
public int Id { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Comment { get; set; }
public int CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
public abstract class AbstractEntity
{
//this method can be used to preselect properties you want
protected virtual Tuple<bool, ICollection<PropertyInfo>> PropertyCollector()
{
return new Tuple<bool, ICollection<PropertyInfo>>(false, null);
}
public IEnumerable<Tuple<Type, object>> GetRowValues()
{
foreach (var prop in GetRows())
{
yield return new Tuple<Type, object>(prop.PropertyType, prop.GetValue(this));
}
}
public ICollection<PropertyInfo> GetRows()
{
var tuple = PropertyCollector();
ISet<PropertyInfo> pInfo;
if (tuple.Item1)
{
pInfo = new HashSet<PropertyInfo>(tuple.Item2);
}
else //search all non virtual, private, protected properties, "following POCO scheme"
{
pInfo = new HashSet<PropertyInfo>();
foreach (var prop in GetType().GetProperties())
{
foreach (var access in prop.GetAccessors())
{
if ((!access.IsVirtual && !access.IsPrivate) && (prop.CanWrite && prop.CanRead))
{
pInfo.Add(prop);
}
}
}
}
return pInfo;
}
}
public static class Searchs
{
public static ICollection<object> ObjectsWithStringFound(ICollection<Customer> customers, string toBeFound)
{
var objs = new List<object>();
foreach (var cust in customers)
{
var strings = cust.GetRowValues().Where(tpl => tpl.Item1 == typeof(string)).Select(tpl => tpl.Item2);
var contracts = cust.GetRowValues().Where(tpl => tpl.Item2 is IEnumerable<Contract>).Select(tpl => tpl.Item2);
if (strings.Any(str => str == toBeFound))
{
objs.Add(cust);
}
else if (contracts.Any(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound))
{ //What I suppose I must "match" with "Peter"??!?!
objs.Add(contracts.First(ctr => ((IEnumerable<Contract>)ctr).!!!!!!!!! == toBeFound));
}
}
return objs;
}
}
I think we aren't understanding each other.
I have the two expression trees : one to get a navigation property of a class and another to filter on the values from this navigation property :
I am trying to combine them .
class Program
{
public interface IDeletable
{
bool IsDeleted { get; set; }
}
public class User
{
public int Id { get; set; }
public IEnumerable<BlogPost> BlogPosts;
}
public class BlogPost : IDeletable
{
public string Text { get; set; }
public bool IsDeleted { get; set; }
}
static void Main(string[] args)
{
var user = new User()
{
Id = 1,
BlogPosts = new List<BlogPost> {
new BlogPost {IsDeleted=false,Text="hello" },
new BlogPost {IsDeleted=true,Text="this is deleted" }
}
};
Expression<Func<User, IEnumerable<BlogPost>>> notDeletedExpression = Combine<User, BlogPost>(x => x.BlogPosts, x => !x.IsDeleted);
Console.ReadLine();
}
public static Expression<Func<T, IEnumerable<TChild>>> Combine<T, TChild>
(
Expression<Func<T, IEnumerable<TChild>>> navigationProperty,
Expression<Func<TChild, bool>> filter
)
where T : class
where TChild : class, IDeletable
{
//TODO
// shourld return x=>x.Posts.Where(p=>IsDeleted==false) ;
return null;
}
}
In the sample below the two Expressions from your sample are combined using Enumerable.Where method:
public static Expression<Func<T, IEnumerable<TChild>>> Combine<T, TChild>
(
Expression<Func<T, IEnumerable<TChild>>> navigationProperty,
Expression<Func<TChild, bool>> filter
)
where T : class
where TChild : class, IDeletable
{
// Consider caching the MethodInfo object:
var whereMethodInfo = GetEnumerableWhereMethodInfo<TChild>();
// Produce an Expression tree like:
// Enumerable.Where(<navigationProperty>, <filter>)
var filterExpr = Expression
.Call(
whereMethodInfo,
navigationProperty.Body,
filter
);
// Create a Lambda Expression with the parameters
// used for `navigationProperty` expression
return Expression
.Lambda<Func<T, IEnumerable<TChild>>>(
filterExpr,
navigationProperty.Parameters
);
}
private static MethodInfo GetEnumerableWhereMethodInfo<TSource>()
{
// Get a MethodInfo definition for `Enumerable.Where<>`:
var methodInfoDefinition = typeof(Enumerable)
.GetMethods()
.Where(x => x.Name == nameof(Enumerable.Where))
.First(x =>
{
var parameters = x.GetParameters();
return
parameters.Length == 2 &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>);
});
// Get a MethodInfo object for `Enumerable.Where<TSource>`:
var methodInfo = methodInfoDefinition.MakeGenericMethod(typeof(TSource));
return methodInfo;
}
I'm trying to use AutoMapper to map classes like this:
class FooDTO
{
public int X { get; set; }
public EmbeddedDTO Embedded { get; set; }
public class EmbeddedDTO
{
public BarDTO Y { get; set; }
public BazDTO Z { get; set; }
}
}
To classes like this:
class Foo
{
public int X { get; set; }
public Bar Y { get; set; }
public Baz Z { get; set; }
}
(FooDTO is a HAL resource)
I know I can do it by creating the map explicitly like this:
Mapper.CreateMap<FooDTO, Foo>()
.ForMember(f => f.Y, c => c.MapFrom(f => f.Embedded.Y))
.ForMember(f => f.Z, c => c.MapFrom(f => f.Embedded.Z));
Or even with a trick like this:
Mapper.CreateMap<FooDTO, Foo>()
.AfterMap((source, dest) => Mapper.Map(source.Embedded, dest));
But the problem is that I will have many similar HAL resources to map, and I'd rather not have to configure each one separately. I actually have a generic object model that looks like this:
class HalResource
{
[JsonProperty("_links")]
public IDictionary<string, HalLink> Links { get; set; }
}
class HalResource<TEmbedded> : HalResource
{
[JsonProperty("_embedded")]
public TEmbedded Embedded { get; set; }
}
class HalLink
{
[JsonProperty("href")]
public string Href { get; set; }
}
With this model, the FooDTO class is actually declared like this
class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
public int X { get; set; }
public class EmbeddedDTO
{
public int Y { get; set; }
public int Z { get; set; }
}
}
Is there a way to configure the mapping globally for all classes that inherit HalResource<TEmbedded>, so that the properties of the DTO's Embedded property are mapped directly to the target object? I tried to do it with a custom IObjectMapper, but it proved more challenging than I expected...
If your use case is as limited as presented in the question, that is:
A one-way mapping from HalResource derived instances to straight POCOS (vs bidirectional mapping)
Mapping of properties of the same name and type
The exact embedded structure you presented here
than it may make sense to setup a specific mapping yourself that takes into account this structure. This is something I tend to do if I have a very narrowly defined need for mapping with some clear mapping conventions (instead of relying on a generic mapper such as AutoMapper). For this purpose I have some building blocks that I tend to reuse in different contexts. I whipped together a mapper that applies to the problem you described from these building blocks, as shown below:
public class Mapper
{
private const BindingFlags DestConstructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private const BindingFlags DestFlags = BindingFlags.Instance | BindingFlags.Public;
private const BindingFlags SrcFlags = BindingFlags.Instance | BindingFlags.Public;
private static readonly object[] NoArgs = new object[0];
private static readonly Type GenericEmbeddedSourceType = typeof(HalResource<>);
private readonly Dictionary<Type, Func<object, object>> _oneWayMap = new Dictionary<Type, Func<object, object>>();
public void CreateMap<TDestination, TSource>()
where TDestination : class
where TSource : HalResource
{
CreateMap(typeof(TDestination), typeof(TSource));
}
public void CreateMap(Type destType, Type srcType)
{
_oneWayMap[srcType] = InternalCreateMapper(destType, srcType);
}
public object Map<TSource>(TSource toMap) where TSource : HalResource
{
var mapper = default(Func<object, object>);
if (!_oneWayMap.TryGetValue(typeof(TSource), out mapper))
throw new KeyNotFoundException(string.Format("No mapping for {0} is defined.", typeof(TSource)));
return mapper(toMap);
}
public TDestination Map<TDestination, TSource>(TSource toMap)
where TDestination : class
where TSource : HalResource
{
var converted = Map(toMap);
if (converted != null && !typeof(TDestination).IsAssignableFrom(converted.GetType()))
throw new InvalidOperationException(string.Format("No mapping from type {0} to type {1} has been configured.", typeof(TSource), typeof(TDestination)));
return (TDestination)converted;
}
public void Clear()
{
_oneWayMap.Clear();
}
private static Func<object, object> InternalCreateMapper(Type destType, Type srcType)
{
// Destination specific constructor + setter map.
var destConstructor = BuildConstructor(destType.GetConstructor(DestConstructorFlags, null, Type.EmptyTypes, null));
var destSetters = destType
.GetProperties(DestFlags)
.Where(p => p.CanWrite)
.ToDictionary(k => k.Name, v => Tuple.Create(v.PropertyType, BuildSetter(v)));
// Source specific getter maps
var srcPrimPropGetters = CreateGetters(srcType);
var srcEmbeddedGetter = default(Func<object, object>);
var srcEmbeddedPropGetters = default(IDictionary<string, Tuple<Type, Func<object, object>>>);
var baseType = srcType.BaseType;
while (baseType != null && baseType != typeof(object))
{
if (baseType.IsGenericType && GenericEmbeddedSourceType.IsAssignableFrom(baseType.GetGenericTypeDefinition()))
{
var genericParamType = baseType.GetGenericArguments()[0];
if (srcPrimPropGetters.Any(g => g.Value.Item1.Equals(genericParamType)))
{
var entry = srcPrimPropGetters.First(g => g.Value.Item1.Equals(genericParamType));
srcPrimPropGetters.Remove(entry.Key);
srcEmbeddedGetter = entry.Value.Item2;
srcEmbeddedPropGetters = CreateGetters(entry.Value.Item1);
break;
}
}
baseType = baseType.BaseType;
}
// Build mapper delegate function.
return (src) =>
{
var result = destConstructor(NoArgs);
var srcEmbedded = srcEmbeddedGetter != null ? srcEmbeddedGetter(src) : null;
foreach (var setter in destSetters)
{
var getter = default(Tuple<Type, Func<object, object>>);
if (srcPrimPropGetters.TryGetValue(setter.Key, out getter) && setter.Value.Item1.IsAssignableFrom(getter.Item1))
setter.Value.Item2(result, getter.Item2(src));
else if (srcEmbeddedPropGetters.TryGetValue(setter.Key, out getter) && setter.Value.Item1.IsAssignableFrom(getter.Item1))
setter.Value.Item2(result, getter.Item2(srcEmbedded));
}
return result;
};
}
private static IDictionary<string, Tuple<Type, Func<object, object>>> CreateGetters(Type srcType)
{
return srcType
.GetProperties(SrcFlags)
.Where(p => p.CanRead)
.ToDictionary(k => k.Name, v => Tuple.Create(v.PropertyType, BuildGetter(v)));
}
private static Func<object[], object> BuildConstructor(ConstructorInfo constructorInfo)
{
var param = Expression.Parameter(typeof(object[]), "args");
var argsExp = constructorInfo.GetParameters()
.Select((p, i) => Expression.Convert(Expression.ArrayIndex(param, Expression.Constant(i)), p.ParameterType))
.ToArray();
return Expression.Lambda<Func<object[], object>>(Expression.New(constructorInfo, argsExp), param).Compile();
}
private static Func<object, object> BuildGetter(PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(typeof(object), "instance");
var instanceCast = propertyInfo.DeclaringType.IsValueType
? Expression.Convert(instance, propertyInfo.DeclaringType)
: Expression.TypeAs(instance, propertyInfo.DeclaringType);
var propertyCast = Expression.TypeAs(Expression.Property(instanceCast, propertyInfo), typeof(object));
return Expression.Lambda<Func<object, object>>(propertyCast, instance).Compile();
}
private static Action<object, object> BuildSetter(PropertyInfo propertyInfo)
{
var setMethodInfo = propertyInfo.GetSetMethod(true);
var instance = Expression.Parameter(typeof(object), "instance");
var value = Expression.Parameter(typeof(object), "value");
var instanceCast = propertyInfo.DeclaringType.IsValueType
? Expression.Convert(instance, propertyInfo.DeclaringType)
: Expression.TypeAs(instance, propertyInfo.DeclaringType);
var call = Expression.Call(instanceCast, setMethodInfo, Expression.Convert(value, propertyInfo.PropertyType));
return Expression.Lambda<Action<object, object>>(call, instance, value).Compile();
}
}
Some optimizations can be performed, but performance is likely sufficient for most problems. This can then be used like:
public abstract class HalResource
{
public IDictionary<string, HalLink> Links { get; set; }
}
public abstract class HalResource<TEmbedded> : HalResource
{
public TEmbedded Embedded { get; set; }
}
public class HalLink
{
public string Href { get; set; }
}
public class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
public int X { get; set; }
public class EmbeddedDTO
{
public int Y { get; set; }
public int Z { get; set; }
}
}
public class MyMappedFoo
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
}
class Program
{
public static void Main(params string[] args)
{
// Configure mapper manually
var mapper = new Mapper();
mapper.CreateMap<MyMappedFoo, FooDTO>();
var myDTO = new FooDTO
{
X = 10,
Embedded = new FooDTO.EmbeddedDTO { Y = 5, Z = 9 }
};
var mappedFoo = mapper.Map<MyMappedFoo, FooDTO>(myDTO);
Console.WriteLine("X = {0}, Y = {1}, Z = {2}", mappedFoo.X, mappedFoo.Y, mappedFoo.Z);
Console.WriteLine("Done");
Console.ReadLine();
}
}
If your source and destination types can be discovered by convention, you can go a step further and have a builder that encodes these conventions populate the map as in the example below (again not the most optimal implementation, but there to illustrate the point):
public static class ByConventionMapBuilder
{
public static Func<IEnumerable<Type>> DestinationTypesProvider = DefaultDestTypesProvider;
public static Func<IEnumerable<Type>> SourceTypesProvider = DefaultSourceTypesProvider;
public static Func<Type, Type, bool> TypeMatcher = DefaultTypeMatcher;
public static Mapper Build()
{
var mapper = new Mapper();
var sourceTypes = SourceTypesProvider().ToList();
var destTypes = DestinationTypesProvider();
foreach (var destCandidateType in destTypes)
{
var match = sourceTypes.FirstOrDefault(t => TypeMatcher(t, destCandidateType));
if (match != null)
{
mapper.CreateMap(destCandidateType, match);
sourceTypes.Remove(match);
}
}
return mapper;
}
public static IEnumerable<Type> TypesFromAssembliesWhere(Func<IEnumerable<Assembly>> assembliesProvider, Predicate<Type> matches)
{
foreach (var a in assembliesProvider())
{
foreach (var t in a.GetTypes())
{
if (matches(t))
yield return t;
}
}
}
private static IEnumerable<Type> DefaultDestTypesProvider()
{
return TypesFromAssembliesWhere(
() => new[] { Assembly.GetExecutingAssembly() },
t => t.IsClass && !t.IsAbstract && !t.Name.EndsWith("DTO"));
}
private static IEnumerable<Type> DefaultSourceTypesProvider()
{
return TypesFromAssembliesWhere(
() => new[] { Assembly.GetExecutingAssembly() },
t => typeof(HalResource).IsAssignableFrom(t) && !t.IsAbstract && t.Name.EndsWith("DTO"));
}
private static bool DefaultTypeMatcher(Type srcType, Type destType)
{
var stn = srcType.Name;
return (stn.Length > 3 && stn.EndsWith("DTO") && destType.Name.EndsWith(stn.Substring(0, stn.Length - 3)));
}
}
class Program
{
public static void Main(params string[] args)
{
// Configure mapper by type scanning & convention matching
var mapper = ByConventionMapBuilder.Build();
var myDTO = new FooDTO
{
X = 10,
Embedded = new FooDTO.EmbeddedDTO { Y = 5, Z = 9 }
};
var mappedFoo = mapper.Map<MyMappedFoo, FooDTO>(myDTO);
Console.WriteLine("X = {0}, Y = {1}, Z = {2}", mappedFoo.X, mappedFoo.Y, mappedFoo.Z);
Console.WriteLine("Done");
Console.ReadLine();
}
}
If you have other reasons to want to hang on to AutoMapper, I suggest creating a similar map builder that encodes both the type matching and the embedded property mapping.
I have two dummy classes named TClass1 and TClass2. I would like to know how to build an expression tree to call the operation TClass1.TestMethod. I specifically have problem using Expression.Call method to create an expression based on instance methods of a class. Any help would be appreciated.
public class TClass1
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
public TClass2 TestMethod(TClass2 tc2, int c)
{
return new TClass2() { Cprop1 = "The result: " + this.Prop1 + ".", Cprop2 = this.Prop2 * c };
}
}
public class TClass2
{
public string Cprop1 { get; set; }
public int Cprop2 { get; set; }
}
Try this code:
var par1 = Expression.Parameter(typeof(TClass2), "tc2");
var par2 = Expression.Parameter(typeof(int), "c");
var instance = Expression.Parameter(typeof(TClass1), "inst");
var inExp = Expression.Call(instance,typeof(TClass1).GetMethod("TestMethod"),par1,par2);
var exp = Expression.Lambda<Func<TClass1,TClass2,int,TClass2>>(inExp,instance,
par1,par2);
var deleg = exp.Compile();