LINQ To SQL一对一关联重复绑定资源时抛出异常求助
问题核心
在同一资源被同一实体多次绑定(多语言场景)时,执行关联重建代码抛出异常:
An attempt was made to remove a relationship between a tbl_res_Resource and a tbl_res_ResourcesEntity. However, one of the relationship's foreign keys (tbl_res_ResourcesEntity.RESOURCE_ID, tbl_res_ResourcesEntity.RESOURCE_TYPE_ID) cannot be set to null.
异常原因
实体状态跟踪冲突
原流程中,创建交叉表记录后立即SubmitChanges,随后又通过FirstOrDefault重新查询该实体,导致上下文存在两个ID相同但实例不同的tbl_res_ResourcesEntity对象。LINQ to SQL的状态跟踪逻辑因此混乱,错误触发了"移除关系"的操作(试图将外键置空)。复合外键的严格校验
交叉表使用RESOURCE_ID + RESOURCE_TYPE复合外键关联资源表,LINQ to SQL处理复合外键的导航属性赋值时,会先尝试清除原有外键值(置为null)再设置新值。即使当前记录外键原本为null,混乱的状态跟踪也会触发外键非空校验,抛出异常。不必要的分步提交与实体查询
分两次SubmitChanges以及重复查询实体的操作,完全多余且加剧了上下文的状态冲突。
解决方案
方案1:简化流程,统一提交变更
直接在创建交叉表记录时关联资源,避免分步提交和重复查询:
foreach (var candidareResource in candidateResources) { // 先查询已有资源,不存在则创建 var dbResource = context.tbl_res_Resources.FirstOrDefault(res => res.RESOURCE_KEY == candidareResource.RESOURCE_KEY && res.RESOURCE_TYPE == candidareResource.RESOURCE_TYPE); if (dbResource == null) { dbResource = candidareResource; dbResource.SanifyLinqEntity(); context.tbl_res_Resources.InsertOnSubmit(dbResource); } // 创建交叉表记录并直接关联资源 var relation = new tbl_res_ResourcesEntity() { ENTITY_ID = entity.Id, ENTITY_TYPE_ID = (int)entity.EType, ID_LANG = candidareResource.ID_LANG, Resource = dbResource // 直接绑定导航属性 }; context.tbl_res_ResourcesEntities.InsertOnSubmit(relation); } // 统一提交所有变更 context.SubmitChanges();
方案2:修复AttachResource的实体状态问题
若必须保留原有流程,需避免重复查询实体,直接使用上下文中已跟踪的实例:
public virtual tbl_res_ResourcesEntity AttachResource(tbl_res_ResourcesEntity objToUpdate, tbl_res_Resource resourceToAttach) { if (objToUpdate.ID <= 0) throw new ExtendedArgumentException("resourceToAttach", "No valid resource"); var dbResource = this.Datacontext.tbl_res_Resources.FirstOrDefault(res => res.RESOURCE_KEY == resourceToAttach.RESOURCE_KEY && res.RESOURCE_TYPE == resourceToAttach.RESOURCE_TYPE); if (dbResource == null) { dbResource = resourceToAttach; dbResource.SanifyLinqEntity(); this.Datacontext.tbl_res_Resources.InsertOnSubmit(dbResource); } // 直接使用传入的已跟踪实体,而非重新查询的实例 objToUpdate.Resource = dbResource; return objToUpdate; }
同时修改循环逻辑,取消分步提交:
foreach (var candidareResource in candidateResources) { var relation = new tbl_res_ResourcesEntity() { ENTITY_ID = entity.Id, ENTITY_TYPE_ID = (int)entity.EType, ID_LANG = candidareResource.ID_LANG, }; repository.Create(relation); repository.AttachResource(relation, candidareResource); } // 统一提交 context.SubmitChanges();
内容的提问来源于stack exchange,提问作者Skary

