AutoMapper.Collections,EFCore-使用短暂的DbContext

我尝试使用AutoMapper.Collection.EntityFrameworkCore映射对象。如果我一直使用相同的DbContext,那么一切都很好。

问题在于,无法拒绝DbContext中所有缓存的对象。是的,我进行了搜索,发现了this个帖子,但它不起作用。我不太了解这个问题,但我敢打赌,这是因为我仅分离了容器对象。没有复杂的算法就无法遍历所有对象以分离它们。

这是当前有效的代码(非常简单):

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();

此代码重用了ctx上的SaveChangesAsync(),该代码按预期工作。

但是,这导致一个DbContext实例的寿命很长,因为只要使用业务对象,它就必须处于活动状态。这听起来不像是一个真正的问题,但是如果需要,我无法使DbContext中的对象无效以强制重新加载。

它似乎要走的路是拥有一个短暂的DbContext实例。听起来不错。我更改了上面的代码,以便使用单独的方法加载业务对象,并使用新的上下文来保存更改。

此简单代码显示更改:

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();

唯一的更改是一个名为DbContext的新tmpCtx,用于存储更改后的值。

但是此代码引发了一个DbUpdateException,告诉我jobs.id的UNIQUE约束违规。 “容器”实例p的接缝被接受,但是包含的作业实例的接缝失败。

该如何解决?

以下代码显示了自动映射器的配置和对象声明:

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.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.Name))
            .ForMember(dst => dst.Jobs,opt => opt.MapFrom(src => src.Jobs));
        cfg.CreateMap<DtoProject,Project>()
            .EqualityComparison((dto,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; }
}

这是一个非常简单的测试代码,用于隔离问题。

aidou309 回答:AutoMapper.Collections,EFCore-使用短暂的DbContext

我找到了一个可行的解决方案。步骤如下:

  • 加载:
    • 创建一个DbContext实例
    • 加载DTO并将其映射到业务逻辑实例
    • 拒绝DbContext实例
  • 修改业务逻辑实例(添加/修改/删除子级)
  • 保存:
    • 创建一个DbContext实例
    • 通过键将与业务对象相对应的DTO装入DbSet中。返回值无关紧要。我们只需要了解这个对象。
    • 使用InsertOrUpdate保存更改后的业务逻辑实例。

这执行了不需要的加载操作,但我没有找到其他解决方案。

简单的代码是:

var p = (await this.LoadAsync()).FirstOrDefault();
// null handling omitted
var j = new Job(Guid.NewGuid().ToString("B").ToUpperInvariant(),$"Job {p.Jobs.Count + 1}");

p.Jobs.Add(j);
p.Jobs.RemoveAt(0);

await this.SaveAsync(p);

这使用以下方法:

private async Task<IList<Project>> LoadAsync(Expression<Func<Project,bool>> filter = null)
{
    using var ctx = this.DbContextFactory.CreateDbContext();
    IQueryable<DtoProject> query = ctx.Set<DtoProject>().Include(item => item.Jobs);
    if (!(filter is null))
    {
        query = query.Where(this.Mapper.MapExpression<Expression<Func<DtoProject,bool>>>(filter));
    }

    var resultDtos = await query.ToListAsync();
    var result = resultDtos.Select(this.Mapper.Map<Project>).ToList();
    return result;
}

private async Task SaveAsync(Project project)
{
    using var ctx = this.DbContextFactory.CreateDbContext();
    await ctx.Set<DtoProject>().FindAsync(p.Id == project.Id);
    await ctx.Set<DtoProject>().Persist(this.Mapper).InsertOrUpdateAsync(projectToStore);
    await ctx.SaveChangesAsync();
}

此解决方案的主要优点是DbContext实例的寿命很短。它们仅用于加载和保存对象。业务逻辑对象的生存期对DbContext实例没有任何影响。

这就是我现在要尝试的方法,希望它能够解决所有问题。

本文链接:https://www.f2er.com/3013213.html

大家都在问