Automapper performance issue only when debugging in Visual Studio - c#

In my WPF application I am using AutoMapper to map from the entities to the domain objects.
For one particular mapping I am seeing a substantial slow down when running the application with debugging (F5) versus without (CTRL + F5).
Without debugging this mapping takes < 1 second, but with debugging it takes ~14 seconds.
The domain objects (simplified for brevity):
public class CompanyModel : UpdateableModel
{
public CompanyModel() : this(true){}
public CompanyModel(bool isNewModel) : base(isNewModel)
{
Projects = new ObservableCollectionEx<ProjectModel>();
Projects.ItemPropertyChanged += (sender, args) => OnPropertyChanged("Projects");
}
public int? Id { get; set;}
public string Name { get; set; }
public string CompanyXref { get; set; }
public bool IsActive { get; set; }
public ObservableCollectionEx<ProjectModel> Projects { get; set;}
public DateTime? DateUpdated { get; set;}
}
public class ProjectModel : UpdateableModel
{
public ProjectModel() : this(true){}
public ProjectModel(bool isNewModel) : base(isNewModel)
{
Tasks = new ObservableCollectionEx<ProjectTaskModel>();
SetPropertyDefault(() => StartDate, DateTime.Today);
SetPropertyDefault(() => EndDate, DateTime.Today);
// ReSharper disable once ExplicitCallerInfoArgument
Tasks.ItemPropertyChanged += (sender, args) => OnPropertyChanged("Tasks");
}
public int? Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string BillingCurrency { get; set; }
public string ExternalXref { get; set; }
public bool IsChargeable { get; set; }
public bool IsActive { get; set; }
public int TimeTypeId { get; set; }
public ObservableCollectionEx<ProjectTaskModel> Tasks { get; set;}
public DateTime? DateUpdated { get; set; }
}
public class ProjectTaskModel : UpdateableModel
{
public ProjectTaskModel() : this(true){}
public ProjectTaskModel(bool isNewModel) : base(isNewModel)
{
SetPropertyDefault(() => StartDate, DateTime.Today);
SetPropertyDefault(() => EndDate, DateTime.Today);
}
public int? Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string ExternalXref { get; set; }
public bool IsActive { get; set; }
public DateTime? DateUpdated { get; set; }
}
The entities:
public class CompanyEntity : BaseEntity
{
public virtual string CompanyName { get; set; }
public virtual string CompanyXref { get; set; }
public virtual bool IsActive { get; set; }
public virtual IList<ProjectEntity> Projects { get; set; }
public virtual DateTime? DateUpdated { get; set; }
}
public class ProjectEntity : BaseEntity
{
public virtual string ProjectName { get; set; }
public virtual string Description { get; set; }
public virtual DateTime StartDate { get; set; }
public virtual DateTime EndDate { get; set; }
public virtual string ExternalXref { get; set; }
public virtual bool IsChargeable { get; set; }
public virtual bool IsActive { get; set; }
public virtual int TimeTypeId { get; set; }
public virtual string CurrencyId { get; set; }
public virtual CompanyEntity ClientCompany { get; set; }
public virtual DateTime? DateUpdated { get; set; }
public virtual IList<ProjectTaskEntity> Tasks { get; set; }
}
public class ProjectTaskEntity : BaseEntity
{
public virtual string ProjectTaskName { get; set; }
public virtual string Description { get; set; }
public virtual DateTime StartDate { get; set; }
public virtual DateTime EndDate { get; set; }
public virtual string ExternalXref { get; set; }
public virtual bool IsActive { get; set; }
public virtual DateTime? DateUpdated { get; set; }
public virtual ProjectEntity Project { get; set; }
}
Finally the mappings, all properties are currently explicitly mapped:
CreateMap<CompanyEntity, CompanyModel>()
.ConstructUsing((CompanyEntity src) => new CompanyModel(false))
.MapMember(dest => dest.Id, src => src.Id)
.MapMember(dest => dest.Name, src => src.CompanyName)
.MapMember(dest => dest.CompanyXref, src => src.CompanyXref)
.MapMember(dest => dest.IsActive, src => src.IsActive)
.MapMember(dest => dest.Projects, src => src.Projects)
.IgnoreMember(dest => dest.State);
CreateMap<ProjectEntity, ProjectModel>()
.ConstructUsing((ProjectEntity src) => new ProjectModel(false))
.MapMember(dest => dest.Id, src => src.Id)
.MapMember(dest => dest.Name, src => src.ProjectName)
.MapMember(dest => dest.Description, src => src.Description)
.MapMember(dest => dest.BillingCurrency, src => src.CurrencyId)
.MapMember(dest => dest.StartDate, src => src.StartDate)
.MapMember(dest => dest.EndDate, src => src.EndDate)
.MapMember(dest => dest.ExternalXref, src => src.ExternalXref)
.MapMember(dest => dest.IsActive, src => src.IsActive)
.MapMember(dest => dest.IsChargeable, src => src.IsChargeable)
.MapMember(dest => dest.Tasks, src => src.Tasks)
.MapMember(dest => dest.TimeTypeId, src => src.TimeTypeId)
.IgnoreMember(dest => dest.State);
CreateMap<ProjectTaskEntity, ProjectTaskModel>()
.ConstructUsing((ProjectTaskEntity src) => new ProjectTaskModel(false))
.MapMember(dest => dest.Id, src => src.Id)
.MapMember(dest => dest.Name, src => src.ProjectTaskName)
.MapMember(dest => dest.Description, src => src.Description)
.MapMember(dest => dest.StartDate, src => src.StartDate)
.MapMember(dest => dest.EndDate, src => src.EndDate)
.MapMember(dest => dest.ExternalXref, src => src.ExternalXref)
.MapMember(dest => dest.IsActive, src => src.IsActive)
.IgnoreMember(dest => dest.State);
When the mapping is performed there are 16 companies, 122 total projects, and each project only has one task currently.
I did try ignoring various properties on the mappings and it seems to be the Tasks collection at the project level that is causing this; with it ignored the mapping takes 2 seconds instead of 14.
Is there something about how Visual Studio hosts the application for debugging that could cause this performance issue?

