使用 Entity Framework Core 更新记录的更好(更干净)的方法

tmn*_*ngu 5 c# entity-framework-core .net-core

我已经经历过这里提供的一些解决方案,就像这样。基本上我有一个User具有很多属性的类。

public class Users
{
    [Key]
    public Guid Id { get; set; }
    [MaxLength(200)]
    public string Username { get; set; }
    public bool Enabled { get; set; }
    public string Name { get; set; }
    //...Many more properties
}
Run Code Online (Sandbox Code Playgroud)

以及UpdateUser()更新用户的方法。我的问题是该方法很长并且看起来不干净,因为正在更新许多属性。

public async Task<UpdateUserResponse> UpdateUser(UpdateUserRequest userRequest)
{
    var user = await _userRepository.GetByIdAsync(userRequest.Id);

    if (user == null)
        throw new HttpException(HttpStatusCode.BadRequest, $"User does not exist");
    
    user.EmailAddress = userRequest.EmailAddress;
    user.Enabled = userRequest.Enabled;
    user.Name = userRequest.Name;
    user.SurName = userRequest.SurName;
    //...many more properties which make this method to be long
    
    // I attempted to do something like this which gave the error
    // var usrObj = JsonConvert.DeserializeObject<Users>(JsonConvert.SerializeObject(userRequest));
    // var res = await _userRepository.UpdateAsync(usrObj);
        
    context.Entry(user).State = EntityState.Modified;

    var result = await context.SaveChangesAsync();
    //......

    return response;
}
Run Code Online (Sandbox Code Playgroud)

我尝试将userRequest对象反序列化为类型User,但在该UpdateAsync()方法中,Entity Framework Core 抱怨以下消息,这是有道理的:

无法跟踪实体类型“Users”的实例,因为已跟踪具有相同键值 {'Id'} 的另一个实例。

我正在寻找一种更清洁的方法来做到这一点。

Iva*_*oev 12

首先,当使用加载/修改/保存模式时,不要调用

context.Entry(user).State = EntityState.Modified;
Run Code Online (Sandbox Code Playgroud)

或者

context.Update(user);
Run Code Online (Sandbox Code Playgroud)

这些强制将实体的所有属性更新到数据库中,并且适用于当您没有从数据库中新鲜加载(并由上下文更改跟踪器跟踪)实体实例时断开连接的实体场景。

当您拥有它时(与相关代码一样),您所需要的只是设置一些属性,然后 EF Core 更改跟踪器将自动确定要在数据库中更新哪些属性(如果所有属性都可能根本不会发出更新命令)值与原始值相同)。

话虽如此,具体问题是如何在不编写大量代码的情况下设置感兴趣的目标属性。EF Core 提供了开箱即用的EntityEntry.CurrentValues属性和方法SetValues,只要属性名称和类型匹配,它就与实体类实例一起接受字典或任何对象。类似于 AutoMapper 默认映射的东西。但请注意,这仅适用于原始(数据)属性,导航属性需要手动更新。

所以有问题的代码可以简化为

public async Task<UpdateUserResponse> UpdateUser(UpdateUserRequest userRequest)
{
    var user = await _userRepository.GetByIdAsync(userRequest.Id);

    if (user == null)
        throw new HttpException(HttpStatusCode.BadRequest, $"User does not exist");
    
    // Assuming GetByIdAsync method returns tracked entity instances
    // If not, you'd need to modify it or use another one which does that
    // The next call is all you need to update matching properties
    context.Entry(user).CurrentValues.SetValues(userRequest);

    var result = await context.SaveChangesAsync();
    //......

    return response;
}
Run Code Online (Sandbox Code Playgroud)

如果您需要更大的灵活性(具有不匹配的源属性,或者不想应用所有源属性,或者想要处理导航属性等),您最好使用一些第三方库,例如 AutoMapper。