2.4.6 EF Core更新与迁移
一、EF Core更新操作
1.1Entity State
- Added 添加
- Unchanged 没有变化
- Modified 已修改
- Deleted 已删除
- Detached 未跟踪
1.2 Property State
- IsModified
- CurrentValue
- OriginValue
1.3自动变更检测
- 使用自动变更检测完成确定字段的更新
- 使用自动变更检测完成任意字段的更新
1.4使用自动变更检测完成确定字段的更新
ProjectController
[HttpPatch] [Route("{id}")] public async Task<ActionResult<Project>> SetTitleAsync(string id, [FromQuery] string title, CancellationToken cancellationToken) { // 查询实体信息 var origin = await _lighterDbContext.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken); // 修改实体属性 origin.Title = title; // 数据提交保存 await _lighterDbContext.SaveChangesAsync(); return origin; }
修改分组信息
// 查询实体信息 var originGroup = await _lighterDbContext.ProjectGroups.Where(g => g.ProjectId == id).ToListAsync(cancellationToken: cancellationToken); // 修改实体属性 foreach (var group in originGroup) { group.Name = $"{title} - {group.Name}"; }
查询项目信息时带出分组信息
[HttpGet] public async Task<IEnumerable<Project>> GetListAsync(CancellationToken cancellationToken) { return await _lighterDbContext.Projects.Include(p => p.Groups).ToListAsync(cancellationToken); }
1.5使用自动变更检测完成任意字段的更新
[HttpPatch] [Route("{id}")] public async Task<ActionResult<Project>> SetAsync(string id, CancellationToken cancellationToken) { // 查询实体信息 var origin = await _lighterDbContext.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken); var properties = _lighterDbContext.Entry(origin).Properties.ToList(); // 修改实体属性 foreach (var query in HttpContext.Request.Query) { var property = properties.FirstOrDefault(p => p.Metadata.Name == query.Key); if (property == null) continue; var currentValue = Convert.ChangeType(query.Value.First(), property.Metadata.ClrType); _lighterDbContext.Entry(origin).Property(query.Key).CurrentValue = currentValue; _lighterDbContext.Entry(origin).Property(query.Key).IsModified = true; } // 数据提交保存 await _lighterDbContext.SaveChangesAsync(cancellationToken); return origin; }
1.6不查询删除和更新
删除之前先查询
var id = 1; using(var db = new entityContext()) { var entity = db.dbset.FirstOrDefault(e=>e.ID == id); if(entity != null) { db.dbset.Remove(entity); db.SaveChanges(); } }
不查询删除
var id = 1; using(var db = new entityContext()) { var entity = new Entity{ID = id}; db.dbset.Attach(entity); db.dbset.Remove(entity); db.SaveChanges(); }
不查询更新
try { using(var db = new dbContext()) { var entity = new myEntity{PageID = pageid}; db.Pages.Attach(entity);// added entity.Title = "new title";// modified, isModified=true entity.Url = "new-url"; db.SaveChanges(); } } catch(DataException) { }
二、EF Core并发
2.1乐观处理:系统认为数据的更新在大多数情况下是不会产生冲突的,只在数据库更新操作提交的时候才对数据作冲突检测(推荐)
2.2悲观处理:根据命名即对数据库进行操作更新时,对操作持悲观保守的态度,认为产生数据冲突的可能性很大,需要先对请求的数据加锁再进行相关操作
在 Entity 中添加行版本号字段
/// <summary> /// 行版本号 /// </summary> [Timestamp] public byte[] RowVersion { get; set; }
每次对数据进行更新的时候,都会产生最新的版本号,如果更新的时候查询的版本号与之前的版本号不一致,就会报错
在 UpdateAsync 方法中的查询和更新中间如果数据库的行版本号发生了修改,就会报错
[HttpPut] [Route("{id")] public async Task<ActionResult<Project>> UpdateAsync(string id, [FromBody] Project project, CancellationToken cancellationToken) { var origin = await _lighterDbContext.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken); if (origin == null) return NotFound(); _lighterDbContext.Entry(origin).CurrentValues.SetValues(project); await _lighterDbContext.SaveChangesAsync(cancellationToken); return origin; }
通过客户端传入行版本号,解决前端浏览器数据覆盖问题
_lighterDbContext.Entry(origin).Property(p => p.RowVersion).OriginalValue = project.RowVersion;
三、EF Core 迁移脚本
3.1生成 SQL 脚本
从空白开始生成sql脚本 dotnet ef migrations script 生成指定版本到最新版本的sql dotnet ef migrations script AddNewTables 从A-B版本生成迁移SQL脚本 dotnet ef migrations script AddNewTables AddAuditTable
3.2database-first
dotnet ef dbcontext scaffold "server=172.0.0.1;port=7306;user=root;password=root123456@;database=lighter" Pomelo.EntityFrameworkCore.MySql -o Models