Проблема отображения списков в EmitMapper с коллекциями
Исходный класс:
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; }
}
класс назначения
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; }
}
Я пытаюсь использовать EmitMapper для сопоставления друг с другом; при отображении из исходного кода в приведенный ниже пример кода:
[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());
}
и я получил исключение,
Метод тестирования mBlog.Test.EmitMapperTest.ShouleMapEntityToModel вызвал исключение: System.Exception: конструктор для типов [] не найден в System.Collections.Generic.IList`1[[mBlog.Core.Models.Post, mBlog.Core, Version=1.0.0.0, Культура = нейтральная, PublicKeyToken=null]]
2 ответа
У меня была такая же проблема, но я нашел решение. Не используйте списки пользователей для вашего целевого объекта. Если вы используете простые массивы в своем объекте mBlog.Core.Models.Post, вы должны получить красиво заполненный объект. Таким образом, ваш класс назначения должен выглядеть так:
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; }
}
Этот ответ показывает, как обрабатывать IEnumerable в IEnumerable: EmitMapper и List
Я считаю, что это применимо и к этому делу. Взглянуть:
Это можно сделать путем создания пользовательского класса, реализации интерфейса "ICustomConverterProvider" и добавления ConvertGeneric к "DefaultMapConfig".
Глядя на исходный код EmitMapper, я нашел класс с именем "ArraysConverterProvider", который является стандартным конвертером по умолчанию из ICollections в Arrays.
Адаптация кода из этого класса для работы с коллекциями IEnumerable:
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; } }
Этот код является просто копией с минимально возможной адаптацией и может применяться к объектам со многими уровнями иерархии.
Вы можете использовать приведенный выше код с помощью следующей команды:
new DefaultMapConfig().ConvertGeneric( typeof(IEnumerable<>), typeof(IEnumerable<>), new GenericIEnumerableConverterProvider());
Это спасло мой день, и я надеюсь сохранить и ваш! хехехе