前言

和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的一些问题后面陆陆续续会将EF Core中需要注意的地方补充上来,有些是我一直以来比较疏忽的地方,不喜勿喷。用在实际项目中的时候才发现和平时所学有很大差异,靠着项目才能检验出真理。

EntityFramework Core问题集锦

更新单个实体

更新单个实体的方式有两种:

(1)查询出实体进行赋值更新

说的更专业一点则是已被跟踪的实体进行赋值更新,此时实体已被快照,此时进行更新时只需要调用SaveChanges或者SaveChangesAsync,当已赋值属性与快照中值不同时,此时调用SaveChangesAsync或者SaveChanges方法时会将此属性的状态即(IsModified)修改为True,否则为False。代码大概如下:

        public async Task<bool> UpdateStatus(int id, byte status)
{
var blog = _efCoreContext.Blogs.Find(id);
blog.Status = status;
var effectRows = await _efCoreContext.SaveChangesAsync(CancellationToken.None);
if (effectRows > )
{
return true;
}
return false;
}

但是如上又带来一个问题,我们通过影响行数来获取是否更新成功,如果想更新某一列,但是此列的值未进行改变,此时与快照中的值一致,则受影响行数为0,结果返回的更新失败,如下所示:

在这种情况就需要一个扩展方法来显式指定更新属性即使值未发生改变也将其属性状态IsModified修改为True,这样才不会导致值未改变但是更新失败的情况,即如下:

 _efCoreContext.Entry(blog).Property(d => d.Status).IsModified = true;

(2)未查询出实体进行赋值更新。

当此实体未进行查询,此时需要调用Update来将此实体状态中所有属性的IsModified修改为True,此时代码大概如下:

        public async Task<bool> UpdateStatus(Blog blog)
{
_efCoreContext.Blogs.Update(blog);
var effectRows = await _efCoreContext.SaveChangesAsync(CancellationToken.None);
if (effectRows > )
{
return true;
}
return false;
}

也就是说如果是明确实体所有属性都会更改则可以利用Update方法来更新所有属性,否则不需要更新的属性比如常见场景:数据库中表中数据创建时间下次进行更新时是不需要更新,如若调用Update方法,如果对创建时间赋值会进行覆盖,未赋值则会显示DateTime默认时间。

批量更新之表达式树

批量更新的场景大有,在我们项目中选择多个产品将产品的状态更新为下架状态,下面我们来还原场景。创建批量更新接口,此时数据库中数据如下:

Task<bool> UpdateStatus(int[] ids);

我们将Blog中状态中为0的行更新为1,此时接口则如下:

        public async Task<bool> UpdateStatus(int[] ids)
{
var blogs = _efCoreContext.Blogs.Where(d => ids.Contains(d.Id)); blogs.Select(b => new Blog() { Id = b.Id, Status = }).ToList(); if (await _efCoreContext.SaveChangesAsync(CancellationToken.None) > )
{
return true;
}
return false;
}

