【问题标题】:AutoMapper.Collections, EFCore - using short living DbContextAutoMapper.Collections,EFCore - 使用短暂的 DbContext
【发布时间】:2019-11-28 14:31:21
【问题描述】:

我尝试使用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();

此代码在SaveChangesAsync() 处重用ctx,它按预期工作。

但这会导致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();

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

但是这段代码会抛出一个DbUpdateException,告诉我jobs.id 的唯一约束违规。 'container' 实例 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.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; }
}

这都是一个非常简单的测试代码来隔离问题。

【问题讨论】:

    标签: c# entity-framework-core automapper dbcontext


    【解决方案1】:

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

    • 加载:
      • 创建一个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 实例没有任何影响。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-15
      • 2019-12-28
      相关资源
      最近更新 更多