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 天......谢谢!