此时更新肯定不能正确更新,其原因不必多讲,由于是更新集合中的指定属性,此时我写了关于单个和集合更新指定属性的扩展方法,如下:

    public static class EfCoreUpdateExe
{
public static void Update<T>(this EFCoreContext context, T entity, params Expression<Func<T, object>>[] properties) where T : class, new()
{
var dbEntityEntry = context.Entry(entity);
if (properties.Any())
{
foreach (var property in properties)
{
dbEntityEntry.Property(property).IsModified = true;
}
}
else
{
foreach (var rawProperty in dbEntityEntry.Entity.GetType().GetTypeInfo().DeclaredProperties)
{
var originalValue = dbEntityEntry.Property(rawProperty.Name).OriginalValue;
var currentValue = dbEntityEntry.Property(rawProperty.Name).CurrentValue;
foreach (var property in properties)
{
if (originalValue != null && !originalValue.Equals(currentValue))
dbEntityEntry.Property(property).IsModified = true;
} }
}
} public static void UpdateRange<TEntity>(this EFCoreContext context, IEnumerable<TEntity> entities, bool isNoTracking = true, params Expression<Func<TEntity, object>>[] properties) where TEntity : class, new()
{
foreach (var entity in entities)
{
var dbEntityEntry = context.Entry(entity);
//Notice that:当更新实体指定属性时,若实体从数据库中查询而出,此时实体已被跟踪,则无需处理,若实例化对象而更新对象指定属性,此时需要将其状态修改为Unchanged即需要附加
if (!isNoTracking) { dbEntityEntry.State = EntityState.Unchanged; }
if (properties.Any())
{
foreach (var property in properties)
{
dbEntityEntry.Property(property).IsModified = true;
}
}
else
{
foreach (var rawProperty in dbEntityEntry.Entity.GetType().GetTypeInfo().DeclaredProperties)
{
var originalValue = dbEntityEntry.Property(rawProperty.Name).OriginalValue;
var currentValue = dbEntityEntry.Property(rawProperty.Name).CurrentValue;
foreach (var property in properties)
{
if (originalValue != null && !originalValue.Equals(currentValue))
dbEntityEntry.Property(property).IsModified = true;
} }
}
}
}
}

然后代码更新代码修改如下:

        public async Task<bool> UpdateStatus(int[] ids)
{
var blogs = _efCoreContext.Blogs
.Where(d => ids.Contains(d.Id)); var updateProductList = blogs.Select(b => new Blog() { Id = b.Id, Status = }).ToList(); _efCoreContext.UpdateRange(updateProductList, true, d => d.Status); if (await _efCoreContext.SaveChangesAsync(CancellationToken.None) > )
{
return true;
}
return false;
}

此时与数据库会进行两次连接,一次是查询,一次是更新指定属性字段,通过SQL跟踪我们能看到如下语句:

SELECT [d].[Id]
FROM [Blog] AS [d]
WHERE [d].[Id] IN (, , , )
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Blog] SET [Status] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT; UPDATE [Blog] SET [Status] = @p2
WHERE [Id] = @p3;
SELECT @@ROWCOUNT; UPDATE [Blog] SET [Status] = @p4
WHERE [Id] = @p5;
SELECT @@ROWCOUNT; UPDATE [Blog] SET [Status] = @p6
WHERE [Id] = @p7;
SELECT @@ROWCOUNT; ',N'@p1 int,@p0 tinyint,@p3 int,@p2 tinyint,@p5 int,@p4 tinyint,@p7 int,@p6 tinyint',@p1=2,@p0=1,@p3=3,@p2=1,@p5=5,@p4=1,@p7=6,@p6=1

最终正确更新如下:

除了上述通过写反射扩展方法来更新外属性外,一直在想着其中会进行两次数据库链接,进行一次数据库链接比较耗时,这个时候想到的只能执行SQL命令了。

批量更新之SQL命令

利用WHERE ....IN来进行更新,此时SQL更新代码则如下:

        public async Task<bool> UpdateStatus(int[] ids)
{
var testIds = string.Join(",", ids); var effctRow = await _efCoreContext.Database.ExecuteSqlCommandAsync("update dbo.Blog set [Status] = 1 where id in ({0})", CancellationToken.None, testIds);
if (effctRow > )
{
return true;
}
return false;
}

不知道各位看客发现什么没有,上述的代码是有问题的,哪里有问题,不知道的请看如下动态演示。

正常情况下WHERE...IN(2,3,5,6)而非上述“2,3,5,6”,此时我将上述代码修改为如下:

        public async Task<bool> UpdateStatus(int[] ids)
{
var blogIds = string.Empty;
foreach (var id in ids)
{
blogIds += $"{id},";
}
blogIds = blogIds.TrimEnd(',');
var effectRows = await _efCoreContext.Database.ExecuteSqlCommandAsync("update dbo.Blog set [Status] = 1 where id in ({0})", CancellationToken.None, blogIds);
if (effectRows > )
{
return true;
}
return false;
}

此时再来看看演示效果:

