zoukankan      html  css  js  c++  java
  • 开发笔记:基于EntityFramework.Extended用EF实现指定字段的更新

    今天在将一个项目中使用存储过程的遗留代码迁移至新的架构时,遇到了一个问题——如何用EF实现数据库中指定字段的更新(根据UserId更新Users表中的FaceUrl与AvatarUrl字段)?

    原先调用存储过程的代码:

    public bool UpdateAvatar(Guid userId, string faceUrl, string avatarUrl)
    {
        DbCommand command = _db.GetStoredProcCommand("User_UpdateFaceAvatar");
        _db.AddInParameter(command, "@FaceUrl", DbType.String, faceUrl);
        _db.AddInParameter(command, "@AvatarUrl", DbType.String, avatarUrl);
        _db.AddInParameter(command, "@UserId", userId);
        return _db.ExecuteNonQuery(command) > 0;
    }

    存储过程中所使用的SQL语句:

    UPDATE Users 
    SET FaceUrl=@FaceUrl,AvatarUrl=@AvatarUrl
    WHERE [UserId]=@UserId

    在新的架构中,数据库访问用的是Entity Framework,并且用IUnitOfWork接口进行了封装,Application层对数据库的操作是通过IUnitOfWork接口完成的。

    IUnitOfWork接口是这么定义的:

    public interface IUnitOfWork : IDisposable
    {
        IQueryable<T> Set<T>() where T : class;
    
        T Add<T>(T entity) where T : class;
    
        IEnumerable<T> AddRange<T>(IEnumerable<T> entities) where T : class;
    
        T Attach<T>(T entity) where T : class;
    
        T Remove<T>(T entity) where T : class;
    
        IEnumerable<T> RemoveRange<T>(IEnumerable<T> entities) where T : class;
    
        bool Commit();
    
        Task<bool> CommitAsync();
    }

    如果不给IUnitOfWork添加新的接口方法,可以使用EF的change tracking完成指定字段的更新操作。

    Application.Services中的实现代码: 

    public async Task<bool> UpdateFaceAvatar(Guid userId, string faceUrl, string avatarUrl)
    {
        var user = await _userRepository.GetByUserId(userId);
        user.FaceUrl = faceUrl;
        user.AvatarUrl = avatarUrl;
        return await _unitOfWork.CommitAsync();
    }

    使用ef change tracking的优点是如果属性的值没有被改变,就不会触发数据库更新操作,缺点是每次更新前都要进行1次查询操作。

    而对于这里的更新FaceUrl与AvatarUrl的应用场景,更新前就明确知道数据已经改变,直接更新数据库即可。ef change tracking的更新前查询不仅没有必要,而且增加了额外的开销。

    于是,尝试寻找新的解决方法。

    开始尝试的是EF的DbEntityEntry,未抽象的实现代码:

    public class EfUnitOfWork : DbContext, IUnitOfWork
    {
        public async Task<bool> UpdateAsync(User user) 
        {
            base.Set<User>().Attach(user);            
            base.Entry<User>(user).Property(x => x.FaceUrl).IsModified = true;
            base.Entry<User>(user).Property(x => x.AvatarUrl).IsModified = true;
            return (await base.SaveChangesAsync()) > 0;
        }     
    }

    调用代码:

    await UpdateAsync(new User { UserId = userId, FaceUrl = faceUrl, AvatarUrl = avatarUrl });

    但是基于这个实现,很难抽象出一个好用的接口方法。

    后来突然想到前一段时间在一个项目中用到的EntityFramework.Extended。针对Update操作,它实现了一个优雅的EF扩展,为何不直接用它呢?

    //example of using an IQueryable as the filter for the update
    var users = context.Users.Where(u => u.FirstName == "firstname");
    context.Users.Update(users, u => new User {FirstName = "newfirstname"});

    于是,如获珍宝地基于EntityFramework.Extended进行实现。

    首先在IUnitOfWork中添加一个UpdateAsync()的接口方法:

    public interface IUnitOfWork : IDisposable
    {
        Task<bool> UpdateAsync<T>(IQueryable<T> source, Expression<Func<T, T>> updateExpression) where T : class;
    }

    然后在IUnitOfWork.UpdateAsync()的实现中,调用EntityFramework.Extended的UpdateAsync扩展方法,完成Update操作:

    using EntityFramework.Extensions;
    public class EfUnitOfWork : DbContext, IUnitOfWork
    {  
        async Task<bool> IUnitOfWork.UpdateAsync<T>(IQueryable<T> source, 
            Expression<Func<T, T>> updateExpression)
        {
            return (await source.UpdateAsync<T>(updateExpression)) > 0;
        }     
    }

    随之,Application.Services中的实现代码改为这样:

    public async Task<bool> UpdateFaceAvatar(Guid userId, string faceUrl, string avatarUrl)
    {            
        IQueryable<User> userQuery = _userRepository.GetByUserId(userId);
        return await _unitOfWork.UpdateAsync<User>(
            userQuery,
            x => new User { FaceUrl = faceUrl, AvatarUrl = avatarUrl }
            );
    }

    (注:这里的_userRepository.GetByUserId的返回类型需要改为IQueryable,再一次验证了Repository返回IQueryable的必要性,相关博文:开发笔记:用不用UnitOfWork以及Repository返回什么集合类型

    跑单元测试验证一下。

    单元测试代码:

    [Fact]
    public async Task UpdateFaceAvatar()
    {
        var result = await _userService.UpdateFaceAvatar(userId, faceUrl, avatarUrl);
        Assert.True(result);
    }

    测试结果:

    1 passed, 0 failed, 0 skipped, took 11.38 seconds (xUnit.net 1.9.1 build 1600).

    测试通过!

    用SQL Profiler查看一下数据库中实际执行的SQL语句:

    exec sp_executesql N'UPDATE [dbo].[Users] SET 
    [FaceUrl] = @p__update__0, 
    [AvatarUrl] = @p__update__1 
    FROM [dbo].[Users] AS j0 INNER JOIN (
    SELECT 
        1 AS [C1], 
        [Extent1].[UserId] AS [UserId]
        FROM [dbo].[Users] AS [Extent1]
        WHERE [Extent1].[UserId] = @p__linq__0
    ) AS j1 ON (j0.[UserId] = j1.[UserId])',N'@p__linq__0 uniqueidentifier,@p__update__0 nvarchar(24),@p__update__1 nvarchar(24)',
    @p__linq__0='BD42420B-63CF-DD11-9E4D-001CF0CD104B',@p__update__0=N'20150810180742.png',@p__update__1=N'20150810180743.png'

    就是我们期望的SQL语句。

    最终验证了,添加IUnitOfWork.UpadteAsync()接口,基于EntityFramework.Extended,用EF实现数据库中指定字段的更新,这种方法在实际开发中使用完全可行。

    于是,又少了一个使用存储过程的理由。

  • 相关阅读:
    Some bugs in CE6
    WINCE知识点滴
    多个设备共享同一个硬件中断
    磁盘分区详解
    开发串口驱动程序
    CE BSP开发入门之重要的配置文件
    boost pcre Greta RE2 正则表达式性能测试
    Python GIL
    Oracle Instant Client 安装
    C++ 与 python 整数除法差异
  • 原文地址:https://www.cnblogs.com/dudu/p/4735211.html
Copyright © 2011-2022 走看看