# 模块二 基础巩固 EF Core 更新和迁移

## 2.4.6 EF Core -- 更新 <a href="#id-246efcore-geng-xin" id="id-246efcore-geng-xin"></a>

* 状态
* 自动变更检测
* 不查询删除和更新
* 并发

### 状态 <a href="#zhuang-tai" id="zhuang-tai"></a>

* Entity State
* Property State

#### Entity State <a href="#entity-state" id="entity-state"></a>

* Added 添加
* Unchanged 没有变化
* Modified 已修改
* Deleted 已删除
* Detached 未跟踪

![](https://3083743005-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8gwpNo3eyzHkX0O40HRA%2Fuploads%2Fl6rqoJ1vn3q1QTJ7bYDR%2F207.jpg?alt=media\&token=0e481307-89e5-422c-b908-fd1f7981a267)

#### Property State <a href="#property-state" id="property-state"></a>

* IsModified
* CurrentValue
* OriginValue

### 自动变更检测 <a href="#zi-dong-bian-geng-jian-ce" id="zi-dong-bian-geng-jian-ce"></a>

* 使用自动变更检测完成确定字段的更新
* 使用自动变更检测完成任意字段的更新

#### 使用自动变更检测完成确定字段的更新 <a href="#shi-yong-zi-dong-bian-geng-jian-ce-wan-cheng-que-ding-zi-duan-de-geng-xin" id="shi-yong-zi-dong-bian-geng-jian-ce-wan-cheng-que-ding-zi-duan-de-geng-xin"></a>

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);
}
```

#### 使用自动变更检测完成任意字段的更新 <a href="#shi-yong-zi-dong-bian-geng-jian-ce-wan-cheng-ren-yi-zi-duan-de-geng-xin" id="shi-yong-zi-dong-bian-geng-jian-ce-wan-cheng-ren-yi-zi-duan-de-geng-xin"></a>

```
[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;
}
```

### 不查询删除和更新 <a href="#bu-cha-xun-shan-chu-he-geng-xin" id="bu-cha-xun-shan-chu-he-geng-xin"></a>

删除之前先查询

```
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)
{
    
}
```

### 并发 <a href="#bing-fa" id="bing-fa"></a>

乐观处理：系统认为数据的更新在大多数情况下是不会产生冲突的，只在数据库更新操作提交的时候才对数据作冲突检测（推荐）

悲观处理：根据命名即对数据库进行操作更新时，对操作持悲观保守的态度，认为产生数据冲突的可能性很大，需要先对请求的数据加锁再进行相关操作

在 Entity 中添加行版本号字段

```
/// <summary>
/// 行版本号
/// </summary>
[Timestamp]
public byte[] RowVersion { get; set; }
```

每次对数据进行更新的时候，都会产生最新的版本号，如果更新的时候查询的版本号与之前的版本号不一致，就会报错

在 UpdateAsync 方法中的查询和更新中间如果数据库的行版本号发生了修改，就会报错

ProjectController

```
[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;
```

## 2.4.7 EF Core -- 迁移 <a href="#id-247efcore-qian-yi" id="id-247efcore-qian-yi"></a>

生成 SQL 脚本

```
从空白开始生成sql脚本  
dotnet ef migrations script


生成指定版本到最新版本的sql 
dotnet ef migrations script AddNewTables


从A-B版本生成迁移SQL脚本 
dotnet ef migrations script AddNewTables AddAuditTable
```

## 2.4.8 EF Core -- 其他 <a href="#id-248efcore-qi-ta" id="id-248efcore-qi-ta"></a>

database-first

```
dotnet ef dbcontext scaffold "server=172.0.0.1;port=7306;user=root;password=root123456@;database=lighter" Pomelo.EntityFrameworkCore.MySql -o Models
```

### GitHub源码链接： <a href="#github-yuan-ma-lian-jie" id="github-yuan-ma-lian-jie"></a>

<https://github.com/MingsonZheng/ArchitectTrainingCamp/tree/main/LighterApi>
