数据操作CRUD,我们只说Update,因为在EF中Update有点复杂 后面我们说批量数据更新
Update操作
上下文没有提供Update方法,所以我们要更新操作一般都是将数据查询出来,修改实体属性的值,然后SaveChanges()就OK了
眼熟一下平时的Update
// 一般的修改
var pro = ctx.Products.FirstOrDefault();
Console.WriteLine(JsonConvert.SerializeObject(pro));
Console.WriteLine(ctx.Entry(pro).State); // Unchanged
//{"Order":null,"Name":"牙刷","Price":14.00,"Unit":"只","FK_Order_Id":"82903023-a7a6-4839-9caa-153ee9d00e65","Id":"1b25351c-3008-4d27-a9de-6749ec1d0845","AddTime":"2019-01-15T10:35:03.947"}
pro.Name = "牙刷2";
Console.WriteLine(ctx.Entry(pro).State); // Modified
var res = ctx.SaveChanges();
Console.WriteLine(res); // result:1
查询出来没做修改的实体,状态为Unchange,修改了属性值,状态变为Modified
现在要是我凭空new一个Product对象,id设置为数据库中某一个产品的Id,然后让上下文对这个新对象追踪,最后再修改实体状态为Modified,看看能不能修改
// 数据库中存在的某一条数据的Id
string id = "b0465c73-a7ab-4135-9bf8-4ec85ac6b1e2";
Product p = new Product
{
Id = id,
Name = "娃哈哈",
AddTime = DateTime.Now,
Price = 3,
Unit = "瓶"
};
Console.WriteLine(ctx.Entry(p).State); // Detached
ctx.Products.Attach(p); // 这里报错
失败,在我对它进行追踪时就报错了。因为不能跟踪多个相同键的实体,就和数据库中主键重复冲突一样
System.InvalidOperationException: Attaching an entity of type '_2019011402.Entity.Product' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
解决办法就是,去掉一个追踪,那么我们把该条数据查询出来,对它取消追踪,这个再跟踪这个新对象就可以了
// 全部属性更新
var pro = ctx.Products.AsNoTracking().FirstOrDefault(x => x.Id.Contains(id));
Console.WriteLine(ctx.Entry(pro).State); // Detached
Product p = new Product
{
Id = id,
Name = "娃哈哈",
AddTime = DateTime.Now,
Price = 3,
Unit = "瓶",
FK_Order_Id = "469b82be-8139-4e67-b566-5b2b5f6d838d"
};
ctx.Products.Attach(p);
ctx.Entry(p).State = System.Data.Entity.EntityState.Modified;
var res = ctx.SaveChanges();
Console.WriteLine(res); // result:1 ok
然后我贴一段代码,和主题没什么关系
//我来声明两个变量查询数据库中的同一条记录,引用相等
var pro2 = ctx.Products.FirstOrDefault(x => x.Id.Contains(id));
var pro3 = ctx.Products.FirstOrDefault(x => x.Id.Contains(id));
Console.WriteLine("pro2_state:{0}", ctx.Entry(pro2).State); // Unchanged
Console.WriteLine("pro3_state:{0}", ctx.Entry(pro3).State); // Unchanged
Console.WriteLine(ReferenceEquals(pro2, pro3)); // True
Test t1 = new Test { Id = "id1", Name = "Test1" };
Test t2 = new Test { Id = "id1", Name = "Test1" };
Console.WriteLine(ReferenceEquals(t1, t2)); // False
接着看Update操作,如果我们只更新一个实体的部分属性呢?
// 部分属性更新,少了name,unit
var pro = ctx.Products.AsNoTracking().FirstOrDefault(x => x.Id.Contains(id));
Console.WriteLine(ctx.Entry(pro).State); // Detached
Product p = new Product
{
Id = id,
AddTime = DateTime.Now,
Price = 3,
FK_Order_Id = "469b82be-8139-4e67-b566-5b2b5f6d838d"
};
ctx.Products.Attach(p);
ctx.Entry(p).State = System.Data.Entity.EntityState.Modified;
var res = ctx.SaveChanges();
Console.WriteLine(res); // result:1 ok
看看数据库中的情况
上面的情况说明Modified是全部更新,不能部分更新
部分更新
那么怎么部分更新?书中给了两种办法
1.手动指定更新属性
ctx.Entry(p).Property(x => x.Name).IsModified = true;
ctx.Entry(p).Property(x => x.Unit).IsModified = true;
2.用Entry().CurrentValues.SetValues() 方法
// 使用 Entry()..CurrentValues.SetValues() 方式
var pro = ctx.Products.FirstOrDefault(x => x.Id.Contains(id));
Product p = new Product
{
Id = id,
Name = "盆子2",
//AddTime = new DateTime(1999, 2, 2),
//Price = 3,
Unit = "个2",
FK_Order_Id = "469b82be-8139-4e67-b566-5b2b5f6d838d"
};
ctx.Entry(pro).CurrentValues.SetValues(p);
ctx.SaveChanges();
但是我使用了之后,觉得不太好,也不知道是不是用错了,我遇到的问题是这样的,无法部分更新
比如数据库中存在这么一条数据{"id":"123",”"name":"张三","age":25,"FK_AddressId":"234"},那么我现在只想更新Name,我就传递这个对象{"id":"123","name":"赵四"},但是它还是全部更新
比如我没有指定age属性,那么修改为默认值“0”,外键在数据库中不能为空,报错
各位可以去弄一下
批量更新操作
平时做批量更新,那就是遍历修改呗
var products = ctx.Products.ToList();
foreach (var item in products)
{
item.Name = item.Name + "999";
}
ctx.SaveChanges();
看看EF生成并执行的SQL语句
他会先把要更新的数据查询出来,然后逐条更新,如果你创建了存储过程,那么他会自动调用存储过程进行更新,这个性能会好一点
我这里有一个存储过程
然后真正的SQL执行是这样的
他首先会将要更新的数据查询出来,然后调用多次存储过程
还是觉得不太理想?那么作者告诉了我们一个更好的方案,使用第三方库:EntityFrameWork.Extended
引入命名空间:using EntityFramework.Extensions; 然后调用该Update方法
var products = ctx.Products.Update(x => new Product { Name = x.Name + "777" });
ctx.SaveChanges();
捕获到SQL语句是这样的,很奇怪用ctx.Database.Log = Console.WriteLine;捕获不到,我用的SQL Profiler,这应该是这个Extended库是第三方的原因,不是EF团队弄的
UPDATE [dbo].[tb_Products] SET
[Name] = CASE WHEN ([Name] IS NULL) THEN N'' ELSE [Name] END + N'777'
FROM [dbo].[tb_Products] AS j0 INNER JOIN (
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id]
FROM [dbo].[tb_Products] AS [Extent1]
) AS j1 ON (j0.[Id] = j1.[Id])
看看数据库里面