AutoMapper.Collections,EFCore - 使用短暂的 DbContext
AutoMapper.Collections, EFCore - using short living DbContext
我尝试使用 AutoMapper.Collection.EntityFrameworkCore 来映射我的对象。如果我一直使用相同的 DbContext,则一切正常。
问题是无法拒绝 DbContext 中的所有缓存对象。是的,我进行了搜索并找到了 post,但它不起作用。我真的不明白这个问题,但我敢打赌这是因为我只分离了容器对象。没有复杂的算法就无法遍历所有对象以将它们全部分离。
这是当前可用的代码(非常简单):
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
的 UNIQUE 约束违规。 '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; }
}
这都是用于隔离问题的非常简单的测试代码。
我找到了一个似乎有效的解决方案。步骤如下:
- 加载:
- 创建一个
DbContext
实例
- 加载 DTO 并将其映射到业务逻辑实例
- 拒绝
DbContext
实例
- 修改业务逻辑实例(add/modify/remove children)
- 保存:
- 创建一个
DbContext
实例
- 将业务object对应的DTO按键加载到
DbSet
中。 return 值无关紧要。我们只需要知道这个object.
- 使用
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
实例的寿命很短。它们仅用于加载和保存 objects。业务逻辑 objects 的生命周期对 DbContext
个实例没有任何影响。
这就是我现在要尝试的方法,希望它能解决所有问题。
我尝试使用 AutoMapper.Collection.EntityFrameworkCore 来映射我的对象。如果我一直使用相同的 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);
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
的 UNIQUE 约束违规。 '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; }
}
这都是用于隔离问题的非常简单的测试代码。
我找到了一个似乎有效的解决方案。步骤如下:
- 加载:
- 创建一个
DbContext
实例 - 加载 DTO 并将其映射到业务逻辑实例
- 拒绝
DbContext
实例
- 创建一个
- 修改业务逻辑实例(add/modify/remove children)
- 保存:
- 创建一个
DbContext
实例 - 将业务object对应的DTO按键加载到
DbSet
中。 return 值无关紧要。我们只需要知道这个object. - 使用
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
实例的寿命很短。它们仅用于加载和保存 objects。业务逻辑 objects 的生命周期对 DbContext
个实例没有任何影响。
这就是我现在要尝试的方法,希望它能解决所有问题。