0

Entity Framework我正在创建基于和的三层应用程序AutoMapper。我有 DAL、BLL 和表示层。这是我的数据访问层实体:

[Table("Person")]
public class Person
{
    [Key]
    public virtual long Id { get; set; }
    [Column("Pib")]
    public virtual string Pib { get; set; }
    [Column("Bd")]
    public virtual Nullable<DateTime> BirthdaySingle { get; set; }
    [Column("Bp")]
    public virtual string BirthPlace { get; set; }
    [Column("Other")]
    public virtual string Other { get; set; }
    public virtual ICollection<Photo> Photos { get; set; }
}

[Table("Photo")]
public class Photo
{
    [Key]
    public virtual long Id { get; set; }
    [Column("Ph")]
    public virtual byte[] RealPhoto { get; set; }
    public virtual Nullable<long> PersonId { get; set; }
    [ForeignKey("PersonId")]
    public virtual Person Person { get; set; }
}

对于业务层:

public class PersonDTO
{
    public virtual long Id { get; set; }
    public virtual string Pib { get; set; }
    public virtual Nullable<DateTime> BirthdaySingle { get; set; }
    public virtual string BirthPlace { get; set; }
    public virtual string Other { get; set; }
    public virtual ICollection<PhotoDTO> Photos { get; set; }
}

public class PhotoDTO
{
    public virtual long Id { get; set; }
    public virtual byte[] RealPhoto { get; set; }
    public virtual Nullable<long> PersonId { get; set; }
    public virtual PersonDTO PersonDTO { get; set; }
}

对于表示层:

// class for showing details
public class PersonViewModel
{
    public virtual long Id { get; set; }
    public virtual string Pib { get; set; }
    public virtual Nullable<DateTime> BirthdaySingle { get; set; }
    public virtual string BirthPlace { get; set; }
    public virtual string Other { get; set; }
    public virtual ICollection<PhotoViewModel> Photos { get; set; }

    public override string ToString()
    {
        string result = string.Empty;

        if (!string.IsNullOrWhiteSpace(Pib))
            result = string.Format("\r\n{0}", Pib);
        if (BirthdaySingle.HasValue)
            result += string.Format("\r\n{0}", BirthdaySingle.Value.ToShortDateString());
        if (!string.IsNullOrWhiteSpace(BirthPlace))
            result += string.Format("\r\n{0}", BirthPlace);
        if (!string.IsNullOrWhiteSpace(Other))
            result += string.Format("\r\n{0}", Other);

        return result;
    }
}

// class for showing list of objects
public class PersonListViewModel
{
    public class PersonShortViewModel
    {
        [DisplayName("#")]
        public virtual long Id { get; set; }
        [DisplayName("Full Name")]
        public virtual string Pib { get; set; }
        [DisplayName("Birth Date")]
        [DisplayFormat(DataFormatString = "{0:dd.MM.yyyy}")]
        public virtual Nullable<DateTime> BirthdaySingle { get; set; }
    }

    public IPagedList<PersonShortViewModel> Persons { get; set; } 
}

public class PhotoViewModel
{
    public virtual long Id { get; set; }
    public virtual byte[] RealPhoto { get; set; }
    public virtual Nullable<long> PersonId { get; set; }
}

所以,我在 BLL 中有一个 DataService 类:

public class DataService : IDataService
{
    IUnitOfWork Database { get; set; }

    /// <summary>
    /// Инициализирует новый экземпляр класса <see cref="T:System.Object"/>.
    /// </summary>
    public DataService(IUnitOfWork database)
    {
        //AutoMapperBLLConfiguration.Configure();
        Database = database;
    }

    public bool IsConnected()
    {
        return Database.IsConnected();
    }

    public IQueryable<PersonDTO> GetPersons()
    {
        Mapper.CreateMap<Person, PersonDTO>().ForMember(ph => ph.Photos, opt => opt.Ignore());
        return Database.Persons.GetAll().ProjectTo<PersonDTO>();
    }

    public PersonDTO GetPerson(long id)
    {
        var person = Database.Persons.GetById(id);
        if (person == null)
        {
            throw new ValidationException("Об'єкт не знайдено.", "");
        }

        Mapper.CreateMap<Photo, PhotoDTO>();
        Mapper.CreateMap<Person, PersonDTO>().ForMember(pe => pe.Photos, opt => opt.MapFrom(p => p.Photos));
        return Mapper.Map<PersonDTO>(person);
    }