此时则报NVARCHAR转换到INT失败,那么粗暴一点将id转换为NVARCHAR:

 var effectRows = await _efCoreContext.Database.ExecuteSqlCommandAsync("update dbo.Blog set [Status] = 1 where cast(id as nvarchar(max)) in ({0})", CancellationToken.None, blogIds);

当然如上涉及到索引,通过函数转换不会走索引,我们正常情况下应该是定义一个变量将id进行转换,然后利用变量来进行包含。此时再来看演示效果:

此时压根都没去更新,我也是醉了,最后我们再来看一种情况,我们写SQL命令通过拼接的形式来进行,如下:

 var effectRows = await _efCoreContext.Database.ExecuteSqlCommandAsync($"update dbo.Blog set [Status] = 1 where id in ({blogIds})", CancellationToken.None);

此时居然更新成功了,其实我们利用上述字符串拼接的方式进行如下两种转换都会更新成功:

             //转换方式一
//var blogIds = string.Join(",", ids); //转换方式二
var blogIds = string.Empty;
foreach (var id in ids)
{
blogIds += $"{id},";
}
blogIds = blogIds.TrimEnd(',');
var effectRows = await _efCoreContext.Database.ExecuteSqlCommandAsync($"update dbo.Blog set [Status] = 1 where id in ({blogIds})", CancellationToken.None);

但是利用$符号本质无非是简化了string.format的书写罢了,容易导致SQL注入的问题,但是利用参数化SQL对于WHERE....IN情况就是无法进行更新,对于删除亦是如此,上述未曾演示利用SqlParameter来进行更新,如果你这样做了,结果依然一样不好使:

            var blogIds = string.Empty;
foreach (var id in ids)
{
blogIds += $"{id},";
}
blogIds = blogIds.TrimEnd(',');
var parameters = new SqlParameter[]
{
new SqlParameter("@ids",System.Data.SqlDbType.NVarChar,){ Value = blogIds }
};
var effectRows = await _efCoreContext.Database.ExecuteSqlCommandAsync("update dbo.Blog set [Status] = 1 where id in (@ids)", CancellationToken.None, parameters);

上述是对于更新的主键为INT的情况,若是主键为字符串,此时这种情况更加突出,因为对于字符串形式需要这样的格式IN('A','B','C'),此时我们将上述id看作为字符串,我们进行如下转换:

var blogIds = "'" + string.Join("','", ids) + "'";

然后去进行更新,参数正确,格式也正确,但是就是无法进行更新。最终统一得出的结论是:

进行批量更新或者删除的情况利用WHERE....IN参数化SQL无法进行更新或者删除,利用$或者string.format进行拼接却好使,但是会导致SQL注入。

上述演示EF Core版本为1.1.2,遇到这样的问题是在进行批量删除时,有人反问了批量删除不是有RemoveRange么,但是其中涉及到多表查询然后进行批量删除,就是期望达到一步到位的效果,最终没有办法,我采用LINQ的方法利用两步来进行批量删除,看到此文的你对于EF Core中利用SQL(WHERE....IN)命令来进行批量删除或者更新的情况见解是怎样,是否有遇到这样的问题,如果利用参数化SQL解决了问题的话望告知。

2017-08-07利用WHERE...IN参数化SQL批量更新或删除

这两天人感冒,什么都不想干,回来太早又没事干,于是乎再次回顾了下这个问题,我天真的以为在ADO.NET中利用WHERE...IN用SQL的方式来进行批量更新呢或者删除是好使的,结果一试居然一样不好使,有人想了为何不利用存储过程解决何必纠结于此,想了想就一句话的事情,没必要还搞个存储过程而且还要打开数据库操作(我懒)。最终还是利用原生的方式来解决这个问题,在WHERE...IN中将IN中的所有需要更新或者需要删除的数据生成参数的方式来解决即可,请往下看。

        public async Task<bool> UpdateStatus(string idsStr)
{
var ids = idsStr.Split(','); var parms = ids.Select((s, i) => "@p" + i.ToString()).ToArray(); var inclause = string.Join(",", parms); var parameters = new SqlParameter[parms.Length]; for (int i = ; i < ids.Length; i++)
{
parameters[i] = new SqlParameter()
{
Value = ids[i],
ParameterName = parms[i],
SqlDbType = SqlDbType.VarChar,
Size =
};
} var effectRows = await _efCoreContext.Database.ExecuteSqlCommandAsync($"UPDATE dbo.Blog SET [Status] = 1 WHERE Id in({inclause})", CancellationToken.None, parameters);
if (effectRows > )
{
return true;
}
return false;
}

