I try to use AutoMapper.Collection.EntityFrameworkCore to map my objects. All is working fine if I use the same DbContext all the time.
The problem is that there is no possibility to reject all cached objects in DbContext. Yes I did a search and found this post but it doesn't work. I don't realy understand the problem but I bet it is because I only detach the container object. There is no way without complex algorithms to traverse all objects to detach them all.
This is the currently working code (very simplyfied):
using var ctx = this.DbContextFactory.CreateDbContext();
var dtoProject = await ctx.Set<DtoProject>().Include(item => item.Jobs).FirstAsync();
var p = this.Mapper.Map<Project>(dtoProject);
var j = new Job(Guid.NewGuid().ToString("B").ToUpperInvariant(), $"Job {p.Jobs.Count + 1}");
p.Jobs.Add(j);
await ctx.Set<DtoProject>().Persist(this.Mapper).InsertOrUpdateAsync(p);
await ctx.SaveChangesAsync();
This code reuses ctx
at SaveChangesAsync()
which is working as expected.
But this leads to a DbContext
instance that is very long living because it has to be alive as long as the business objects are in use. This doesn't sound like a real problem but I'm not able to invalidate objects in DbContext
to force a reload if needed.
It seams that the way to go is to have a short living DbContext
instance. Sounds good. I changed the code above so that a separate method is loading the business object and a new context is used to save the changes.
This simplyfied code shows the changes:
using var ctx = this.DbContextFactory.CreateDbContext();
var dtoProject = await ctx.Set<DtoProject>().Include(item => item.Jobs).FirstAsync();
var p = this.Mapper.Map<Project>(dtoProject);
var j = new Job(Guid.NewGuid().ToString("B").ToUpperInvariant(), $"Job {p.Jobs.Count + 1}");
p.Jobs.Add(j);
using var tmpCtx = this.DbContextFactory.CreateDbContext();
await tmpCtx.Set<DtoProject>().Persist(this.Mapper).InsertOrUpdateAsync(p);
await tmpCtx.SaveChangesAsync();
The only change is a new DbContext
called tmpCtx
used to store the changed value.
But this code throws a DbUpdateException
that told me a UNIQUE constraint violation for jobs.id
. The 'container' instance p
seams to be accepted but the contained job instances seam to fail.
How to fix that?
The following code shows the automapper configuration and object declarations:
private IMapper CreateMapper()
{
var mapperCfg = new MapperConfiguration(cfg =>
{
cfg.AddExpressionMapping();
cfg.AddCollectionMappers();
cfg.CreateMap<Job, DtoJob>()
.EqualityComparison((blo, dto) => blo.Id == dto.Id)
.ForMember(dst => dst.ParentId, opt => opt.Ignore())
.ForMember(dst => dst.Parent, opt => opt.Ignore())
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Name));
cfg.CreateMap<DtoJob, Job>()
.EqualityComparison((dto, blo) => dto.Id == blo.Id)
.ForCtorParam("id", opt => opt.MapFrom(src => src.Id))
.ForCtorParam("name", opt => opt.MapFrom(src => src.Name))
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Name))
.ForSourceMember(src => src.ParentId, opt => opt.DoNotValidate())
.ForSourceMember(src => src.Parent, opt => opt.DoNotValidate());
cfg.CreateMap<Project, DtoProject>()
.EqualityComparison((blo, dto) => blo.Id == dto.Id)
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dst => dst.Jobs, opt => opt.MapFrom(src => src.Jobs));
cfg.CreateMap<DtoProject, Project>()
.EqualityComparison((dto, blo) => dto.Id == blo.Id)
.ForCtorParam("id", opt => opt.MapFrom(src => src.Id))
.ForCtorParam("name", opt => opt.MapFrom(src => src.Name))
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dst => dst.Jobs, opt => opt.MapFrom(src => src.Jobs));
});
mapperCfg.AssertConfigurationIsValid();
return mapperCfg.CreateMapper();
}
public class Job
{
public Job(string id, string name)
{
this.Id = id;
this.Name = name;
}
public string Id { get; }
public string Name { get; }
}
public class Project
{
public Project(string id, string name)
{
this.Id = id;
this.Name = name;
}
public string Id { get; }
public string Name { get; }
public List<Job> Jobs { get; set; }
}
[Table("jobs")]
public class DtoJob
{
[Key]
[Column("id")]
public string Id { get; set; }
[Column("parent_id")]
[ForeignKey(nameof(Parent))]
[Required]
public string ParentId { get; set; }
public DtoProject Parent { get; set; }
[Column("name")]
[Required]
public string Name { get; set; }
}
[Table("projects")]
public class DtoProject
{
[Key]
[Column("id")]
[Required]
public string Id { get; set; }
[Column("name")]
[Required]
public string Name { get; set; }
public List<DtoJob> Jobs { get; set; }
}
This all is a very simplyfied test code to isolate the problem.