Entity Framework
とAutoMapper
に基づいて、3層のアプリケーションを作成しています。私はDAL、BLL、プレゼンテーション層を持っています。ここではデータアクセス層のための私のエンティティです:AutoMapperは設定を書き換えますか?
[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();
}
}
私は、プレゼンテーション層は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));
}
}
とプレゼンテーション層のために... AutoMapperのための個別の構成を作成することにしました:
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<>
を行う必要があるため?同様のエンティティに対して異なる設定を1か所で作成するにはどうすればよいですか?例えば。今ではこのエラーを示しています
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
どのように私は(あなたはそれがDataService
コンストラクタでの行をコメントした見たように)AutoMapper
は異なる層betweeen構成を作成分けることができますか? そして私は初心者であり、私のプログラムをベストプラクティスにしたいので、いくつかのアーキテクチャーロジックで私を助けてください/私はこの問題を3日間戦っています...ありがとう!
これは大きなコードです。どこかのサンプルプロジェクトにアクセスできますか? – Kalitsov
初期化を呼び出すたびに、設定を上書きします。一度呼び出すと、関連するすべてのプロファイルが含まれます。レイヤー間を移動するオブジェクトをマップするためのAutomapperでは、両側を意識する必要があります。 – stuartd
@stuartdプレゼンテーション層はビジネス層のロジックについて知る必要はありません。もちろん、私はすべてのオートマトンロジックをプレゼンテーションレイヤーに入れることができますが、間違った方法ではありませんか? – Dmitry