接下来进行调用更新:

        [HttpGet("[action]")]
public async Task<IActionResult> Index()
{
var ids = "2,4,6,7";
var result = await _blogRepository.UpdateStatus(ids); return Ok();
}

数据库原始数据如下:

至此成功进行更新,上述代码则无需一一进行解释,简单易懂。为了方便调用,对于利用WHERE...IN利用进行批量更新或删除将其进行如下封装。

第一步:构造WHERE...IN中的参数

        public static string BuildWhereInClause<T>(string partialClause, string paramPrefix, IEnumerable<T> parameters)
{
string[] parameterNames = parameters.Select(
(paramText, paramNumber) => "@" + paramPrefix + paramNumber.ToString())
.ToArray(); string inClause = string.Join(",", parameterNames);
string whereInClause = string.Format(partialClause.Trim(), inClause); return whereInClause;
}

第二步:构造参数化Parameter

        public static SqlParameter[] Parameter<T>(string paramPrefix, IEnumerable<T> parameters)
{
string[] parameterValues = parameters.Select((paramText) => paramText.ToString()).ToArray(); string[] parameterNames = parameterValues.Select(
(paramText, paramNumber) => "@" + paramPrefix + paramNumber.ToString()
).ToArray();
var param = new SqlParameter[parameterNames.Length];
for (int i = ; i < parameterNames.Length; i++)
{
param[i] = new SqlParameter()
{
Value = parameterValues[i],
ParameterName = parameterNames[i],
SqlDbType = SqlDbType.VarChar
};
} return param;
}

最终定义一个静态类来调用如上两个方法:

    public static class SqlWhereInParameterBuilder
{
public static string BuildWhereInClause<T>(string partialClause, string paramPrefix, IEnumerable<T> parameters)
{
string[] parameterNames = parameters.Select(
(paramText, paramNumber) => "@" + paramPrefix + paramNumber.ToString())
.ToArray(); string inClause = string.Join(",", parameterNames);
string whereInClause = string.Format(partialClause.Trim(), inClause); return whereInClause;
} public static SqlParameter[] Parameter<T>(string paramPrefix, IEnumerable<T> parameters)
{
string[] parameterValues = parameters.Select((paramText) => paramText.ToString()).ToArray(); string[] parameterNames = parameterValues.Select(
(paramText, paramNumber) => "@" + paramPrefix + paramNumber.ToString()
).ToArray();
var param = new SqlParameter[parameterNames.Length];
for (int i = ; i < parameterNames.Length; i++)
{
param[i] = new SqlParameter()
{
Value = parameterValues[i],
ParameterName = parameterNames[i],
SqlDbType = SqlDbType.VarChar
};
} return param;
}
}

此时上述调用则进行如下调用:

            var sql = SqlWhereInParameterBuilder.BuildWhereInClause("UPDATE dbo.Blog SET [Status] = 1 WHERE Id in({0})", "Id", ids);
var parameters = SqlWhereInParameterBuilder.Parameter("id", ids);
var effectRows = await _efCoreContext.Database.ExecuteSqlCommandAsync(sql, CancellationToken.None, parameters);

一切从简,想要批量删除或者更新一步到位,你get到没有!遗留一个问题,上述只是针对单表而言,如果是多表,还有其他判断条件的参数,那么上述方法则不再适用,那又该如何改造呢?容我想想!

彩蛋

