今天在将一个项目中使用存储过程的遗留代码迁移至新的架构时,遇到了一个问题——如何用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实现数据库中指定字段的更新,这种方法在实际开发中使用完全可行。
于是,又少了一个使用存储过程的理由。
相关推荐
EF批量更新、批量插入、 批量删除 使用的是EFUtilities,免费的。 操作简单,速度超级快
更新全局dotnet-ef工具。 dotnet tool update --global dotnet-ef 数据库设置 使用SQL Server Management Studio连接到SQL Server 最简单的方法是使用随Visual Studio一起安装的LocalDb 。 连接
完整EF实例demo,基于MVC4.0,运行需要更新一下VS,含增删查改可运用到各个企业级项目,解压即用。
EntityFrameworkCore扩展: -批量操作(插入,更新,删除,读取,向上插入,同步,截断)和-批处理操作(删除,更新)。 库轻巧且非常高效,具有所有最常用的CRUD操作。 在Microsoft推荐的前20个被选中。 当前版本...
ASP.NET Core2.1使用EF Core操作MySql数据库,适合新手学习
VS PowerTools ,一个非常好用的VS插件,特别是EF DBfirst中,数据库改变了使用该插件很方便的更新VS代码,避免出错
更新poco,entity framework属性和关系用ef4
EF4.1 Release Notes
以VS2010中MVC2为例,讲解EF简单入门使用。入门级也能看懂。网络资料共享。 更新,减少下载所需积分。
EfCore.GenericEventRunner 该库允许Entity Framework Core(EF Core)的用户向其实体类(即EF Core映射到数据库的类)添加事件。 如果您有因属性更改或诸如接收客户订单之类的事件而触发的业务规则,并且需要先...
最近使用VS2019连接数据库生成模型,生成实体,遇到的坑,找了好多资料在这里做个总结。 一、安装环境 mysql-installer-community-5.6.49.0.msi Visual Studio 2019专业版 mysql-for-visualstudio-1.2.9.msi
报错信息:Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See ...参考资料: ht
自己最近两天刚做的mvc项目linq to model first,包括分页,动态加载菜单栏,登录,记住密码,权限分配(差一步,等后续),非常适合刚入mvc的人借鉴,后续有更新,请下载者留下邮箱,后续会发到你的邮箱,或有什么...
在C/S架构中,客户端的自动更新,含客户端与服务端,开发工具VS2010
C# sqlite 批量更新及性能测试
IDA Pro 7.2.181105,Windows版本。 更新于2019年9月17日
它支持LINQ查询,更改跟踪,更新和架构迁移。 EF Core通过提供程序插件API与SQL Server,Azure SQL数据库,SQLite,Azure Cosmos DB,MySQL,PostgreSQL和其他数据库一起使用。 安装 EF Core在上可用。 安装与您的...
git clone https://github.com/WerterBonfim/efcore-references.gitcd efcore-references/dockerdocker build -t sql-dev .docker container run --rm --name sql -d -p 1433:1433 sql-dev实体框架核心部分简介6-...