diff --git a/ExpressMapper NET40/DestinationMappingService.cs b/ExpressMapper NET40/DestinationMappingService.cs index c061f0b..adc438f 100644 --- a/ExpressMapper NET40/DestinationMappingService.cs +++ b/ExpressMapper NET40/DestinationMappingService.cs @@ -168,7 +168,7 @@ public override void PrecompileCollection() var lambda = Expression.Lambda>(block, sourceVariable, destVariable); var compiledFunc = lambda.Compile(); - CollectionMappers.Add(cacheKey, compiledFunc); + CollectionMappers[cacheKey] = compiledFunc; } #endregion diff --git a/ExpressMapper NET40/ExpressMapper NET40.csproj b/ExpressMapper NET40/ExpressMapper NET40.csproj index 8208740..6bc6c43 100644 --- a/ExpressMapper NET40/ExpressMapper NET40.csproj +++ b/ExpressMapper NET40/ExpressMapper NET40.csproj @@ -71,6 +71,7 @@ + diff --git a/ExpressMapper NET40/Mapper.cs b/ExpressMapper NET40/Mapper.cs index 62b7486..f99b680 100644 --- a/ExpressMapper NET40/Mapper.cs +++ b/ExpressMapper NET40/Mapper.cs @@ -1,11 +1,9 @@ using System; -using System.Linq; namespace ExpressMapper { public static class Mapper { - private static readonly object _lock = new object(); private static IMappingServiceProvider _instance; // todo: via Internal DependencyResolver @@ -16,18 +14,12 @@ public static IMappingServiceProvider Instance public static void Compile() { - lock (_lock) - { - Instance.Compile(); - } + Instance.Compile(); } public static void PrecompileCollection() { - lock (_lock) - { - Instance.PrecompileCollection(); - } + Instance.PrecompileCollection(); } public static TN Map(T src) @@ -62,35 +54,23 @@ public static object Map(object src, object dest, Type srcType, Type dstType) public static IMemberConfiguration Register() { - lock (_lock) - { - return Instance.Register(); - } + return Instance.Register(); } public static void RegisterCustom() where TMapper : ICustomTypeMapper { - lock (_lock) - { - Instance.RegisterCustom(); - } + Instance.RegisterCustom(); } public static void RegisterCustom(Func mapFunc) { - lock (_lock) - { - Instance.RegisterCustom(mapFunc); - } + Instance.RegisterCustom(mapFunc); } public static void Reset() { - lock (_lock) - { - Instance.Reset(); - } + Instance.Reset(); } } } diff --git a/ExpressMapper NET40/MappingServiceBase.cs b/ExpressMapper NET40/MappingServiceBase.cs index df78f7b..e2881b8 100644 --- a/ExpressMapper NET40/MappingServiceBase.cs +++ b/ExpressMapper NET40/MappingServiceBase.cs @@ -403,9 +403,22 @@ internal static Expression ConvertCollection(Type destPropType, { return Expression.Call(typeof(Queryable), "AsQueryable", new[] { destType }, destColl); } - var collectionType = typeof(Collection<>).MakeGenericType(destType); - return destPropType == collectionType ? Expression.New(collectionType.GetConstructor(new Type[] { destList }), destColl) : destColl; + if (destPropType.IsInterface && destColl.Type.IsSubclassOf(destPropType)) + { + return destColl; + } + + if (destPropType.IsClass) + { + + } + + return destPropType.IsClass ? Expression.New(destPropType.GetConstructor(new Type[] { destList }), destColl) : destColl; + + //var collectionType = typeof(Collection<>).MakeGenericType(destType); + + //return destPropType == collectionType ? Expression.New(collectionType.GetConstructor(new Type[] { destList }), destColl) : destColl; } } } diff --git a/ExpressMapper NET40/MappingServiceProvider.cs b/ExpressMapper NET40/MappingServiceProvider.cs index 646ede7..15d0b54 100644 --- a/ExpressMapper NET40/MappingServiceProvider.cs +++ b/ExpressMapper NET40/MappingServiceProvider.cs @@ -8,6 +8,8 @@ namespace ExpressMapper { public sealed class MappingServiceProvider : IMappingServiceProvider { + private static readonly object _lock = new object(); + public Dictionary> CustomMappers { get; set; } private readonly Dictionary> _customTypeMapperCache = new Dictionary>(); private readonly List _nonGenericCollectionMappingCache = new List(); @@ -54,89 +56,115 @@ public IQueryable Project(IQueryable source) public IMemberConfiguration Register() { - var src = typeof(T); - var dest = typeof(TN); - var cacheKey = CalculateCacheKey(src, dest); + lock (_lock) + { + var src = typeof (T); + var dest = typeof (TN); + var cacheKey = CalculateCacheKey(src, dest); - if (SourceService.TypeMappers.ContainsKey(cacheKey) && DestinationService.TypeMappers.ContainsKey(cacheKey)) - { - throw new InvalidOperationException(string.Format("Mapping from {0} to {1} is already registered", src.FullName, dest.FullName)); - } + if (SourceService.TypeMappers.ContainsKey(cacheKey) && + DestinationService.TypeMappers.ContainsKey(cacheKey)) + { + throw new InvalidOperationException(string.Format("Mapping from {0} to {1} is already registered", + src.FullName, dest.FullName)); + } - var sourceClassMapper = new SourceTypeMapper(SourceService); - var destinationClassMapper = new DestinationTypeMapper(DestinationService); + var sourceClassMapper = new SourceTypeMapper(SourceService); + var destinationClassMapper = new DestinationTypeMapper(DestinationService); - SourceService.TypeMappers[cacheKey] = sourceClassMapper; - DestinationService.TypeMappers[cacheKey] = destinationClassMapper; - return new MemberConfiguration(new ITypeMapper[]{sourceClassMapper, destinationClassMapper}); + SourceService.TypeMappers[cacheKey] = sourceClassMapper; + DestinationService.TypeMappers[cacheKey] = destinationClassMapper; + return + new MemberConfiguration(new ITypeMapper[] {sourceClassMapper, destinationClassMapper}); + } } public void Compile() { - foreach (var mappingService in _mappingServices) + lock (_lock) { - mappingService.Compile(); + foreach (var mappingService in _mappingServices) + { + mappingService.Compile(); + } } } public void PrecompileCollection() { - foreach (var mappingService in _mappingServices) + lock (_lock) { - mappingService.PrecompileCollection(); + foreach (var mappingService in _mappingServices) + { + mappingService.PrecompileCollection(); + } } } public void Reset() { - foreach (var mappingService in _mappingServices) + lock (_lock) { - mappingService.TypeMappers.Clear(); - } + foreach (var mappingService in _mappingServices) + { + mappingService.TypeMappers.Clear(); + } - CustomMappers.Clear(); + CustomMappers.Clear(); - foreach (var mappingService in _mappingServices) - { - mappingService.Reset(); + foreach (var mappingService in _mappingServices) + { + mappingService.Reset(); + } } } public void RegisterCustom(Func mapFunc) { - var src = typeof(T); - var dest = typeof(TN); - var cacheKey = CalculateCacheKey(src, dest); - - if (CustomMappers.ContainsKey(cacheKey)) + lock (_lock) { - throw new InvalidOperationException(string.Format("Mapping from {0} to {1} is already registered", src.FullName, dest.FullName)); - } + var src = typeof (T); + var dest = typeof (TN); + var cacheKey = CalculateCacheKey(src, dest); - var delegateMapperType = typeof(DelegateCustomTypeMapper<,>).MakeGenericType(src, dest); - var newExpression = Expression.New(delegateMapperType.GetConstructor(new Type[] { typeof(Func<,>).MakeGenericType(src, dest) }), Expression.Constant(mapFunc)); - var newLambda = Expression.Lambda>>(newExpression); - var compile = newLambda.Compile(); - CustomMappers.Add(cacheKey, compile); + if (CustomMappers.ContainsKey(cacheKey)) + { + throw new InvalidOperationException(string.Format("Mapping from {0} to {1} is already registered", + src.FullName, dest.FullName)); + } + + var delegateMapperType = typeof (DelegateCustomTypeMapper<,>).MakeGenericType(src, dest); + var newExpression = + Expression.New( + delegateMapperType.GetConstructor(new Type[] {typeof (Func<,>).MakeGenericType(src, dest)}), + Expression.Constant(mapFunc)); + var newLambda = Expression.Lambda>>(newExpression); + var compile = newLambda.Compile(); + CustomMappers.Add(cacheKey, compile); + } } public void RegisterCustom() where TMapper : ICustomTypeMapper { - var src = typeof(T); - var dest = typeof(TN); - var cacheKey = CalculateCacheKey(src, dest); - - if (CustomMappers.ContainsKey(cacheKey)) + lock (_lock) { - throw new InvalidOperationException(string.Format("Mapping from {0} to {1} is already registered", src.FullName, dest.FullName)); - } + var src = typeof (T); + var dest = typeof (TN); + var cacheKey = CalculateCacheKey(src, dest); - var newExpression = Expression.New(typeof(TMapper)); - var newLambda = Expression.Lambda>>(newExpression); - var compile = newLambda.Compile(); - CustomMappers.Add(cacheKey, compile); + if (CustomMappers.ContainsKey(cacheKey)) + { + throw new InvalidOperationException(string.Format("Mapping from {0} to {1} is already registered", + src.FullName, dest.FullName)); + } + + var newExpression = Expression.New(typeof (TMapper)); + var newLambda = Expression.Lambda>>(newExpression); + var compile = newLambda.Compile(); + CustomMappers[cacheKey] = compile; + } } public TN Map(T src, TN dest = default(TN)) @@ -314,7 +342,7 @@ private void CompileNonGenericCustomTypeMapper(Type srcType, Type dstType, ICust srcAssigned, dstAssigned, assignExp, assignContextExp, sourceAssignedExp, destAssignedExp, /*destinationAssignedExp,*/ resultAssignExp, resultVarExp); var lambda = Expression.Lambda>(blockExpression, sourceExpression, destinationExpression); - _customTypeMapperCache.Add(cacheKey, lambda.Compile()); + _customTypeMapperCache[cacheKey] = lambda.Compile(); } internal static Type GetCollectionElementType(Type type) diff --git a/ExpressMapper NET40/ProjectionAccessMemberVisitor.cs b/ExpressMapper NET40/ProjectionAccessMemberVisitor.cs new file mode 100644 index 0000000..283669c --- /dev/null +++ b/ExpressMapper NET40/ProjectionAccessMemberVisitor.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace ExpressMapper +{ + public class ProjectionAccessMemberVisitor : ExpressionVisitor + { + private Expression _exp; + //private ParameterExpression _src; + private Type _type; + public ProjectionAccessMemberVisitor(Type type, Expression exp) + { + _exp = exp; + _type = type; + //_src = src; + } + + //protected override Expression VisitParameter(ParameterExpression node) + //{ + // if (node.Type == _type) + // { + // return _src; + // } + // return base.VisitParameter(node); + //} + + protected override Expression VisitMember(MemberExpression node) + { + if (node.Expression.Type == _type) + { + return Expression.PropertyOrField(_exp, node.Member.Name); + } + return base.VisitMember(node); + } + } +} diff --git a/ExpressMapper NET40/SourceMappingService.cs b/ExpressMapper NET40/SourceMappingService.cs index d3d4e68..57100f8 100644 --- a/ExpressMapper NET40/SourceMappingService.cs +++ b/ExpressMapper NET40/SourceMappingService.cs @@ -43,7 +43,7 @@ public override void PrecompileCollection() var blockExp = CompileCollectionInternal(sourceParameterExp); var lambda = Expression.Lambda>(blockExp, sourceParameterExp); var compiledFunc = lambda.Compile(); - CollectionMappers.Add(cacheKey, compiledFunc); + CollectionMappers[cacheKey] = compiledFunc; } public override bool DestinationSupport diff --git a/ExpressMapper NET40/SourceTypeMapper.cs b/ExpressMapper NET40/SourceTypeMapper.cs index 5d6d833..b88f8ae 100644 --- a/ExpressMapper NET40/SourceTypeMapper.cs +++ b/ExpressMapper NET40/SourceTypeMapper.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using System.Reflection; namespace ExpressMapper { @@ -108,11 +109,33 @@ private void CreateQueryableProjection() { if (_bindingExpressions.ContainsKey(autoMember.Value.Name)) continue; + var source = autoMember.Key as PropertyInfo; + var destination = autoMember.Value as PropertyInfo; + var memberQueryableExpression = - MappingService.GetMemberQueryableExpression(autoMember.Key.DeclaringType, - autoMember.Value.DeclaringType); - var expression = memberQueryableExpression ?? - Expression.PropertyOrField(SourceParameter, autoMember.Key.Name); + MappingService.GetMemberQueryableExpression(source.PropertyType, + destination.PropertyType); + + var propertyOrField = Expression.PropertyOrField(SourceParameter, autoMember.Key.Name); + + Expression expression; + if (memberQueryableExpression != null) + { + var lambdaExpression = memberQueryableExpression as LambdaExpression; + var projectionAccessMemberVisitor = new ProjectionAccessMemberVisitor(propertyOrField.Type, propertyOrField); + var clearanceExp = projectionAccessMemberVisitor.Visit(lambdaExpression.Body); + expression = + Expression.Condition( + Expression.Equal(propertyOrField, Expression.Constant(null, propertyOrField.Type)), + Expression.Constant(null, ((PropertyInfo) autoMember.Value).PropertyType), clearanceExp); + } + else + { + expression = propertyOrField; + } + + + _bindingExpressions.Add(autoMember.Value.Name, Expression.Bind(autoMember.Value, expression)); } diff --git a/ExpressMapper NET40/TypeMapperBase.cs b/ExpressMapper NET40/TypeMapperBase.cs index 068fbee..c498930 100644 --- a/ExpressMapper NET40/TypeMapperBase.cs +++ b/ExpressMapper NET40/TypeMapperBase.cs @@ -191,7 +191,7 @@ protected void ProcessAutoProperties() IgnoreMemberList.Add(prop.Name); continue; } - AutoMembers.Add(prop, setprop); + AutoMembers[prop] = setprop; AutoMapProperty(prop, setprop); } } @@ -242,7 +242,7 @@ protected void MapFunction(MemberExpression left, Expression rightExpression) else { var binaryExpression = Expression.Assign(left, rightExpression); - CustomPropertyCache.Add(left.Member.Name, binaryExpression); + CustomPropertyCache[left.Member.Name] = binaryExpression; } } diff --git a/ExpressMapper NET45/ExpressMapper NET45.csproj b/ExpressMapper NET45/ExpressMapper NET45.csproj index ca721e5..eeb846b 100644 --- a/ExpressMapper NET45/ExpressMapper NET45.csproj +++ b/ExpressMapper NET45/ExpressMapper NET45.csproj @@ -100,6 +100,9 @@ PreciseSubstituteParameterVisitor.cs + + ProjectionAccessMemberVisitor.cs + Properties\AssemblyInfo.cs diff --git a/ExpressMapper NET451/ExpressMapper NET451.csproj b/ExpressMapper NET451/ExpressMapper NET451.csproj index 35c4eae..4025278 100644 --- a/ExpressMapper NET451/ExpressMapper NET451.csproj +++ b/ExpressMapper NET451/ExpressMapper NET451.csproj @@ -101,6 +101,9 @@ PreciseSubstituteParameterVisitor.cs + + ProjectionAccessMemberVisitor.cs + Properties\AssemblyInfo.cs diff --git a/ExpressMapper Net46/ExpressMapper Net46.csproj b/ExpressMapper Net46/ExpressMapper Net46.csproj index 2921dd6..9c761f3 100644 --- a/ExpressMapper Net46/ExpressMapper Net46.csproj +++ b/ExpressMapper Net46/ExpressMapper Net46.csproj @@ -103,6 +103,9 @@ PreciseSubstituteParameterVisitor.cs + + ProjectionAccessMemberVisitor.cs + SourceMappingService.cs diff --git a/ExpressMapper PCL NET40/ExpressMapper PCL NET40.csproj b/ExpressMapper PCL NET40/ExpressMapper PCL NET40.csproj index bd54c70..5f2f5aa 100644 --- a/ExpressMapper PCL NET40/ExpressMapper PCL NET40.csproj +++ b/ExpressMapper PCL NET40/ExpressMapper PCL NET40.csproj @@ -103,6 +103,9 @@ PreciseSubstituteParameterVisitor.cs + + ProjectionAccessMemberVisitor.cs + SourceMappingService.cs diff --git a/ExpressMapper PCL NET45/ExpressMapper PCL NET45.csproj b/ExpressMapper PCL NET45/ExpressMapper PCL NET45.csproj index b1b93f1..91c9e12 100644 --- a/ExpressMapper PCL NET45/ExpressMapper PCL NET45.csproj +++ b/ExpressMapper PCL NET45/ExpressMapper PCL NET45.csproj @@ -103,6 +103,9 @@ PreciseSubstituteParameterVisitor.cs + + ProjectionAccessMemberVisitor.cs + SourceMappingService.cs diff --git a/ExpressMapper.Tests NET40/BasicTests.cs b/ExpressMapper.Tests NET40/BasicTests.cs index 4e6b05b..f352f4b 100644 --- a/ExpressMapper.Tests NET40/BasicTests.cs +++ b/ExpressMapper.Tests NET40/BasicTests.cs @@ -715,6 +715,26 @@ public void ExistingDestCollEqualsWithNullElement() } } + [Test] + public void OtherCollectionTypesTest() + { + Mapper.Register(); + Mapper.Register() + .Member(dest => dest.ObservableCollection, src => src.Array) + .Ignore(dest => dest.Array); + + Mapper.Compile(); + var testResult = Functional.OtherCollectionMapTest(); + + var result = Mapper.Map(testResult.Key); + Assert.AreEqual(result.ObservableCollection.Count, testResult.Key.Array.Length); + + for (var i = 0; i < result.ObservableCollection.Count; i++) + { + Assert.AreEqual(result.ObservableCollection[i], testResult.Value.ObservableCollection[i]); + } + } + [Test] public void ExistingSrcCollGreater() { diff --git a/ExpressMapper.Tests.Model/Generator/Functional.cs b/ExpressMapper.Tests.Model/Generator/Functional.cs index c063a8d..bedb167 100644 --- a/ExpressMapper.Tests.Model/Generator/Functional.cs +++ b/ExpressMapper.Tests.Model/Generator/Functional.cs @@ -712,6 +712,40 @@ public static KeyValuePair ExistingDestCollEquals() return new KeyValuePair(testItem, testItemVm); } + public static KeyValuePair OtherCollectionMapTest() + { + var testItem = new TestItem(); + var testItemVm = new TestItemViewModel(); + + var testItems = new List(); + var testItemsVm = new List(); + + for (var i = 0; i < 5; i++) + { + var id = Guid.NewGuid(); + var format = string.Format("Name - {0}", i); + + var testCollection = new TestCollection + { + Id = id, + Name = format + }; + + var testCollectionVm = new TestCollectionViewModel + { + Id = id, + Name = format + }; + + testItems.Add(testCollection); + testItemsVm.Add(testCollectionVm); + } + testItem.Array = testItems.ToArray(); + testItemVm.ObservableCollection = new ObservableCollection(testItemsVm); + + return new KeyValuePair(testItem, testItemVm); + } + public static KeyValuePair ExistingDestCollEqualsWithNullElement() { var testItem = new TestItem(); diff --git a/ExpressMapper.Tests.Model/ViewModels/TestItemViewModel.cs b/ExpressMapper.Tests.Model/ViewModels/TestItemViewModel.cs index e537fc0..01ea260 100644 --- a/ExpressMapper.Tests.Model/ViewModels/TestItemViewModel.cs +++ b/ExpressMapper.Tests.Model/ViewModels/TestItemViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace ExpressMapper.Tests.Model.ViewModels @@ -10,5 +11,6 @@ public class TestItemViewModel public IList List { get; set; } public IEnumerable Enumerable { get; set; } public IQueryable Queryable { get; set; } + public ObservableCollection ObservableCollection { get; set; } } }