System.InvalidOperationException:“操作失败: 无法更改关系,因为一个或多个外键属性不可以为 null。对关系作出更改后,会将相关的外键属性设置为 null 值。如果外

前言

今天用EF做更新操作时,抛出了System.InvalidOperationException:“操作失败: 无法更改关系,因为一个或多个外键属性不可以为 null。对关系作出更改后,会将相关的外键属性设置为 null 值。如果外键不支持 null 值,则必须定义新的关系,必须向外键属性分配另一个非 null 值,或必须删除无关的对象。”的异常。经过调试之后发现了问题所在。

报错代码:

        public void Update(HouseDTO house)
        {
            using (ZMZDBEntities ctx = new ZMZDBEntities())
            {
                var bs = new BaseService<HouseEntity>(ctx);
                var entity = bs.GetById(house.Id);
                if (entity != null)
                {
                    entity.Address = house.Address;
                    entity.Area = house.Area;

                    entity.HouseAttachments.Clear();//先删再加
                    foreach (var aid in house.AttachmentIds)
                    {
                        entity.HouseAttachments.Add(new HouseAttachmentEntity { AttachmentId = aid, HouseId = house.Id });
                    }

                    entity.CheckInDateTime = house.CheckInDateTime;
                    entity.CommunityId = house.CommunityId;
                    entity.DecorateStatuesId = house.DecorateStatusId;
                    entity.Description = house.Description;
                    entity.Direction = house.Direction;
                    entity.FloorIndex = house.FloorIndex;
                    entity.LookableDateTime = house.LookableDateTime;
                    entity.MonthRent = house.MonthRent;
                    entity.OwnerName = house.OwnerName;
                    entity.OwnerPhoneNum = house.OwnerPhoneNum;
                    entity.RoomTypeId = house.RoomTypeId;
                    entity.StatusId = house.StatusId;
                    entity.TotalFloorCount = house.TotalFloorCount;
                    entity.TypeId = house.TypeId;
                    entity.Address = house.Address;
                    entity.Area = house.Area;
                    ctx.SaveChanges();
                }
            }
        }

当执行到ctx.SaveChanges()是就抛出了异常,而引起问题的代码实际在这里:

entity.HouseAttachments.Clear();//先删再加
foreach (var aid in house.AttachmentIds)
{
    entity.HouseAttachments.Add(new HouseAttachmentEntity { AttachmentId = aid, HouseId = house.Id });
}

这里涉及了三个表House、Attachment和HouseAttachment(中间表,只有Id主键,HouseId外键,AttachmentId外键三个字段)。当Clear掉House对应的Attachement时,HouseAttachment就和House脱离了关系(注意:当执行Clear时EF认为我们是要二者脱离关系,而我们的想法是删除House对应的HouseAttachments)。因为是脱离关系,所以HouseAttachment中House对应的外键字段就会被设置为null,以可被其它House所继续关联。而由于HouseAttachment表的HouseId(外键)为非空类型,所以SaveChanges时就报了应为非null的异常。至于EF为什么要这么做,就要理解下EF的 复合(Composition)和聚集(Aggregation)

分析

对于复合:当父对象创建时子对象也会一并创建,当父对象销毁时子对象也一并销毁,父对象控制着子对象的生命周期。举个例子:你发布了一篇博客,并在博客里上传了一些附件,当你删除这篇博客时对应的附件也应该要一起删掉。

对于聚集:父对象和子对象是独立存在的,当父对象销毁时,子对象仍然可以存在(因为可能会被用到其它父对象中)。举个例子:一个歌单里包括了好几首歌,当这个歌单删除时,它对应的歌曲不应该被删除,因为还可以被加入到其它歌单里。

对于上述例子我们认为它应该是复合,但实际上EF认为是聚集。

EF则根据以下两点来区分复合还是聚集:

  • 复合:如果EF在子对象中发现了复合主键(ParentId,ChildId),则认为是复合关系。对于此例来说如果HouseAttachment的Id和HouseId同时为主键,那么就不会报错了,然而我们的主键并不包括HouseId。
  • 聚集:如果EF在子对象中发现了外键且不是复合主键,则认为是聚集关系。

所以走到这里问题也就明了了,解决方式有三种:

  1. 让EF认为我们这是复合关系,Clear时就删除对应的HouseAttachment:就需要将HouseAttachment的Id和HouseId同时设置为主键。
  2. 仍旧保持聚集关系:将HouseAttachment的HouseId字段设置为可空类型。
  3. 仍旧保持聚集关系:手动将house下的HouseAttachment删除,将出错代码改为
ctx.HouseAttachments.RemoveRange(entity.HouseAttachments);
foreach(var aid in house.AttachmentIds)
{
    ctx.HouseAttachments.Add(new HouseAttachmentEntity { AttachmentId = aid, HouseId = house.Id });
}

Inspired by
The relationship could not be changed because one or more of the foreign-key properties is non-nullable

这个异常信息 "System.InvalidOperationException: 'ExecuteNonQuery: Connection 属性尚未初始化'" 是在使用 .NET Framework 中的 ADO.NET(ActiveX Data Objects for .NET)库时遇到的问题。它通常发生在尝试执行 SQL 命令(如 INSERT, UPDATE 或 DELETE 等)之前,但当前的 SqlConnection 对象还没有被正确地打开(即尚未调用 Open() 方法)。 在编写代码时,如果你试图立即执行 SQL 操作而没有首先创建并打开数据库连接,就抛出这样的异常。这可能出现在以下几个场景: - 在创建 SqlConnection 对象后忘记调用 Open() 方法。 - 在 Try/Catch 块内执行数据库操作前,SqlConnection 的状态未在 Try 块内部正确管理。 - 在使用事务或存储过程时,在提交或回滚事务之前忘记了打开连接。 修复这个问题的方法是在执行 ExecuteNonQuery() 或相关的数据库操作之前,确保 SqlConnection 对象已经处于可用状态,即已经成功打开了连接: ```vbnet Using conn As New SqlConnection(connectionString) conn.Open() Dim cmd As New SqlCommand("your_sql_command", conn) cmd.ExecuteNonQuery() ' 现在可以安全地执行 SQL End Using ``` 或者 ```csharp using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); SqlCommand cmd = new SqlCommand("your_sql_command", conn); cmd.ExecuteNonQuery(); // 同样在这里执行 SQL } ``` 确保你在每个数据库操作块结束时使用 `using` 语句来自动关闭连接,这样可以避免资源泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JimCarter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值