带有Id和对象的实体框架导航属性失败
本文关键字:框架 导航 属性 失败 实体 Id 对象 带有 | 更新日期: 2023-09-27 18:11:22
保存动态定义导航属性的实体时,会导致问题
这是一个更复杂的代码的复制。
namespace ConsoleAppEFAttaching
{
public class MyContext : DbContext
{
public MyContext()
: base("MyContextConnectionString")
{
base.Configuration.LazyLoadingEnabled = false;
base.Configuration.AutoDetectChangesEnabled = false;
base.Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Parent>();
modelBuilder.Entity<Child>();
}
}
public class Parent
{
public int Id { get; set; }
public string NameParent { get; set; }
public static Parent Create(int id)
{
return new Parent { Id = id };
}
}
public class Child
{
private Parent theOnlyParent;
public int Id { get; set; }
public string NameChild { get; set; }
public Parent TheOnlyParent {
get { return Parent.Create(TheOnlyParentId); }
set { TheOnlyParentId = value.Id; }
}
public int TheOnlyParentId { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Start create database");
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
Console.WriteLine("Start adding Parent");
var p1 = new Parent {NameParent = "Test Parent Name#1"};
int parentCreatedId;
Console.WriteLine("Context");
using (var context = new MyContext())
{
context.Set<Parent>().Add(p1);
context.SaveChanges();
parentCreatedId = p1.Id;
}
Console.WriteLine("Start adding a child from a different context");
var c1 = new Child { NameChild= "Child #1" };
c1.TheOnlyParentId = parentCreatedId;
c1.TheOnlyParent = new Parent {Id = parentCreatedId};
Console.WriteLine("Context");
using (var context = new MyContext())
{
Console.WriteLine("*Change State Child");
context.Entry(c1).State = EntityState.Added; // !!! Error : Conflicting changes to the role 'Child_TheOnlyParent_Target' of the relationship 'Child_TheOnlyParent' have been detected.
Console.WriteLine("*Change State Child->Parent Navigability Property");
context.Entry(c1.TheOnlyParent).State = EntityState.Detached;
Console.WriteLine("*Save Changes");
context.SaveChanges();
}
Console.WriteLine("End");
Console.ReadLine();
}
}
}
将Entry State更改为add时出现问题。错误Conflicting changes to the role 'Child_TheOnlyParent_Target' of the relationship 'ConsoleAppEFAttaching.Child_TheOnlyParent' have been detected.
引发。
如果我放一个Console。在子元素中写入一行。onlyparent属性,我看到该方法在状态变化期间多次设置和获取。我认为这个问题可能是由于返回的对象不相同而引起的,但即使我创建了这个对象一次(只实例化第一次,然后返回相同的实例),它也有同样的问题。
如果我不使用父元素。在子对象中创建。onlyparent,它起作用了。但是,我想使用我们的逻辑(与Create方法)来定义类的情况下,我们想限制包括为性能原因的id。
所以我的问题分为两个:为什么它在更改状态期间多次调用Getter和Setter,为什么我对角色有冲突的更改?
由于context.Entry(c1)方法调用而调用getter和setter。这里发生的事情是,当您为一个分离对象调用这个方法时,ChangeTracker将整个对象图(对象及其所有导航属性)递归地附加到上下文。这就是调用getter的原因。
如果导航属性与已经附加的对象匹配,ChangeTracker也会尝试修复它们。如果你有DbContext和一个Parent。Id = 1已经附加到您的上下文,并且您附加了Child with Child。ParentId = 1和Child。在context.Entry(c1)调用Child之后,父导航属性= null。属性将自动填充。这就是为什么要调用setter
正如您所假设的那样,您的问题是每次访问getter时都会创建Parent对象的新实例。对于EF来说,这基本上就像拥有具有相同主键的对象的多个实例,而ChangeTracker根本无法处理。像这样更改导航和外键属性应该可以工作。
public Parent TheOnlyParent
{
get
{
if (theOnlyParent == null) {
theOnlyParent = Parent.Create(TheOnlyParentId);
}
return theOnlyParent;
}
set
{
If(theOnlyParent != value){
theOnlyParent = value;
if (value != null) {
TheOnlyParentId = value.Id;
}
}
}
}
private int theOnlyParentId;
public int TheOnlyParentId
{
get
{
return theOnlyParentId;
}
set
{
if (theOnlyParentId != value) {
theOnlyParentId = value;
theOnlyParent = null;
}
}
}
我有一些东西需要改变,使它工作
首先,我们需要Child来返回对象。原因是,如果有人将可导航性设置为Null,我们可以在属性为Null的同时保持ID。
public class Child
{
private Parent theOnlyParent;
private int theOnlyParentId;
public int Id { get; set; }
public string NameChild { get; set; }
[Required]
public Parent TheOnlyParent
{
get
{
return theOnlyParent;
}
set
{
theOnlyParent = value;
if (value != null)
TheOnlyParentId = value.Id;
}
}
public int TheOnlyParentId
{
get { return theOnlyParentId; }
set {
theOnlyParentId = value;
theOnlyParent = Parent.Create(value);
}
}
}
第二件事是在处理实体时。我可以将TheOnlyParent设置为null并保留Id,或者我可以使用上下文的Entry并设置为Unchanged。
using (var context = new MyContext())
{
Console.WriteLine("*Change State Child");
context.Entry(c1).State = EntityState.Added; // Conflicting changes to the role 'Child_TheOnlyParent_Target' of the relationship 'Child_TheOnlyParent' have been detected.
Console.WriteLine("*Change State Child->Parent Navigability Property");
context.Entry(c1.TheOnlyParent).State = EntityState.Unchanged; // We do not want to create but reuse
Console.WriteLine("*Save Changes");
context.SaveChanges();
}
如果有人想尝试整个解决方案,这里是完整的代码:
public class MyContext : DbContext
{
public MyContext()
: base("MyContextConnectionString")
{
base.Configuration.LazyLoadingEnabled = false;
base.Configuration.AutoDetectChangesEnabled = false;
base.Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Parent>();
modelBuilder.Entity<Child>();
}
}
public class Parent
{
public int Id { get; set; }
public string NameParent { get; set; }
public static Parent Create(int id)
{
return new Parent { Id = id };
}
}
public class Child
{
private Parent theOnlyParent;
private int theOnlyParentId;
public int Id { get; set; }
public string NameChild { get; set; }
[Required]
public Parent TheOnlyParent
{
get
{
return theOnlyParent;
}
set
{
theOnlyParent = value;
if (value != null)
TheOnlyParentId = value.Id;
}
}
public int TheOnlyParentId
{
get { return theOnlyParentId; }
set {
theOnlyParentId = value;
theOnlyParent = Parent.Create(value);
}
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Start create database");
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
Console.WriteLine("Start adding Parent");
var p1 = new Parent {NameParent = "Test Parent Name#1"};
int parentCreatedId;
Console.WriteLine("Context");
using (var context = new MyContext())
{
context.Set<Parent>().Add(p1);
context.SaveChanges();
parentCreatedId = p1.Id;
}
Console.WriteLine("Start adding a child from a different context");
var c1 = new Child { NameChild= "Child #1" };
c1.TheOnlyParentId = parentCreatedId;
c1.TheOnlyParent = new Parent {Id = parentCreatedId};
Console.WriteLine("Context");
using (var context = new MyContext())
{
Console.WriteLine("*Change State Child");
context.Entry(c1).State = EntityState.Added; // Conflicting changes to the role 'Child_TheOnlyParent_Target' of the relationship 'Child_TheOnlyParent' have been detected.
Console.WriteLine("*Change State Child->Parent Navigability Property");
context.Entry(c1.TheOnlyParent).State = EntityState.Unchanged; // We do not want to create but reuse
Console.WriteLine("*Save Changes");
context.SaveChanges();
}
Console.WriteLine("End");
Console.ReadLine();
}
}