    public IEnumerable<PersonDTO> GetPersonsBy(Expression<Func<PersonDTO, bool>> predicate)
    {
        if (predicate == null)
        {
            throw new ValidationException("Відсутня умова пошуку.", "");
        }

        Mapper.CreateMap<PersonDTO, Person>().ForMember(person => person.CbdId, opt => opt.Ignore());
        return
            Mapper.Map<IEnumerable<PersonDTO>>(
                Database.Persons.GetByCondition(predicate.RemapForType<PersonDTO, Person, bool>()));
    }

    public PhotoDTO GetPhoto(long id)
    {
        var photo = Database.Photos.GetById(id);
        if (photo == null)
        {
            throw new ValidationException("Зображення не знайдено.", "");
        }

        return Mapper.Map<PhotoDTO>(photo);
    }

    public IEnumerable<PhotoDTO> GetPhotosBy(Expression<Func<PhotoDTO, bool>> predicate)
    {
        if (predicate == null)
        {
            throw new ValidationException("Відсутня умова пошуку.", "");
        }
        Expression<Func<Photo, bool>> mappedSelector = Mapper.Map<Expression<Func<Photo, bool>>>(predicate);
        return Mapper.Map<IEnumerable<PhotoDTO>>(Database.Photos.GetByCondition(mappedSelector));
    }

    public void Dispose()
    {
        Database.Dispose();
    }
}

我决定为 AutoMapper 创建单独的配置,因为我认为表示层不需要了解 BLL ......

public static class AutoMapperBLLConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(configuration =>

               /*configuration.AddProfile(new PhotoIgnoreProfile());
               configuration.AddProfile(new PhotoIncludeProfile());*/
               /*configuration.AddProfile(new PhotoProfile());
               configuration.AddProfile(new PersonProfile());*/
               GetConfiguration(Mapper.Configuration)
            );
        Mapper.AssertConfigurationIsValid();
    }

    private static void GetConfiguration(IConfiguration configuration)
    {
        var profiles =
            typeof(PhotoProfile).Assembly.GetTypes().Where(type => typeof(Profile).IsAssignableFrom(type));
        foreach (Type profile in profiles)
        {
            configuration.AddProfile(Activator.CreateInstance(profile) as Profile);
        }
    }
}

public class PersonProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Person, PersonDTO>();
        Mapper.CreateMap<PersonDTO, Person>().ForMember(person => person.CbdId, opt => opt.Ignore());
    }
}

/*public class PhotoIgnoreProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Person, PersonDTO>().ForMember(ph => ph.Photos, opt => opt.Ignore());
    }
}

public class PhotoIncludeProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Person, PersonDTO>().ForMember(pe => pe.Photos, opt => opt.MapFrom(p => p.Photos));
    }
}*/

public class PhotoProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Photo, PhotoDTO>().ForMember(dto => dto.PersonDTO, opt => opt.MapFrom(photo => photo.Person));
        Mapper.CreateMap<PhotoDTO, Photo>().ForMember(photo => photo.Person, opt => opt.MapFrom(dto => dto.PersonDTO));
    }
}

对于表示层:

public static class AutoMapperPLConfiguration
{
    public static void Configure()
    {
        Mapper.Initialize(configuration =>

                //configuration.AddProfile(new PersonViewProfile());
                //configuration.AddProfile(new PhotoViewProfile());
                GetConfiguration(Mapper.Configuration)
            );
        Mapper.AssertConfigurationIsValid();
    }

    private static void GetConfiguration(IConfiguration configuration)
    {
        // we use order by because we need photo mapping to be the first
        var profiles =
            typeof(PhotoViewProfile).Assembly.GetTypes().Where(type => typeof(Profile).IsAssignableFrom(type)).OrderByDescending(type => type.Name);
        foreach (Type profile in profiles)
        {
            configuration.AddProfile(Activator.CreateInstance(profile) as Profile);
        }
    }
}

