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)
我已经弄清楚发生了什么.当我设置时many.MyOneSideEntity = null;,实体的更改跟踪器已捕获初始值"null".因此,从实体的角度来看,属性的值没有变化,因此即使属性的值与数据库中的值不同,更改跟踪器也不认为属性需要更新.
要解决此问题,只需通过getter访问该属性即可.这会导致实体的状态被加载到内存中,并且更改跟踪器将与这些值同步.然后,更改跟踪器会通过将该属性设置为null来注意所做的更改,然后在调用SaveChanges时将更改推送到数据库.
我必须这样做有点奇怪,但它与Entity的更改跟踪器的工作原理相吻合.
希望这有助于其他人!