EntityFramework Core Shadow Property(狭隘属性)

在EF Core系列中介绍过EF Core中几个新特性比如可选键作为除主键外的唯一约束,BackFileds,关于BackFieds未曾用到也差不多忘记了,本节我们介绍一下EF Core漏掉的狭隘属性。

狭隘属性不是实体类的一部分,所以不存在于实体类中但是存在于实体模型中,那么到底该如何使用狭隘属性呢?使用狭隘属性主要在以下两个场景。

(1)当不想对实体类作出更改,但是需要添加一些字段到实体模型中。

(2)明确知道该属性是上下文中的一部分,但是不希望暴露这些属性。

例如在Blog实体类中存在如何字段和导航属性。

    public class Blog : IEntityBase
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public byte Status { get; set; }
public IEnumerable<Post> Posts { get; set; }
}

常见场景:现在我们需要添加一个属性创建时间作为狭隘属性,此创建时间只有在实体添加状态时才有其值,其他状态值不发生改变且无需对外暴露,此时我们在映射中进行配置保持实体类洁净,如下:

        public override void Map(EntityTypeBuilder<Blog> b)
{
b.ToTable("Blog"); b.HasKey(k => k.Id); b.Property(p => p.Url);
b.Property(p => p.Name);
b.Property(p => p.Status).HasColumnType("TINYINT").IsRequired(); b.Property<DateTime>("CreatedTime");
}

那么如何对CreatedTime进行设置值和获取值呢?Change Tracker API负责维护狭隘属性,当我们创建Blog时为其狭隘属性赋值,如下:

        public async Task<bool> Create()
{
var blog = new Blog() { Name = "Jeffcky", Status = , Url = "http://www.cnblogs.com/CreateMyself" }; _efCoreContext.Entry(blog).Property("CreatedTime").CurrentValue = DateTime.Now; if (await _efCoreContext.SaveChangesAsync(CancellationToken.None) > )
{
return true;
}
return false;
}

由于对于大部分情况下都有其创建时间这一列,我们放在SaveChanges方法中并将其重写,如下:

        public override int SaveChanges()
{
var modifiedEntries = ChangeTracker
.Entries().Where(x => x.State == EntityState.Added); foreach (var item in modifiedEntries)
{
item.Property("CreatedTime").CurrentValue = DateTime.Now;
}
return base.SaveChanges();
} public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var modifiedEntries = ChangeTracker
.Entries().Where(x => x.State == EntityState.Added); foreach (var item in modifiedEntries)
{
item.Property("CreatedTime").CurrentValue = DateTime.Now;
}
return await base.SaveChangesAsync();
}

此时创建Blog则改写为如下:

        public async Task<bool> Create()
{
var blog = new Blog() { Name = "Jeffcky", Status = , Url = "http://www.cnblogs.com/CreateMyself" }; _efCoreContext.Add(blog); if (await _efCoreContext.SaveChangesAsync(CancellationToken.None) > )
{
return true;
}
return false;
}

那么问题来了,如果配置的狭隘属性在实体类中已存在那么是否会抛出异常呢?不会,自动将已存在的实体类中同名的名称配置成狭隘属性。当然我们也可以通过如下来起别名:

b.Property<DateTime>("CreatedTime").HasColumnName("CreatedDate");

结论:狭隘属性应是对已存在的实体类添加但是不会去修改狭隘属性值。

那么最后一个问题又来了,在LINQ中如何引用狭隘属性进行查询呢?如下:通过EF.Property<>实现引用狭隘属性:

var cList = _efCoreContext.Blogs
.OrderBy(b => EF.Property<DateTime>(b, "CreatedTime")).ToList();

总结

有一段时间没写博客感觉有点生硬,后面会陆陆续续捡起来并将项目中遇到的问题进行总结,如有疑问或言论不对之处,请指教。see u.

