Reputation: 7579
The source class:
public class Post
{
public long ID { get; set; }
[Column(TypeName="nvarchar")]
[Required]
[StringLength(250)]
public string Name { get; set; }
[Column(TypeName="varchar")]
[StringLength(250)]
public string UrlName { get; set; }
[Column(TypeName="ntext")]
public string Excerpt { get; set; }
[Column(TypeName="ntext")]
[Required]
public string Content { get; set; }
public DateTime PostedTime { get; set; }
public DateTime? PublishedTime { get; set; }
public DateTime? LastUpdatedTime { get; set; }
public bool IsPublished { get; set; }
public virtual List<Category> Categories { get; set; }
public virtual List<Comment> Comments { get; set; }
public virtual List<Tag> Tags { get; set; }
}
the destination class
public class Post : Model
{
public long ID { get; set; }
public string Name { get; set; }
public string UrlName { get; set; }
public string Excerpt { get; set; }
public string Content { get; set; }
public DateTime PostedTime { get; set; }
public DateTime LastCommentedTime { get; set; }
public bool IsPublished { get; set; }
public List<Category> Category { get; set; }
public List<Comment> Comments { get; set; }
public List<Tag> Tags { get; set; }
}
I try using EmitMapper to map from each other; when mapping from source to desction, here is the code sample:
[TestMethod]
public void ShouleMapEntityToModel()
{
Post eP = new Post();
eP.ID = 2;
eP.Comments = new List<Comment>();
eP.Comments.Add(new Comment()
{
ID = 2,
Author = "derek"
});
var mP = eP.Map<Post, mBlog.Core.Models.Post>();
Assert.IsNotNull(mP);
Assert.AreEqual(1, mP.Comments.Count());
}
and I got an exception,
Test method mBlog.Test.EmitMapperTest.ShouleMapEntityToModel threw exception: System.Exception: Constructor for types [] not found in System.Collections.Generic.IList`1[[mBlog.Core.Models.Post, mBlog.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Upvotes: 3
Views: 2080
Reputation: 91
This answer shows how to handle IEnumerable to IEnumerable: EmitMapper and List
I believe that can be applied to this case too. Take a look:
This can be done creating a custom class, implementing the interface "ICustomConverterProvider" and adding a ConvertGeneric to the "DefaultMapConfig".
Looking on the source code of EmitMapper, i found a class named "ArraysConverterProvider", which is the default generic converter from ICollections to Arrays.
Adapting the code from this class to work with IEnumerable collections:
class GenericIEnumerableConverterProvider : ICustomConverterProvider { public CustomConverterDescriptor GetCustomConverterDescr( Type from, Type to, MapConfigBaseImpl mappingConfig) { var tFromTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(from); var tToTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(to); if (tFromTypeArgs == null || tToTypeArgs == null || tFromTypeArgs.Length != 1 || tToTypeArgs.Length != 1) { return null; } var tFrom = tFromTypeArgs[0]; var tTo = tToTypeArgs[0]; if (tFrom == tTo && (tFrom.IsValueType || mappingConfig.GetRootMappingOperation(tFrom, tTo).ShallowCopy)) { return new CustomConverterDescriptor { ConversionMethodName = "Convert", ConverterImplementation = typeof(GenericIEnumerableConverter_OneTypes<>), ConverterClassTypeArguments = new[] { tFrom } }; } return new CustomConverterDescriptor { ConversionMethodName = "Convert", ConverterImplementation = typeof(GenericIEnumerableConverter_DifferentTypes<,>), ConverterClassTypeArguments = new[] { tFrom, tTo } }; } } class GenericIEnumerableConverter_DifferentTypes<TFrom, TTo> : ICustomConverter { private Func<TFrom, TTo> _converter; public IEnumerable<TTo> Convert(IEnumerable<TFrom> from, object state) { if (from == null) { return null; } TTo[] result = new TTo[from.Count()]; int idx = 0; foreach (var f in from) { result[idx++] = _converter(f); } return result; } public void Initialize(Type from, Type to, MapConfigBaseImpl mappingConfig) { var staticConverters = mappingConfig.GetStaticConvertersManager() ?? StaticConvertersManager.DefaultInstance; var staticConverterMethod = staticConverters.GetStaticConverter(typeof(TFrom), typeof(TTo)); if (staticConverterMethod != null) { _converter = (Func<TFrom, TTo>)Delegate.CreateDelegate( typeof(Func<TFrom, TTo>), null, staticConverterMethod ); } else { _subMapper = ObjectMapperManager.DefaultInstance.GetMapperImpl(typeof(TFrom), typeof(TTo), mappingConfig); _converter = ConverterBySubmapper; } } ObjectsMapperBaseImpl _subMapper; private TTo ConverterBySubmapper(TFrom from) { return (TTo)_subMapper.Map(from); } } class GenericIEnumerableConverter_OneTypes<T> { public IEnumerable<T> Convert(IEnumerable<T> from, object state) { if (from == null) { return null; } return from; } }
This code is just a copy with a minimum of adaptation as possible and can be applyed to objects with many levels of hierarchy.
You can use the above code with the following command:
new DefaultMapConfig().ConvertGeneric( typeof(IEnumerable<>), typeof(IEnumerable<>), new GenericIEnumerableConverterProvider());
This saved my day and I hope to save yours too! hehehe
Upvotes: 0
Reputation: 415
I had the same problem, but I have found the solution. Don't user Lists for your destination object. If you use simple arrays in your mBlog.Core.Models.Post object you should get a nicely filled object. So your destination class should look like:
public class Post : Model
{
public long ID { get; set; }
public string Name { get; set; }
public string UrlName { get; set; }
public string Excerpt { get; set; }
public string Content { get; set; }
public DateTime PostedTime { get; set; }
public DateTime LastCommentedTime { get; set; }
public bool IsPublished { get; set; }
public Category[] Category { get; set; }
public Comment[] Comments { get; set; }
public Tag[] Tags { get; set; }
}
Upvotes: 1