You could possibly download an alternate CSharp IDE run it in debugging mode there and see if there is a difference.
SharpDev is an opensource IDE that is very nice and supports C# projects.
http://www.icsharpcode.net/OpenSource/SD/Download/
It would at least be interesting and a simple way to try an alternative.
SharpDev also has a code profiler built-in which may indicate what the issue is.
Good luck.

Related

AutoMapperMappingException Strange Behaviour

I've found some strange behaviour regarding AutoMapper when I'm trying to map from this class
public class SkinAnalyzerResult {
public Guid Id { get; set; }
public ICollection<SkinAnalyzerQuestionAnswer> SelectedAnswers { get; set; }
public string Description { get; set; }
public ICollection<Product> RecommendedProducts { get; set; }
}
public class SkinAnalyzerQuestionAnswer {
public Guid Id { get; set; }
public String Answer { get; set; }
public ICollection<SkinAnalyzerResult> Results { get; set; }
public SkinAnalyzerQuestion Question { get; set; }
}
to this class
public class SkinAnalyzerResultDataModel {
[Key] public Guid Id { get; set; }
[Required] public ICollection<SkinAnalyzerResultQuestionAnswerDataModel> SelectedAnswers { get; set; }
public string Description { get; set; }
public ICollection<SkinAnalyzerResultProductDataModel> RecommendedProducts { get; set; }
public SkinAnalyzerDataModel SkinAnalyzer { get; set; }
}
public class SkinAnalyzerResultQuestionAnswerDataModel {
public Guid ResultId { get; set; }
public SkinAnalyzerResultDataModel Result { get; set; }
public Guid QuestionAnswerId { get; set; }
public SkinAnalyzerQuestionAnswerDataModel QuestionAnswer { get; set; }
}
public class SkinAnalyzerQuestionAnswerDataModel {
[Key] public Guid Id { get; set; }
[Required] public String Answer { get; set; }
public SkinAnalyzerQuestionDataModel Question { get; set; }
public ICollection<SkinAnalyzerResultQuestionAnswerDataModel> Results { get; set; }
}
using this config map
cfg.CreateMap<SkinAnalyzerResultDataModel, SkinAnalyzerResult>()
.ForMember(dest => dest.SelectedAnswers,
opt => opt.MapFrom(
src => src.SelectedAnswers.Select(x => x.QuestionAnswer)
))
.ForMember(dest => dest.RecommendedProducts,
opt => opt.MapFrom(
src => src.RecommendedProducts.Select(x => x.Product)
))
.ReverseMap();
When I tried to do that I got this AutoMapperMappingException
as you can see I've specified to map SkinAnalyzerResult.Answers into SkinAnalyzerResultDataModel.SelectedAnswers.QuestionAnswer
is there something that I missed or do something wrong?
cfg.CreateMap<SkinAnalyzerResultDataModel, SkinAnalyzerResult>()
.ForMember(dest => dest.SelectedAnswers,
opt => opt.MapFrom(
src => src.SelectedAnswers.Select(x => x.QuestionAnswer)
))
The type of SelectedAnswers is ICollection<SkinAnalyzerQuestionAnswer> while the type of QuestionAnswer is SkinAnalyzerQuestionAnswerDataModel, so you also need to create mapping between the two models:
CreateMap<SkinAnalyzerQuestionAnswerDataModel, SkinAnalyzerQuestionAnswer>()
.ForMember( //config if necessary)

GetAllIncluding With Optional Relationships ABP

I have an entity with some optional relationships and I'm doing a GetAllIncluding(someProperties) but the navigation properties keeps in null when the GetAll is done.
All relation in the include (Cliente, ClienteFuturo) keeps in null, and always almost one of them has a value on ClienteId or ClienteFuturoId
Here is my GetAll Method:
public override Task<PagedResultDto<SolicitudPrestamoDto>> GetAll(PagedAndSortedRequest input)
{
var lista = new List<SolicitudPrestamo>();
var query = Repository.GetAllIncluding(x => x.ClienteFuturo, x => x.Cliente);
query = CreateFilteredQuery(input);
query = ApplySorting(query, input);
query = FilterHelper<SolicitudPrestamo>.FilerByProperties(input.FilterProperties, query);
lista = query
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
.ToList();
var result = new PagedResultDto<SolicitudPrestamoDto>(query.Count(), ObjectMapper.Map<List<SolicitudPrestamoDto>>(lista));
return Task.FromResult(result);
}
Here is the entity relation configuration:
entidad.HasOne(e => e.Cosolicitante)
.WithMany()
.HasForeignKey(e => e.CosolicitanteId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_Cosolicitante")
.OnDelete(DeleteBehavior.Restrict);
entidad.HasOne(e => e.Cliente)
.WithMany()
.HasForeignKey(e => e.ClienteId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_Cliente")
.OnDelete(DeleteBehavior.Restrict);
entidad.HasOne(e => e.CosolicitanteCliente)
.WithMany()
.HasForeignKey(e => e.CosolicitanteClienteId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_CosolicitanteCliente")
.OnDelete(DeleteBehavior.Restrict);
entidad.HasOne(e => e.ClienteFuturo)
.WithMany()
.HasForeignKey(e => e.ClienteFuturoId)
.HasConstraintName("ForeignKey_SolicitudPrestamo_ClienteFuturo")
.OnDelete(DeleteBehavior.Restrict);
Here is my entity:
public class SolicitudPrestamo : AuditedEntity<int>
{
public string Identificador { get; set; }
public int CantidadCuotas { get; set; }
public double Monto { get; set; }
public string FormaPago { get; set; }
public DateTime Fecha { get; set; }
public string Proposito { get; set; }
public string Referencia { get; set; }
public EstadoSolicitud Estado { get; set; }
public int SucursalId { get; set; }
public virtual Sucursal Sucursal { get; set; }
public int? ClienteId { get; set; }
public virtual Cliente Cliente { get; set; }
public int? CosolicitanteClienteId { get; set; }
public virtual Cliente CosolicitanteCliente { get; set; }
public int? ClienteFuturoId { get; set; }
public virtual ClienteFuturo ClienteFuturo { get; set; }
public int ClasificacionPrestamoId { get; set; }
public virtual ClasificacionPrestamo ClasificacionPrestamo { get; set; }
public int? OficialNegocioId { get; set; }
public virtual OficialNegocio OficialNegocio { get; set; }
public int? CobradorPrestamoId { get; set; }
public virtual CobradorPrestamo CobradorPrestamo { get; set; }
public int? CosolicitanteId { get; set; }
public virtual ClienteFuturo Cosolicitante { get; set; }
public IEnumerable<GarantiaPrestamoSolicitud> ListaGarantiaPrestamo { get; set; }
public IEnumerable<ReferenciaPrestamo> ListaReferencias { get; set; }
public List<GarantiaPrestamo> ListaGarantias { get; set; }
}
Sorry for my English.
protected override IQueryable<SolicitudPrestamo> CreateFilteredQuery(PagedAndSortedRequest input)
{
return Repository.GetAll().
WhereIf(!input.Filter.IsNullOrWhiteSpace(), x =>
x.Identificador.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase) ||
x.FormaPago.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase) ||
x.Proposito.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase) ||
x.Referencia.StartsWith(input.Filter, StringComparison.CurrentCultureIgnoreCase)
);
}
Thanks illia-popov the problem is that in the CreatedFilteredQuery Method I forget to do the GetAllIncluding
Thanks for help.!

Violation of PRIMARY KEY constraint, even though im not adding or modyfying anything in the context

My program is parsing an xml file and adding orders from this file to the mssql database. Before he adds those orders to database he analyses them if there are any duplicates that need to be dealt with.
foreach (var newOrderXml in newOrdersList)
{
var dupesInDb = _context.OrdersXml.Include(o=>o.OrderXmlItems)
.Where(o => o.OrX_ORDERNR.Contains(newOrderXml.OrX_ORDERNR))
.ToList();
_context.SaveChanges();
}
Program loops through all of the new orders in newOrderList and gets the list of duplicates with linq query. If there are 0 duplicates and nothing gets returned, everything works fine, but if a single duplicate is returned then SaveChanges method will throw an exception "Violation of PRIMARY KEY constraint PK_dbo.SewingCardBundles, Cannot insert duplicate key in object 'dbo.SewingCardBundles'. The duplicate key value is (1).", even though im not adding or modyfying anything in the context. I dont really know what is happening, all im doing is getting, im not changing anything, im not creating new objects. This exception happens exactly at this spot, if i try to save changes before this linq query then nothing bad happens but if i try it after this linq query i get the exceptions. So where does those changes to context come from?
Here are my models:
public class OrderXml
{
public OrderXml()
{
OrderXmlItems = new List<OrderXmlItem>();
}
public int OrX_Id { get; set; }
public string OrX_ORDERNR { get; set; }
public string OrX_REFERGB { get; set; }
public int? OrX_CUSTOMERNUM { get; set; }
public string OrX_DNAME { get; set; }
public string OrX_DADR { get; set; }
public string OrX_DPCODE { get; set; }
public string OrX_POSTALCODE { get; set; }
public string OrX_COUNTRY { get; set; }
public string OrX_PHONE { get; set; }
public string OrX_EMAIL { get; set; }
public int? OrX_LANG { get; set; }
public int? OrX_CUSTGRP { get; set; }
public int? OrX_QUALITCON { get; set; }
public string OrX_SHIPVIA { get; set; }
public string OrX_DATE1 { get; set; }
public string OrX_DATE2 { get; set; }
public string OrX_DELIVGB { get; set; }
public string OrX_SORT { get; set; }
public int? OrX_CURLAB { get; set; }
public List<OrderXmlItem> OrderXmlItems { get; set; }
public Adress Adress { get; set; }
}
public OrderXmlItem()
{
SewingCardBundle = new SewingCardBundle();
}
public int OxI_Id { get; set; }
public int? OxI_PRODUCT { get; set; }
public int? OxI_ORDERLINE { get; set; }
public int? OxI_QUANTITY { get; set; }
public int? OxI_TYPE { get; set; }
public string OxI_TPFABNR { get; set; }
public string OxI_TPFABDEF { get; set; }
public string OxI_TPFABNAME { get; set; }
public int? OxI_CURDIR { get; set; }
public int? OxI_CURWIDTH { get; set; }
public int? OxI_CURHEIGHT { get; set; }
public int? OxI_WORKMETH { get; set; }
public int? OxI_FOLDTYPE { get; set; }
public decimal? OxI_FOLDFACT { get; set; }
public int? OxI_CURBAND { get; set; }
public int? OxI_CURHEAD { get; set; }
public int? OxI_CURBOTSEAM { get; set; }
public int? OxI_PACKWLEFT { get; set; }
public int? OxI_PACKWRIGHT { get; set; }
public decimal? OxI_NRSTROL { get; set; }
public decimal? OxI_NRSTROR { get; set; }
public int? OxI_LINTYP { get; set; }
public string OxI_LINCOL { get; set; }
public int? OxI_EMBSORT { get; set; }
public int? OxI_EXTRA { get; set; }
public int? OxI_PRODUCE { get; set; }
public int? OxI_PACKSORT { get; set; }
public int? OxI_CURMODEL { get; set; }
public string OxI_BARCODE { get; set; }
public string OxI_EXTRAINF { get; set; }
public int? OxI_RAILTYP { get; set; }
public int? OxI_RAILCONT { get; set; }
public int? OxI_RAILCONTSIDE { get; set; }
public decimal? OxI_FABSTROTOT { get; set; }
public decimal? OxI_FABSTROLEFT { get; set; }
public decimal? OxI_FABSTRORIGHT { get; set; }
public int? OxI_FABUNDSIZ { get; set; }
public int? OxI_FABTOTSIZ { get; set; }
public int? OxI_LINSTROTOT { get; set; }
public int? OxI_LINUNDSIZ { get; set; }
public int? OxI_LINTOTSIZ { get; set; }
public decimal? OxI_FABWIDTH { get; set; }
public int? OxI_CHILDSFT { get; set; }
public int? OxI_FOLDSORT { get; set; }
public int? OxI_EMBLENGTH { get; set; }
public int? OxI_PACKMETH { get; set; }
public int OrderXmlId { get; set; }
public OrderXml OrderXml { get; set; }
public SewingCardBundle SewingCardBundle { get; set; }
}
public class SewingCardBundle
{
public SewingCardBundle()
{
FlamanSewingCards = new List<FlamandzkaSewingCard>();
FlamandzkaBrytaSewingCards = new List<FlamandzkaBrytaSewingCard>();
OczkaSewingCards = new List<OczkaSewingCard>();
OczkaBrytaSewingCards = new List<OczkaBrytaSewingCard>();
WellenbandSewingCards = new List<WellenbandSewingCard>();
WellenbandBrytaSewingCards = new List<WellenbandBrytaSewingCard>();
PodwiazkaSewingCards = new List<PodwiazkaSewingCard>();
TunelSewingCards = new List<TunelSewingCard>();
}
public int SwC_Id { get; set; }
public OrderXmlItem OrderXmlItem { get; set; }
public List<FlamandzkaSewingCard> FlamanSewingCards { get; set; }
public List<FlamandzkaBrytaSewingCard> FlamandzkaBrytaSewingCards { get; set; }
public List<OczkaSewingCard> OczkaSewingCards { get; set; }
public List<OczkaBrytaSewingCard> OczkaBrytaSewingCards { get; set; }
public List<WellenbandSewingCard> WellenbandSewingCards { get; set; }
public List<WellenbandBrytaSewingCard> WellenbandBrytaSewingCards { get; set; }
public List<PodwiazkaSewingCard> PodwiazkaSewingCards { get; set; }
public List<TunelSewingCard> TunelSewingCards { get; set; }
}
and my Fluent API configurations for those models:
public class OrderXmlConfiguration : EntityTypeConfiguration<OrderXml>
{
public OrderXmlConfiguration()
{
HasKey(o => o.OrX_Id);
Property(o => o.OrX_ORDERNR).IsRequired();
Property(o => o.OrX_REFERGB).IsRequired();
Property(o => o.OrX_CUSTOMERNUM).IsRequired();
Property(o => o.OrX_DNAME).IsRequired();
Property(o => o.OrX_DPCODE).IsRequired();
Property(o => o.OrX_POSTALCODE).IsRequired();
Property(o => o.OrX_COUNTRY).IsRequired();
Property(o => o.OrX_LANG).IsRequired();
Property(o => o.OrX_CUSTGRP).IsRequired();
Property(o => o.OrX_SHIPVIA).IsRequired();
Property(o => o.OrX_CURLAB).IsRequired();
HasMany(i => i.OrderXmlItems)
.WithRequired(o => o.OrderXml)
.HasForeignKey(o => o.OrderXmlId)
.WillCascadeOnDelete(true);
}
}
public class OrderXmlItemConfiguration : EntityTypeConfiguration<OrderXmlItem>
{
public OrderXmlItemConfiguration()
{
HasKey(o => o.OxI_Id);
Property(p => p.OxI_Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(p => p.OxI_PRODUCT).IsRequired();
Property(p => p.OxI_ORDERLINE).IsRequired();
Property(p => p.OxI_QUANTITY).IsRequired();
Property(p => p.OxI_TYPE).IsRequired();
Property(p => p.OxI_CURDIR).IsRequired();
Property(p => p.OxI_CURWIDTH).IsRequired();
Property(p => p.OxI_CURHEIGHT).IsRequired();
Property(p => p.OxI_WORKMETH).IsRequired();
Property(p => p.OxI_FOLDTYPE).IsRequired();
Property(p => p.OxI_FOLDFACT).IsRequired();
Property(p => p.OxI_PACKWLEFT).IsRequired();
Property(p => p.OxI_PACKWRIGHT).IsRequired();
Property(p => p.OxI_BARCODE).IsRequired();
HasRequired(i => i.SewingCardBundle)
.WithRequiredPrincipal( s=> s.OrderXmlItem)
.WillCascadeOnDelete(true);
}
}
public class SewingCardBundleConfiguration : EntityTypeConfiguration<SewingCardBundle>
{
public SewingCardBundleConfiguration()
{
HasKey(s => s.SwC_Id);
HasMany(s=>s.FlamanSewingCards)
.WithRequired(c=>c.SewingCardBundle)
.WillCascadeOnDelete(true);
HasMany(s => s.FlamandzkaBrytaSewingCards)
.WithRequired(c => c.SewingCardBundle)
.WillCascadeOnDelete(true);
HasMany(s => s.OczkaBrytaSewingCards)
.WithRequired(c => c.SewingCardBundle)
.WillCascadeOnDelete(true);
HasMany(s => s.OczkaSewingCards)
.WithRequired(c => c.SewingCardBundle)
.WillCascadeOnDelete(true);
HasMany(s => s.WellenbandSewingCards)
.WithRequired(c => c.SewingCardBundle)
.WillCascadeOnDelete(true);
HasMany(s => s.WellenbandBrytaSewingCards)
.WithRequired(c => c.SewingCardBundle)
.WillCascadeOnDelete(true);
HasMany(s => s.TunelSewingCards)
.WithRequired(c => c.SewingCardBundle)
.WillCascadeOnDelete(true);
HasMany(s => s.PodwiazkaSewingCards)
.WithRequired(c => c.SewingCardBundle)
.WillCascadeOnDelete(true);
}
}
I'm not sure why you are calling SaveChanges in the first place (it is not needed), but once you get your data from database, context will track them (i.e. have them cached).
Since, you haven't specified AsNoTracking in your query, SaveChanges method will try to save entities which are being tracked which will lead to your "primary key violation" exception.
To circumvent the issue, you can just specify AsNoTracking:
var dupesInDb = _context
.OrdersXml.Include(o=>o.OrderXmlItems)
.Where(o => o.OrX_ORDERNR.Contains(newOrderXml.OrX_ORDERNR))
.AsNoTracking()
.ToList();

Mapping to nested value Automapper

I'm struggling to map 2 objects. Basically have Product which is my EF model, and I'm mapping this to ProductDto, which has FileDto.
I'd like to map Product.FileName to ProductDto.File.Internal name, how to do this?
Classes below.
public class Product : BaseEntity<long>
{
[MaxLength(100)]
public string Name { get; set; }
[MaxLength(100)]
public string Barcode { get; set; }
public int ShelfLife { get; set; }
public int Weight { get; set; }
public bool HasAllergens { get; set; }
[MaxLength(100)]
public string FileName { get; set; }
[ForeignKey("Id")]
public int CustomerId { get; set; }
public virtual ICollection<ProductIngredient> ProductIngredient { get; set; }
public virtual ICollection<Nutrition> Nutritions { get; set; }
public virtual ICollection<ProductComposition> Composition { get; set; }
public virtual IList<ProductionProcess> ProductionProcess { get; set; }
}
public class ProductDto
{
public long Id { get; set; }
public DateTime CretedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
public int ShelfLife { get; set; }
public int Weight { get; set; }
public bool HasAllergens { get; set; }
public int CustomerId { get; set; }
public FileDto File { get; set; }
public IList<IngredientDto> Ingredients { get; set; }
public IList<NutritionDto> Nutritions { get; set; }
public IList<ProductCompositionDto> Composition { get; set; }
public IList<ProductionProcessDto> ProductionProcess { get; set; }
}
public class ProductionProcessDto
{
public string Key { get; set; }
public string Value { get; set; }
public FileDto File { get; set; }
}
public class NutritionDto
{
public string Key { get; set; }
public string Value { get; set; }
}
public class ProductCompositionDto
{
public string Key { get; set; }
public string Value { get; set; }
}
File Dto:
public class FileDto
{
public string Base64EncodedFile { get; set; }
public string OriginalName { get; set; }
public string InternalName { get; set; }
public string Type { get; set; }
}
Automapper so far:
//Product
CreateMap<Nutrition, NutritionDto>().ReverseMap();
CreateMap<ProductComposition, ProductCompositionDto>().ReverseMap();
CreateMap<ProductionProcessDto, ProductionProcess>()
.ForMember(dest => dest.FileInternalName, opt => opt.MapFrom(src => src.File.InternalName))
.ForMember(dest => dest.FileOriginalName, opt => opt.MapFrom(src => src.File.OriginalName))
.ReverseMap();
CreateMap<Product, ProductDto>()
.ForMember(d => d.File, o => o.MapFrom(s => Mapper.Map<Product, FileDto>(s)))
.ForMember(d => d.Nutritions, o => o.MapFrom(s => s.Nutritions))
.ForMember(d => d.Composition, o => o.MapFrom(s => s.Composition))
.ForMember(d => d.ProductionProcess, o => o.MapFrom(s => s.ProductionProcess))
.ForMember(d => d.Ingredients, o => o.MapFrom(s => s.ProductIngredient.Select(pi => pi.Ingredients)))
.ReverseMap();
CreateMap<ProductDto, Product>()
.ForMember(d => d.FileName, o => o.MapFrom(s => s.File.InternalName))
.ReverseMap();
I am able to map from ProductDto (on data post) to Product but not other way around, all help much appreciated
Thanks
This code solved my issue:
.ForMember(d => d.File, o => o.MapFrom(model => new FileDto { InternalName = model.FileName }))
Applied to:
CreateMap<Product, ProductDto>()

C# AutoMapper with Entity Framework - Works with List but not single nullable instance

I have a very wierd error that I can't get my head around. I'm using AutoMapper 6 with AutoMapper.Collection and AutoMapper.Collection.EntityFramework.
https://github.com/AutoMapper/AutoMapper.Collection
As you can see from the screenshot below, every component is updated apart from Image that is null for updatedContact. If I however do an explicit mapping for only updatedImage it works. It also works to update a collection of images without a problem. Has anyone experienced this? Other single properties works as well but for some reason Image is causing trouble.
//Works
var updatedArticle = Mapper.Map<ArticleViewModel, Article>(articleVm, articleOriginal);
//Every component is updated a part from Image.
var updatedContact = Mapper.Map<ContactViewModel, Contact>(contactVm, contactOriginal);
//Works
var updatedImage = Mapper.Map<ImageViewModel, Image>(contactVm.Image);
//Works
var newContact = Mapper.Map<ContactViewModel, Contact>(contactVm);
Mapping:
cfg.CreateMap<ArticleViewModel, Article>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id);
cfg.CreateMap<ImageViewModel, Image>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForSourceMember(x => x.IsDeleted, opt => opt.Ignore())
.ForMember(dest => dest.ImageBytes, opt => opt.MapFrom(src => Encoding.ASCII.GetBytes(src.Image)));
cfg.CreateMap<ContactViewModel, Contact>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForSourceMember(x => x.IsDeleted, opt => opt.Ignore())
.ForSourceMember(x => x.FullName, opt => opt.Ignore());
Files:
public class ArticleViewModel
{
public int Id { get; set; }
...
public List<ImageViewModel> Images { get; set; }
}
public class Article : IEntity<int>
{
public int Id { get; set; }
...
public virtual ICollection<Image> Images { get; set; }
}
public class ContactViewModel
{
public int Id { get; set; }
...
public ImageViewModel Image { get; set; }
}
public class Contact: IEntity<int>
{
[Key]
public int Id { get; set; }
...
public int? ImageId { get; set; }
public Image Image { get; set; }
}
public class ImageViewModel
{
public int Id { get; set; }
public string Image { get; set; }
public string ImageType { get; set; }
public bool IsDeleted { get; set; }
}
public class Image : IEntity<int>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public byte[] ImageBytes { get; set; }
public string ImageType { get; set; }
public int? ArticleId { get; set; }
public virtual Article Article { get; set; }
}
Finally solved it, I had forgot to mark Image as virtual in Contact. After doing that everything started working out of the box.
public virtual Image Image { get; set; }
I think you need to tell your contact mapper in the config to explicitly use the mapping for the image vm. There might be one or two typos as I'm doing this from memory but it should be similar to:
.ForMember(x => x.Image, opt => opt.MapFrom(contact => Mapper.Map<ImageViewModel, Image>(contact.ImageVm);))

Categories