public class PersonViewProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<PersonDTO, PersonViewModel>()
              .ForMember(model => model.Photos, opt => opt.MapFrom(dto => dto.Photos));
        Mapper.CreateMap<PersonViewModel, PersonDTO>()
              .ForMember(dto => dto.Photos, opt => opt.MapFrom(model => model.Photos));
    }
}

public class PersonShortViewProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<PersonDTO, PersonListViewModel.PersonShortViewModel>().IgnoreAllNonExisting();
        Mapper.CreateMap<IPagedList<PersonDTO>, IPagedList<PersonListViewModel.PersonShortViewModel>>()
              .AfterMap((s, d) =>
                        Mapper
                            .Map<IEnumerable<PersonDTO>, IEnumerable<PersonListViewModel.PersonShortViewModel>>(s, d))
              .ConvertUsing<PagedListConverter<PersonDTO, PersonListViewModel.PersonShortViewModel>>();
        Mapper.CreateMap<PersonDTO, PersonListViewModel.PersonShortViewModel>().IgnoreAllNonExisting();
    }
}
public class PhotoViewProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<PhotoDTO, PhotoViewModel>().ForSourceMember(dto => dto.PersonDTO, opt => opt.Ignore());
        Mapper.CreateMap<PhotoViewModel, PhotoDTO>().ForMember(dto => dto.PersonDTO, opt => opt.Ignore());
    }
}

我也有这样的扩展来使用Expressions和转换为PagedList

public class PagedListConverter<TIn, TOut> : ITypeConverter<IPagedList<TIn>, IPagedList<TOut>>
{
    /// <summary>
    /// Performs conversion from source to destination type
    /// </summary>
    /// <param name="context">Resolution context</param>
    /// <returns>
    /// Destination object
    /// </returns>
    public IPagedList<TOut> Convert(ResolutionContext context)
    {

        var source = (IPagedList<TIn>) context.SourceValue;
        var mapped = Mapper.Map<IList<TOut>>(source);
        return new StaticPagedList<TOut>(mapped,source.GetMetaData());
    }
}
    /// <summary>
/// An <see cref="ExpressionVisitor"/> implementation which uses <see href="http://automapper.org">AutoMapper</see> to remap property access from elements of type <typeparamref name="TSource"/> to elements of type <typeparamref name="TDestination"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
    private readonly ParameterExpression _newParameter;
    private readonly TypeMap _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();

    /// <summary>
    /// Initialises a new instance of the <see cref="AutoMapVisitor{TSource, TDestination}"/> class.
    /// </summary>
    /// <param name="newParameter">The new <see cref="ParameterExpression"/> to access.</param>
    public AutoMapVisitor(ParameterExpression newParameter)
    {
        Contract.Requires(newParameter != null);

        _newParameter = newParameter;
        Contract.Assume(_typeMap != null);
    }

    [ContractInvariantMethod]
    [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
    private void ObjectInvariant()
    {
        Contract.Invariant(_typeMap != null);
        Contract.Invariant(_newParameter != null);
    }

    /// <summary>
    /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression"/>.
    /// </summary>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
    /// </returns>
    /// <param name="node">The expression to visit.</param>
    protected override Expression VisitMember(MemberExpression node)
    {
        var propertyMaps = _typeMap.GetPropertyMaps();
        Contract.Assume(propertyMaps != null);

        // Find any mapping for this member
        var propertyMap = propertyMaps.SingleOrDefault(map => map.SourceMember == node.Member);
        if (propertyMap == null)
        {
            return base.VisitMember(node);
        }

        var destinationProperty = propertyMap.DestinationProperty;
        Contract.Assume(destinationProperty != null);

        var destinationMember = destinationProperty.MemberInfo;
        Contract.Assume(destinationMember != null);

        // Check the new member is a property too
        var property = destinationMember as PropertyInfo;
        if (property == null)
        {
            return base.VisitMember(node);    
        }

        // Access the new property
        var newPropertyAccess = Expression.Property(_newParameter, property);
        return base.VisitMember(newPropertyAccess);
    }
}