EntityFramework Core问题处理集锦(一)的更多相关文章

  1. EntityFramework Core查询问题集锦(一)

    前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的 ...

  2. EntityFramework Core Raw SQL

    前言 本节我们来讲讲EF Core中的原始查询,目前在项目中对于简单的查询直接通过EF就可以解决,但是涉及到多表查询时为了一步到位就采用了原始查询的方式进行.下面我们一起来看看. EntityFram ...

  3. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  4. 神马玩意,EntityFramework Core 1.1又更新了?走,赶紧去围观

    前言 哦,不搞SQL了么,当然会继续,周末会继续更新,估计写完还得几十篇,但是我会坚持把SQL更新完毕,绝不会烂尾,后续很长一段时间没更新的话,不要想我,那说明我是学习新的技能去了,那就是学习英语,本 ...

  5. Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  6. EntityFramework 7 更名为EntityFramework Core(预发布状态)

    前言 最近很少去学习和探索新的东西,尤其是之前一直比较关注的EF领域,本身不太懒,但是苦于环境比较影响自身的心情,所以迟迟没有下笔,但是不去学习感觉在精神层面缺少点什么,同时也有园友说EF又更新了,要 ...

  7. EntityFramework Core使用PostgreSQL

    EntityFramework Core使用PostgreSQL 0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用 ...

  8. EntityFramework Core解决并发详解

    前言 对过年已经无感,不过还是有很多闲暇时间来学学东西,这一点是极好的,好了,本节我们来讲讲EntityFramewoek Core中的并发问题. 话题(EntityFramework Core并发) ...

  9. EntityFramework Core 1.1有哪些新特性呢?我们需要知道

    前言 在项目中用到EntityFramework Core都是现学现用,及时发现问题及时测试,私下利用休闲时间也会去学习其他未曾遇到过或者用过的特性,本节我们来讲讲在EntityFramework C ...

随机推荐

  1. MFC Bresesnham算法

    Bresesnham算法绘制直线段 Bresenham算法的意义:高效的将图形光栅化.其计算过程中均采用加法运算,故大大减少了程序的开销. 绘制直线段(MFC中) //传入参数:起点.终点,颜色 vo ...

  2. git 入门教程之分支策略

    默认情况下合并分支常常直接使用 git merge 命令,是最方便快速的合并方法.其实这种情况下 git 采用的是 fast forward 模式,特点是删除分支后,会丢失分支信息,好像从来没存在该分 ...

  3. Git多人协作常用命令

    Git多人协作工作模式: 首先,可以试图用git push origin branch-name推送自己的修改. 如果推送失败,则因为远程分支比你的本地更新早,需要先用git pull试图合并. 如果 ...

  4. shell编程—简介(一)

    1.shell概念 shell是一个用C语音编写的程序,他是用户使用Linux的桥梁 shell既是一种命令语音,又是一种程序设计语音 shell是指一种应用程序,这个应用程序提供一个界面,用户通过这 ...

  5. March 07th, 2018 Week 10th Wednesday

    Better later than never. 亡羊补牢,时犹未晚. Time and again all of us are told to complete the tasks assigned ...

  6. Docker: docker container常用命令实战

    容器管理,容器常用选项 选项 描述 -i, –interactive 交互式 -t, –tty 分配一个伪终端 -d, –detach 运行容器到后台 -e, –env 设置环境变量 -p, –pub ...

  7. 聚类——WKFCM

    聚类——认识WKFCM算法 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 参考文献:Shen H, Yang J, Wang S, et al. At ...

  8. 力扣算法题—052N皇后问题2

    跟前面的N皇后问题没区别,还更简单 #include "000库函数.h" //使用回溯法 class Solution { public: int totalNQueens(in ...

  9. webpack模块定义和使用的模式

    在使用webpack作为模块加载工具时,我在想module.exports的模块应该是一种什么模式,最直接地思考是单例.不太确定,所以写一个简单例子做测试. 测试代码 singleton.js: va ...

  10. openPose-注

    静态编译出错:MD能通过 \ https://blog.csdn.net/Silver_sail/article/details/40540887 E:\project\BodyPoseEstimat ...