我很新于 EF,遇到了一个实体并未如我所料的情况,希望能帮我理解这是 EF 中的 bug 还是我的“误解”。本文结尾附上所有代码,可在安装 Entity 6 的测试项目中进行编译和运行。
以下是具体情况:
我正在使用 Code-First。
我有两个实体处于多对一的关系。它们如下:
假设我创建并保存了一个
这里开始变得有趣了。如果我现在尝试使用新的上下文设置
然而,如果我通过使用关系的另一端进行移除,它就可以工作。也就是说,如果我获取
最后,如果同一个失败的单元测试稍微更改一下,使用相同的DbContext添加
我认为应该能够使失败的案例工作,并且应该更新单侧实体的导航属性。有没有一种方法可以将导航属性设置为null,并让Entity在不必保持相同的DbContext的情况下推出更改?
以下是具体情况:
我正在使用 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; }
}
假设我创建并保存了一个
ManySideEntity
到变量many
中,我在执行context.SaveChanges()
之后就处理了上下文。然后我通过对many
的MyOneSideEntity
属性执行“set”来创建并保存一个OneSideEntity
,在之后处理上下文时进行处置。这很好;新的OneSideEntity
实例被添加到数据库中,并且many的外键正确更新为OneSideEntity
实例的主键值。这里开始变得有趣了。如果我现在尝试使用新的上下文设置
many.MyOneSideEntity = null;
并进行保存,更改不会推送到数据库中,单元测试中的asserts RelationshipRemovalFails
将失败。然而,如果我通过使用关系的另一端进行移除,它就可以工作。也就是说,如果我获取
OneSideEntity
实例,称之为one
,并从其单侧导航属性中删除它,如one.MyManySideEntities.Remove(many);
并保存,它确实会被推送到数据库中。因此,与此场景相对应的单元测试中的asserts(在下面的代码中称为RelationshipRemovalSucceeds1
)通过了,因为更改已经保存。实体甚至根据我的理解正确地更新了many.MyOneSideEntity
为null作为保存的副作用。最后,如果同一个失败的单元测试稍微更改一下,使用相同的DbContext添加
one
并通过many.MyOneSideEntity = null;
进行删除,它也会成功。这个单元测试在下面的代码中被称为RelationshipRemovalSucceeds2
。我认为应该能够使失败的案例工作,并且应该更新单侧实体的导航属性。有没有一种方法可以将导航属性设置为null,并让Entity在不必保持相同的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 );
}
}
}
}
MyOneSideEntityId
),您只需将其设置为null,SaveChanges应该会删除引用。如果您更新ID字段而不是实体(就像您所说的那样,如果您通过实体进行操作,则必须首先从数据库中加载它),则更改跟踪器的工作效果更好。 - Flater