将导航属性设置为null不会保存到数据库

Pau*_*aul 3 c# entity-framework

我是EF的新手,我遇到过Entity没有按照我的预期行事的情况.我很感激帮助理解它是EF中的错误还是我理解中的"错误".

我在帖子的末尾包含了所有代码; 它应该在安装了Entity 6的测试项目中编译和运行.

这是情况.

我正在使用Code-First.

我有两个多对一关系的实体.他们来了:

public class OneSideEntity
    {
        public int OneSideEntityId { get; set; }
        public string Name { get; set; }
        public virtual List<ManySideEntity> MyManySideEntities { get; set; }
    }

    public class ManySideEntity
    {
        public int ManySideEntityId { get; set; }
        public string Name { get; set; }
        public virtual OneSideEntity MyOneSideEntity { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

现在假设我创建并保存一个ManySideEntity变量many ,在我执行之后处理上下文context.SaveChanges().然后我创建并保存a OneSideEntity通过对MyOneSideEntity属性执行"set" many并保存,处理上下文后跟.这很好用; 将新OneSideEntity实例添加到数据库中,并将许多外键正确更新为OneSideEntity实例主键的值.

这是它开始变得有趣的地方.如果我现在尝试many.MyOneSideEntity = null;使用新上下文进行设置并保存,则不会将更改推送到数据库,并且单元测试中的断言将RelationshipRemovalFails失败.

但是,如果我通过使用关系的另一端执行删除,它可以工作.也就是说,如果我获取OneSideEntity实例,请one简短地调用它,并将其从像这样的单侧导航属性中删除one.MyManySideEntities.Remove( many );并保存,它会被推送到数据库.因此,对应于此场景的单元测试中的断言(RelationshipRemovalSucceeds1在下面的代码中调用 )在保存更改时传递.实体甚至正确(根据我的理解)更新many.MyOneSideEntity为null作为保存的副作用.

最后,如果相同的单元测试失败稍微更改为使用相同的DbContext添加one及其后来的删除通过many.MyOneSideEntity = null;,它也会成功.RelationshipRemovalSucceeds2在下面的代码中调用此单元测试.

我认为失败的情况应该起作用,并且应该更新单侧实体的导航属性.有没有办法将导航属性设置为null并让实体推送更改而不必保持相同的DbContext?

完整代码:

using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using Epsilon.Toolbox.LINQ;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Tests
{
    ////////////////////////////////////////////////
    // Two simple entity classes
    ////////////////////////////////////////////////

    public class OneSideEntity
    {
        public int OneSideEntityId { get; set; }
        public string Name { get; set; }
        public virtual List<ManySideEntity> MyManySideEntities { get; set; }
    }

    public class ManySideEntity
    {
        public int ManySideEntityId { get; set; }
        public string Name { get; set; }
        public virtual OneSideEntity MyOneSideEntity { get; set; }
    }

    ////////////////////////////////////////////////
    // Fluent configuration classes for the entities
    ////////////////////////////////////////////////

    public class ManySideEntityConfiguration : EntityTypeConfiguration<ManySideEntity>
    {
        public ManySideEntityConfiguration()
        {
            this.HasKey( x => x.ManySideEntityId );
        }
    }

    public class OneSideEntityConfiguration : EntityTypeConfiguration<OneSideEntity>
    {
        public OneSideEntityConfiguration()
        {
            this.HasKey( x => x.OneSideEntityId );
        }
    }

    ////////////////////////////////////////////////
    // DbContext 
    ////////////////////////////////////////////////

    public class RelationshipDeleteTestContext : DbContext
    {
        public DbSet<OneSideEntity> OneSideEntities { get; set; }
        public DbSet<ManySideEntity> ManySideEntities { get; set; }

        protected override void OnModelCreating( DbModelBuilder modelBuilder )
        {
            // Entities
            modelBuilder.Configurations.Add( new OneSideEntityConfiguration() );
            modelBuilder.Configurations.Add( new ManySideEntityConfiguration() );
        }
    }

    ////////////////////////////////////////////////
    // Fails to properly save the result of "manySideEntityX.MyOneSideEntity = null;"
    ////////////////////////////////////////////////

    [TestClass]
    public class EntityTest
    {
        [TestMethod]
        [TestCategory( "EntityTests" )]
        public void RelationshipRemovalFails()
        {
            Database.SetInitializer( new DropCreateDatabaseAlways<RelationshipDeleteTestContext>() );

            // Add a ManySideEntity.
            int manySideEntityXId;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX1 = new ManySideEntity() { Name = @"X" };
                context.ManySideEntities.Add( manySideEntityX1 );
                context.SaveChanges();
                manySideEntityXId = manySideEntityX1.ManySideEntityId;
            }

            // Add a OnSideEntity by setting to the ManySide entity's 
            // navigation property to the newly created OneSideEntity.
            int oneSideEntityIdA;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var oneSideEntityA = new OneSideEntity()
                {
                    Name = "A",
                };
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                manySideEntityX.MyOneSideEntity = oneSideEntityA;
                context.SaveChanges();
                oneSideEntityIdA = oneSideEntityA.OneSideEntityId;
            }

            int oneSideEntityIdB;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var oneSideEntityB = new OneSideEntity()
                {
                    Name = "B",
                };
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                manySideEntityX.MyOneSideEntity = oneSideEntityB;
                context.SaveChanges();
                oneSideEntityIdB = oneSideEntityB.OneSideEntityId;
            }

            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );

                // Here is the statement that doesn't work; the database is not updated to null out the foreign key after SaveChanges.
                manySideEntityX.MyOneSideEntity = null;
                context.SaveChanges();
            }

            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                var oneSideEntityB = context.OneSideEntities.Single( x => x.OneSideEntityId == oneSideEntityIdB );
                var manySideCount = oneSideEntityB.MyManySideEntities.Count();

                // Both Asserts fail since the foreign key in the ManySideEntities table has not been nulled out.
                Assert.IsNull( manySideEntityX.MyOneSideEntity );
                Assert.AreEqual( 0, manySideCount );
            }
        }

        [TestMethod]
        [TestCategory( "EntityTests" )]
        public void RelationshipRemovalSucceeds1()
        {
            Database.SetInitializer( new DropCreateDatabaseAlways<RelationshipDeleteTestContext>() );

            // Add a ManySideEntity.
            int manySideEntityXId;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX = new ManySideEntity() { Name = @"X" };
                context.ManySideEntities.Add( manySideEntityX );
                context.SaveChanges();
                manySideEntityXId = manySideEntityX.ManySideEntityId;
            }

            // Add a OnSideEntity by setting to the ManySide entity's 
            // navigation property to the newly created OneSideEntity.
            int oneSideEntityIdA;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var oneSideEntityA = new OneSideEntity()
                {
                    Name = "A",
                };
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                manySideEntityX.MyOneSideEntity = oneSideEntityA;
                context.SaveChanges();
                oneSideEntityIdA = oneSideEntityA.OneSideEntityId;
            }

            int oneSideEntityIdB;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var oneSideEntityB = new OneSideEntity()
                {
                    Name = "B",
                };
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                manySideEntityX.MyOneSideEntity = oneSideEntityB;
                context.SaveChanges();
                oneSideEntityIdB = oneSideEntityB.OneSideEntityId;
            }

            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );

                // Using the other side of the relationship DOES work!
                var oneSideEntityB = manySideEntityX.MyOneSideEntity;
                oneSideEntityB.MyManySideEntities.Remove( manySideEntityX );
                context.SaveChanges();
            }

            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                var oneSideEntityB = context.OneSideEntities.Single( x => x.OneSideEntityId == oneSideEntityIdB );
                var manySideCount = oneSideEntityB.MyManySideEntities.Count();
                // Asserts now succeed!
                Assert.IsNull( manySideEntityX.MyOneSideEntity );
                Assert.AreEqual( 0, manySideCount );
            }
        }

        [TestMethod]
        [TestCategory( "EntityTests" )]
        public void RelationshipRemovalSucceeds2()
        {
            Database.SetInitializer( new DropCreateDatabaseAlways<RelationshipDeleteTestContext>() );

            // Add a ManySideEntity.
            int manySideEntityXId;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX = new ManySideEntity() { Name = @"X" };
                context.ManySideEntities.Add( manySideEntityX );
                context.SaveChanges();
                manySideEntityXId = manySideEntityX.ManySideEntityId;
            }

            // Add a OnSideEntity by setting to the ManySide entity's 
            // navigation property to the newly created OneSideEntity.
            int oneSideEntityIdA;
            using ( var context = new RelationshipDeleteTestContext() )
            {
                var oneSideEntityA = new OneSideEntity()
                {
                    Name = "A",
                };
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                manySideEntityX.MyOneSideEntity = oneSideEntityA;
                context.SaveChanges();
                oneSideEntityIdA = oneSideEntityA.OneSideEntityId;
            }

            int oneSideEntityIdB;
            using ( var context = new RelationshipDeleteTestContext() )
            {
               {
                    var oneSideEntityB = new OneSideEntity()
                    {
                        Name = "B",
                    };
                    var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                    manySideEntityX.MyOneSideEntity = oneSideEntityB;
                    context.SaveChanges();
                    oneSideEntityIdB = oneSideEntityB.OneSideEntityId;
                }

                {
                    var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );

                    // This now works when using the same context for an add the remove.
                    manySideEntityX.MyOneSideEntity = null;
                    context.SaveChanges();
                }
            }

            using ( var context = new RelationshipDeleteTestContext() )
            {
                var manySideEntityX = context.ManySideEntities.Single( x => x.ManySideEntityId == manySideEntityXId );
                var oneSideEntityB = context.OneSideEntities.Single( x => x.OneSideEntityId == oneSideEntityIdB );
                var manySideCount = oneSideEntityB.MyManySideEntities.Count();

                // Both Asserts now Succeed/
                Assert.IsNull( manySideEntityX.MyOneSideEntity );
                Assert.AreEqual( 0, manySideCount );
            }

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*aul 5

我已经弄清楚发生了什么.当我设置时many.MyOneSideEntity = null;,实体的更改跟踪器已捕获初始值"null".因此,从实体的角度来看,属性的值没有变化,因此即使属性的值与数据库中的值不同,更改跟踪器也不认为属性需要更新.

要解决此问题,只需通过getter访问该属性即可.这会导致实体的状态被加载到内存中,并且更改跟踪器将与这些值同步.然后,更改跟踪器会通过将该属性设置为null来注意所做的更改,然后在调用SaveChanges时将更改推送到数据库.

我必须这样做有点奇怪,但它与Entity的更改跟踪器的工作原理相吻合.

希望这有助于其他人!

  • 如果你有一个FK ID字段(`MyOneSideEntityId`),你可以将它设置为null,SaveChanges应该删除引用.如果您更新ID字段而不是实体,更改跟踪器的效果会更好(就像您说的那样,如果您按实体执行此操作,则必须先从数据库中加载它) (2认同)