关于EF Core中同一个实体被多次tracked的问题

先来看一段错误提示:The instance of entity type ‘XXXX’ cannot be tracked because another instance with the same key value for {‘Key’} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
这里面的"XXXX"可能是任意一个实体模型的名称,“Key"则是这个实体的Key字段。
出现这个错误的原因是试图更新某个实体时,拥有相同Key的另一个实体也被EF Core tracked了。看一下这段代码:

[HttpPost]
public async Task<IActionResult> TestTracked(DeviceType deviceType)
{
    try
    {
    	//type这个实体被EF Core tracked
        var type = await context.DeviceTypes.FindAsync(deviceType.Id);
        //准备更新deviceType实体,但type实体的key与deviceType的key相同,也就是同一个Key的实体被多次tracked,出现本文开头说的错误
        context.Update(deviceType);
        await context.SaveChangesAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
    return new JsonResult(new { success = true });
}

要解决这个问题很简单,从linq查询返回实体时指定不跟踪实体就行了。

//把原来的代码:var type = await context.DeviceTypes.FindAsync(deviceType.Id); 改成下面这样
var type = await context.DeviceTypes.AsNoTracking().FirstOrDefaultAsync(d => d.Id == deviceType.Id);

AsNoTracking()方法就是起这个作用。

但是我今天碰到一个很奇怪的多次tracked问题。
我有一个实体Point,其中包含一个导航属性Devices,这是一个List<Device>,也就是Point实体和Device实体是一对多的关系。Device实体中又包含了几个外键。现在我向服务器提交json数据,准备更新一个Point实体,数据类似如下:

{
	"pointId": 1,
	"pointName": "测试点",
	"devices": [{
		"deviceId": 1,
		"modelId", 1,
		"supplierId: 1,
	},{
		"deviceId": 2,
		"modelId", 2,
		"supplierId: 1,
	}]
}

Device实体中的modelId和supplierId都是外键。
结果在context.Update(point)时,出现实体被多次跟踪的错误,错误中实体名是SupplierSupplierDevice的外键表。在整个更新的过程中,我没有对Supplier实体做任何查询和更新的动作。所以这个错误就出现的比较古怪了。

经过反复跟踪调试,终于找到问题原因。
在服务器端获取Point实体时,为了在客户端展现SupplieName,我使用了Include和ThenInclude方法:

var point = await context.Points
    .Include(p => p.Devices)					//获取Devices集合数据
    .ThenInclude(p => p.DeviceModel)			//获取Device实体的关联实体DeviceModel数据
    .Include(p => p.Devices)
    .ThenInclude(p => p.Supplier)				//获取Device实体的关联实体Supplier的数据
    .FirstOrDefaultAsync(p => p.Id == id);

返回到客户端的json对象类似以下:

{
	"pointId": 1,
	"pointName": "测试点",
	"devices": [{
		"deviceId": 1,
		"modelId", 1,
		"deviceModel": { deviceMode对象,略 },
		"supplierId: 1,
		"supplier": { supplier对象, 略 }
	},{
		"deviceId": 2,
		"modelId", 2,
		"deviceModel": { deviceMode对象,略 },
		"supplierId: 1,
		"supplier": { supplier对象, 略 }
	}]
}

客户端更新时,修改了上面json对象的部分字段,再把json对象提交到服务器,因此json对象中的DeviceModel和Supplier对象都不为null,当使用EF Core的Update方法时,因为多个Device对象中的Supplier实体的Key相同,就出现多次tracked错误。在这个案例中,因为每个Device的ModelId都不同,所以没有报DeviceModel实体被多次tracked错误。
找到问题原因,解决就简单了,在调用Update方法之前,把每个Device对象中关联实体都设为null就可以了。

foreach (var device in point.Devices)
{
    //把所有外键实体设为null,避免出现多次tracked的错误
    device.DeviceModel = null;
    device.Supplier = null;
}

也可以在客户端转换json字符串时处理:

$("#devices input").each((_, item) => {
    devices.push(JSON.parse($(item).val(), (key, value)=>{
        switch(key){
            case "deviceModel":
            case "supplier":
                return null;
            default:
                return value;
        }
    }));
})

当然,从代码结构上来讲,在服务器端处理比较好。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值