/// <summary>
/// A class which contains extension methods for <see cref="Expression"/> and <see cref="Expression{TDelegate}"/> instances.
/// </summary>
public static class ExpressionExtensions
{
    /// <summary>
    /// Remaps all property access from type <typeparamref name="TSource"/> to <typeparamref name="TDestination"/> in <paramref name="expression"/>.
    /// </summary>
    /// <typeparam name="TSource">The type of the source element.</typeparam>
    /// <typeparam name="TDestination">The type of the destination element.</typeparam>
    /// <typeparam name="TResult">The type of the result from the lambda expression.</typeparam>
    /// <param name="expression">The <see cref="Expression{TDelegate}"/> to remap the property access in.</param>
    /// <returns>An <see cref="Expression{TDelegate}"/> equivalent to <paramref name="expression"/>, but applying to elements of type <typeparamref name="TDestination"/> instead of <typeparamref name="TSource"/>.</returns>
    public static Expression<Func<TDestination, TResult>> RemapForType<TSource, TDestination, TResult>(
        this Expression<Func<TSource, TResult>> expression)
    {
        Contract.Requires(expression != null);
        Contract.Ensures(Contract.Result<Expression<Func<TDestination, TResult>>>() != null);

        var newParameter = Expression.Parameter(typeof (TDestination));
        Contract.Assume(newParameter != null);

        var visitor = new AutoMapVisitor<TSource, TDestination>(newParameter);
        var remappedBody = visitor.Visit(expression.Body);
        if (remappedBody == null)
        {
            throw new InvalidOperationException("Unable to remap expression");
        }
        return Expression.Lambda<Func<TDestination, TResult>>(remappedBody, newParameter);
    }
}

像这样使用它:

//...
 PersonListViewModel list = new PersonListViewModel();
            Mapper.CreateMap<PersonDTO, PersonListViewModel.PersonShortViewModel>().IgnoreAllNonExisting();
            if (predicate == null)
            {
                //Mapper.CreateMap<PersonDTO, PersonListViewModel.PersonShortViewModel>();
                list.Persons =
                    Mapper.Map<IPagedList<PersonListViewModel.PersonShortViewModel>>(
                        DataService.GetPersons()
                                   .OrderBy(person => person.Pib)
                                   .ToPagedList(pageIndex, pagingSettings.PageSize));
            }
            else
            {
                list.Persons =
                    Mapper.Map<IPagedList<PersonListViewModel.PersonShortViewModel>>(
                        DataService.GetPersonsBy(predicate.RemapForType<PersonViewModel, PersonDTO, bool>())
                                   .OrderBy(person => person.Pib)
                                   .ToPagedList(pageIndex, pagingSettings.PageSize));
            }
//...

所以,这还不是全部,但其他代码似乎根本不重要。但如果可以,请向我索取,我也会在这里添加。

并在此处实例化映射器配置:

internal static class Program
{
    /// <summary>
    /// Main point.
    /// </summary>
    [STAThread]
    private static void Main()
    {
        AutoMapperBLLConfiguration.Configure();
        AutoMapperPLConfiguration.Configure();
        // etc.
    }
}

我的主要问题是:为什么AutoMapper看起来像重写配置,因为在实例化它之后我仍然需要Mapper.CreateMap<>在每次操作之前做?以及如何在一个地方为类似实体创建不同的配置?例如,现在它显示此错误:

Missing type map configuration or unsupported mapping.

Mapping types:
Person -> PersonDTO
Reestr.DAL.Entities.Person -> Reestr.BLL.DTO.PersonDTO

Destination path:
IEnumerable`1[0]

Source value:
System.Data.Entity.DynamicProxies.Person_81BD716087EE14CF5E255587795725BC7C06DC2382A1A8EBF33C29A04F551C34

如何在AutoMapper不同层之间分离创建配置(正如您在DataService构造函数中看到的那样)?您能否帮助我了解一些架构逻辑,因为我只是初学者,我希望我的程序成为最佳实践/我正在与这个问题作斗争 3 天......谢谢!

4

1 回答 1

0

在 AutoMapper 中初始化会重置配置。您应该为每个 AppDomain 配置一次 AutoMapper,并在应用启动时调用一次 Initialize。我使用 Profile 实例将配置分开,因此您可以将它们用于 CreateMap 调用。我不知道让层彼此不“了解”意味着什么,但应用程序需要知道要从中加载配置文件的所有程序集。只需调用 Initialize,从您的应用中添加所有配置文件。

或者升级到 AutoMapper 5,那里有一种方法可以从类型的程序集中加载配置文件。

于 2016-10-23T02:41:45.827 回答