diff --git a/src/Detached.Mappers.EntityFramework/Detached.Mappers.EntityFramework.csproj b/src/Detached.Mappers.EntityFramework/Detached.Mappers.EntityFramework.csproj index 2ff1e25f..bd443b48 100644 --- a/src/Detached.Mappers.EntityFramework/Detached.Mappers.EntityFramework.csproj +++ b/src/Detached.Mappers.EntityFramework/Detached.Mappers.EntityFramework.csproj @@ -13,19 +13,23 @@ true logo.png - Detached.Mappers.EntityFramework Readme.MD + Detached.Mappers.EntityFramework + Readme.MD + + + diff --git a/src/Detached.Mappers.EntityFramework/TypeMappers/AggregationEntityTypeMapper.cs b/src/Detached.Mappers.EntityFramework/TypeMappers/AggregationEntityTypeMapper.cs index d09fa813..dc71a1d6 100644 --- a/src/Detached.Mappers.EntityFramework/TypeMappers/AggregationEntityTypeMapper.cs +++ b/src/Detached.Mappers.EntityFramework/TypeMappers/AggregationEntityTypeMapper.cs @@ -4,7 +4,6 @@ namespace Detached.Mappers.EntityFramework.TypeMappers { public class AggregationEntityTypeMapper : EntityTypeMapper - where TSource : class where TTarget : class where TKey : IEntityKey { diff --git a/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapper.cs b/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapper.cs index b72a2816..d8a559b3 100644 --- a/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapper.cs +++ b/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapper.cs @@ -11,7 +11,6 @@ namespace Detached.Mappers.EntityFramework.TypeMappers { public abstract class EntityTypeMapper : TypeMapper - where TSource : class where TTarget : class where TKey : IEntityKey { diff --git a/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapperFactory.cs b/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapperFactory.cs index c8d721ee..f7f93f02 100644 --- a/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapperFactory.cs +++ b/src/Detached.Mappers.EntityFramework/TypeMappers/EntityTypeMapperFactory.cs @@ -10,8 +10,7 @@ public class EntityTypeMapperFactory : ITypeMapperFactory { public bool CanCreate(Mapper mapper, TypePair typePair) { - return typePair.SourceType.IsComplexOrEntity() - && typePair.TargetType.IsEntity() + return typePair.TargetType.IsEntity() && typePair.TargetType.IsConcrete(); } diff --git a/src/Detached.Mappers/Detached.Mappers.csproj b/src/Detached.Mappers/Detached.Mappers.csproj index 1632e6bd..f4c1d8a7 100644 --- a/src/Detached.Mappers/Detached.Mappers.csproj +++ b/src/Detached.Mappers/Detached.Mappers.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Detached.Mappers/MapperOptions.cs b/src/Detached.Mappers/MapperOptions.cs index 74701460..9621bbe1 100644 --- a/src/Detached.Mappers/MapperOptions.cs +++ b/src/Detached.Mappers/MapperOptions.cs @@ -13,6 +13,7 @@ using Detached.Mappers.TypeMappers.POCO.Nullable; using Detached.Mappers.TypeMappers.POCO.Primitive; using Detached.Mappers.TypePairs; +using Detached.Mappers.TypePairs.Conventions; using Detached.Mappers.Types; using Detached.Mappers.Types.Class; using Detached.Mappers.Types.Class.Builder; @@ -78,8 +79,7 @@ public MapperOptions() new NullableTypeMapperFactory(), new InheritedTypeMapperFactory(), new EntityCollectionTypeMapperFactory(), - new EntityTypeMapperFactory(), - new KeyToComplexTypeMapperFactory() + new EntityTypeMapperFactory() }; ConcreteTypes = new Dictionary @@ -111,7 +111,11 @@ public MapperOptions() new NullableTypeBinder() }; - PropertyNameConventions = new List(); + MemberNameConventions = new List + { + new ForeignKeyMemberNameConvention(), + new DefaultMemberNameConvention() + }; } public virtual HashSet Primitives { get; } @@ -126,7 +130,7 @@ public MapperOptions() public virtual List TypeConventions { get; } - public virtual List PropertyNameConventions { get; } + public virtual List MemberNameConventions { get; } public virtual Dictionary ConcreteTypes { get; } diff --git a/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapper.cs b/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapper.cs index 034eb09e..c6d89b63 100644 --- a/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapper.cs +++ b/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapper.cs @@ -6,7 +6,6 @@ namespace Detached.Mappers.TypeMappers.Entity.Collection public class EntityCollectionTypeMapper : TypeMapper where TSource : IEnumerable where TTarget : ICollection - where TSourceItem : class where TTargetItem : class where TKey : IEntityKey { diff --git a/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapperFactory.cs b/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapperFactory.cs index e09e4141..576d6aa6 100644 --- a/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapperFactory.cs +++ b/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityCollectionTypeMapperFactory.cs @@ -18,7 +18,7 @@ public bool CanCreate(Mapper mapper, TypePair typePair) IType sourceItemType = mapper.Options.GetType(typePair.TargetType.ItemClrType); IType targetItemType = mapper.Options.GetType(typePair.TargetType.ItemClrType); - return targetItemType.IsEntity() && sourceItemType.IsComplexOrEntity(); + return targetItemType.IsEntity() && (sourceItemType.IsComplexOrEntity() || sourceItemType.IsPrimitive()); } return false; diff --git a/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityListTypeMapper.cs b/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityListTypeMapper.cs index 76f3ff86..fd44b721 100644 --- a/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityListTypeMapper.cs +++ b/src/Detached.Mappers/TypeMappers/Entity/Collection/EntityListTypeMapper.cs @@ -6,7 +6,6 @@ namespace Detached.Mappers.TypeMappers.Entity.Collection public class EntityListTypeMapper : TypeMapper where TSource : IEnumerable where TTarget : IList - where TSourceItem : class where TTargetItem : class where TKey : IEntityKey { diff --git a/src/Detached.Mappers/TypePairs/Conventions/CamelCaseMemberNameConvention.cs b/src/Detached.Mappers/TypePairs/Conventions/CamelCaseMemberNameConvention.cs new file mode 100644 index 00000000..24b44b39 --- /dev/null +++ b/src/Detached.Mappers/TypePairs/Conventions/CamelCaseMemberNameConvention.cs @@ -0,0 +1,12 @@ +using Detached.Mappers.Types; + +namespace Detached.Mappers.TypePairs.Conventions +{ + public class CamelCaseMemberNameConvention : IMemberNameConvention + { + public string GetSourceMemberName(string targetMemberName, IType sourceType, IType targetType, MapperOptions mapperOptions) + { + return char.ToLower(targetMemberName[0]) + targetMemberName.Substring(1); + } + } +} diff --git a/src/Detached.Mappers/TypePairs/Conventions/DefaultMemberNameConvention.cs b/src/Detached.Mappers/TypePairs/Conventions/DefaultMemberNameConvention.cs new file mode 100644 index 00000000..7c6523ec --- /dev/null +++ b/src/Detached.Mappers/TypePairs/Conventions/DefaultMemberNameConvention.cs @@ -0,0 +1,12 @@ +using Detached.Mappers.Types; + +namespace Detached.Mappers.TypePairs.Conventions +{ + public class DefaultMemberNameConvention : IMemberNameConvention + { + public string GetSourceMemberName(string targetMemberName, IType sourceType, IType targetType, MapperOptions mapperOptions) + { + return targetMemberName; + } + } +} diff --git a/src/Detached.Mappers/TypePairs/Conventions/ForeignKeyMemberNameConvention.cs b/src/Detached.Mappers/TypePairs/Conventions/ForeignKeyMemberNameConvention.cs new file mode 100644 index 00000000..b5e1d612 --- /dev/null +++ b/src/Detached.Mappers/TypePairs/Conventions/ForeignKeyMemberNameConvention.cs @@ -0,0 +1,35 @@ +using Detached.Mappers.Types; +using Humanizer; + +namespace Detached.Mappers.TypePairs.Conventions +{ + public class ForeignKeyMemberNameConvention : IMemberNameConvention + { + public string GetSourceMemberName(string targetMemberName, IType sourceType, IType targetType, MapperOptions mapperOptions) + { + string memberName = null; + + if (targetType.IsEntity()) + { + var key = targetType.GetKeyMember(); + + if (key != null) + { + var member = targetType.GetMember(targetMemberName); + var memberType = mapperOptions.GetType(member.ClrType); + + if (memberType.IsCollection()) + { + memberName = targetMemberName.Singularize(false) + key.Name.Pluralize(false); + } + else + { + memberName = targetMemberName + key.Name; + } + } + } + + return memberName; + } + } +} diff --git a/src/Detached.Mappers/TypePairs/Conventions/IMemberNameConvention.cs b/src/Detached.Mappers/TypePairs/Conventions/IMemberNameConvention.cs new file mode 100644 index 00000000..e079ed45 --- /dev/null +++ b/src/Detached.Mappers/TypePairs/Conventions/IMemberNameConvention.cs @@ -0,0 +1,9 @@ +using Detached.Mappers.Types; + +namespace Detached.Mappers.TypePairs.Conventions +{ + public interface IMemberNameConvention + { + string GetSourceMemberName(string targetMemberName, IType sourceType, IType targetType, MapperOptions mapperOptions); + } +} diff --git a/src/Detached.Mappers/TypePairs/ITypePairFactory.cs b/src/Detached.Mappers/TypePairs/ITypePairFactory.cs index 6da27668..e498dd58 100644 --- a/src/Detached.Mappers/TypePairs/ITypePairFactory.cs +++ b/src/Detached.Mappers/TypePairs/ITypePairFactory.cs @@ -4,6 +4,6 @@ namespace Detached.Mappers.TypePairs { public interface ITypePairFactory { - TypePair Create(MapperOptions mapperOptions, IType sourceType, IType targetType, TypePairMember sourceMember); + TypePair Create(MapperOptions mapperOptions, IType sourceType, IType targetType, TypePairMember parentMember); } } \ No newline at end of file diff --git a/src/Detached.Mappers/TypePairs/TypePairFactory.cs b/src/Detached.Mappers/TypePairs/TypePairFactory.cs index 969e327c..c5f88742 100644 --- a/src/Detached.Mappers/TypePairs/TypePairFactory.cs +++ b/src/Detached.Mappers/TypePairs/TypePairFactory.cs @@ -13,8 +13,6 @@ public TypePair Create(MapperOptions mapperOptions, IType sourceType, IType targ if (memberNames != null) { - var keyMember = targetType.GetKeyMember(); - foreach (string targetMemberName in memberNames) { ITypeMember targetMember = targetType.GetMember(targetMemberName); @@ -26,18 +24,7 @@ public TypePair Create(MapperOptions mapperOptions, IType sourceType, IType targ member.TargetType = targetType; member.SourceType = sourceType; member.TargetMember = targetMember; - - string sourceMemberName = GetSourcePropertyName(mapperOptions, sourceType, targetType, targetMemberName); - - ITypeMember sourceMember = sourceType.GetMember(sourceMemberName); - - if (sourceMember == null && keyMember != null) - { - string keyName = targetMemberName + keyMember.Name; - sourceMember = sourceType.GetMember(keyName); - } - - member.SourceMember = sourceMember; + member.SourceMember = GetSourceMember(targetMemberName, sourceType, targetType, mapperOptions); typePair.Members.Add(targetMemberName, member); } @@ -47,14 +34,26 @@ public TypePair Create(MapperOptions mapperOptions, IType sourceType, IType targ return typePair; } - public string GetSourcePropertyName(MapperOptions mapperOptions, IType sourceType, IType targetType, string memberName) + public ITypeMember GetSourceMember(string targetMemberName, IType sourceType, IType targetType, MapperOptions mapperOptions) { - for (int i = mapperOptions.PropertyNameConventions.Count - 1; i >= 0; i--) + ITypeMember result = null; + + for (int i = mapperOptions.MemberNameConventions.Count - 1; i >= 0; i--) { - memberName = mapperOptions.PropertyNameConventions[i].GetSourcePropertyName(sourceType, targetType, memberName); + var convention = mapperOptions.MemberNameConventions[i]; + + var sourceMemberName = convention.GetSourceMemberName(targetMemberName, sourceType, targetType, mapperOptions); + + if (sourceMemberName != null) + { + result = sourceType.GetMember(sourceMemberName); + + if (result != null) + break; + } } - return memberName; + return result; } } -} +} \ No newline at end of file diff --git a/src/Detached.Mappers/TypePairs/TypePairMember.cs b/src/Detached.Mappers/TypePairs/TypePairMember.cs index 8dd4815c..54fa88ff 100644 --- a/src/Detached.Mappers/TypePairs/TypePairMember.cs +++ b/src/Detached.Mappers/TypePairs/TypePairMember.cs @@ -14,5 +14,16 @@ public class TypePairMember public ITypeMember TargetMember { get; set; } public AnnotationCollection Annotations { get; set; } = new(); + + public override string ToString() + { + string sourceName = SourceMember == null + ? "null" + : SourceMember.Name + ": " + SourceMember.ClrType.GetFriendlyName(); + + string targetName = TargetMember.ClrType.GetFriendlyName(); + + return $"TypePairMember ({sourceName} -> {targetName})"; + } } } \ No newline at end of file diff --git a/src/Detached.Mappers/Types/Class/ClassType.cs b/src/Detached.Mappers/Types/Class/ClassType.cs index b88c3d98..8dab346d 100644 --- a/src/Detached.Mappers/Types/Class/ClassType.cs +++ b/src/Detached.Mappers/Types/Class/ClassType.cs @@ -38,6 +38,6 @@ public virtual Expression BuildNewExpression(Expression context, Expression disc return Import(Constructor, context); } - public override string ToString() => $"{ClrType.GetFriendlyName()} (ClassType)"; + public override string ToString() => $"ClassType ({ClrType.GetFriendlyName()})"; } } \ No newline at end of file diff --git a/src/Detached.Mappers/Types/Class/ClassTypeMember.cs b/src/Detached.Mappers/Types/Class/ClassTypeMember.cs index 373a1374..8e2a1376 100644 --- a/src/Detached.Mappers/Types/Class/ClassTypeMember.cs +++ b/src/Detached.Mappers/Types/Class/ClassTypeMember.cs @@ -47,6 +47,6 @@ public virtual Expression BuildTryGetExpression(Expression instance, Expression return Import(TryGetter, instance, outVar, context); } - public override string ToString() => $"{Name} [{ClrType.GetFriendlyName()}] (MemberOptions)"; + public override string ToString() => $"ClassTypeMember ({Name}: {ClrType.GetFriendlyName()})"; } } \ No newline at end of file diff --git a/src/Detached.Mappers/Types/Conventions/CamelCasePropertyNameConvention.cs b/src/Detached.Mappers/Types/Conventions/CamelCasePropertyNameConvention.cs deleted file mode 100644 index bf4d5e0f..00000000 --- a/src/Detached.Mappers/Types/Conventions/CamelCasePropertyNameConvention.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Detached.Mappers.Types.Conventions -{ - public class CamelCasePropertyNameConvention : IPropertyNameConvention - { - public string GetSourcePropertyName(IType sourceType, IType targetType, string targetMemberName) - { - return char.ToLower(targetMemberName[0]) + targetMemberName.Substring(1); - } - } -} diff --git a/src/Detached.Mappers/Types/Conventions/IPropertyNameConvention.cs b/src/Detached.Mappers/Types/Conventions/IPropertyNameConvention.cs deleted file mode 100644 index cc4ef0d6..00000000 --- a/src/Detached.Mappers/Types/Conventions/IPropertyNameConvention.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Detached.Mappers.Types.Conventions -{ - public interface IPropertyNameConvention - { - string GetSourcePropertyName(IType sourceType, IType targetType, string targetMemberName); - } -} diff --git a/test/Detached.Mappers.EntityFramework.Tests/KeyToEntityTests.cs b/test/Detached.Mappers.EntityFramework.Tests/ForeignKeyTests.cs similarity index 74% rename from test/Detached.Mappers.EntityFramework.Tests/KeyToEntityTests.cs rename to test/Detached.Mappers.EntityFramework.Tests/ForeignKeyTests.cs index 0423533b..a5b941ea 100644 --- a/test/Detached.Mappers.EntityFramework.Tests/KeyToEntityTests.cs +++ b/test/Detached.Mappers.EntityFramework.Tests/ForeignKeyTests.cs @@ -1,15 +1,16 @@ using Detached.Mappers.EntityFramework.Extensions; using Detached.Mappers.EntityFramework.Tests.Fixture; using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace Detached.Mappers.EntityFramework.Tests { - public class KeyToEntityTests + public class ForeignKeyTests { - //[Fact] - public async Task map_key_to_entity() + [Fact] + public async Task map_fk_to_entity() { var dbContext = await TestDbContext.Create(); @@ -22,12 +23,20 @@ public async Task map_key_to_entity() { Id = 1, Name = "test user", - ChildId = 2 + ChildId = 2, + ChildIds = new[] { 1, 2 } }); Assert.NotNull(result.Child); Assert.Equal(2, result.Child.Id); Assert.Equal("Child 2", result.Child.Name); + + Assert.NotNull(result.Children); + Assert.Equal(1, result.Children[0].Id); + Assert.Equal("Child 1", result.Children[0].Name); + + Assert.Equal(2, result.Children[1].Id); + Assert.Equal("Child 2", result.Children[1].Name); } public class ParentEntity @@ -37,6 +46,8 @@ public class ParentEntity public string Name { get; set; } public ChildEntity Child { get; set; } + + public List Children { get; set; } } public class ChildEntity diff --git a/test/Detached.Mappers.Json.Tests/JsonTests.cs b/test/Detached.Mappers.Json.Tests/JsonTests.cs index 792db743..d3704e22 100644 --- a/test/Detached.Mappers.Json.Tests/JsonTests.cs +++ b/test/Detached.Mappers.Json.Tests/JsonTests.cs @@ -1,5 +1,5 @@ using Detached.Mappers.Json.Tests.Fixture; -using Detached.Mappers.Types.Conventions; +using Detached.Mappers.TypePairs.Conventions; using System.Text.Json.Nodes; using Xunit; @@ -22,7 +22,7 @@ public void map_json_to_entity() // create options. Json lib is a separate package to avoid adding System.Text.Json dependency to the main lib, call WithJson() to integrate. MapperOptions mapperOptions = new MapperOptions().WithJson(); - mapperOptions.PropertyNameConventions.Add(new CamelCasePropertyNameConvention()); + mapperOptions.MemberNameConventions.Add(new CamelCaseMemberNameConvention()); // create the mapper. Mapper mapper = new Mapper(mapperOptions);