繁体   English   中英

具有ID和对象的实体框架导航属性失败

[英]Entity Framework Navigation Property with Id and Object fail

保存动态定义了导航属性的实体时,会引起问题。

这是更复杂的代码的再现。

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();
        }
    }
}

问题是将进入状态更改为已添加。 Conflicting changes to the role 'Child_TheOnlyParent_Target' of the relationship 'ConsoleAppEFAttaching.Child_TheOnlyParent' have been detected.错误Conflicting changes to the role 'Child_TheOnlyParent_Target' of the relationship 'ConsoleAppEFAttaching.Child_TheOnlyParent' have been detected. 加注。

如果将Console.WriteLine放在Child.TheOnlyParent属性内,则会看到该方法已设置,并且在状态更改期间获得了多次。 我虽然可能由于返回的对象不相同而导致了问题,但是即使我一次创建了该对象(仅实例化一次,然后返回相同的实例),也存在相同的问题。

如果我不使用Child.TheOnlyParent中的Parent.Create,它将起作用。 但是,在出于性能原因要限制Include的情况下,我想使用逻辑(通过Create方法)仅通过id定义类。

因此,我的问题分为两个部分:为什么在更改状态期间多次调用Getter和Setter,为什么对角色进行冲突的更改?

由于调用context.Entry(c1)方法而调用了getter和setter方法。 这里发生的是,当您为分离的对象调用此方法时,ChangeTracker会将整个对象图(对象及其所有导航属性以递归方式)附加到Context。 这就是为什么要调用吸气剂的原因。

ChangeTracker还会尝试使用已连接的对象匹配它们来修复导航属性。 因此,如果您已经将DbContext的Parent.Id = 1附加到您的上下文,并且在context.Entry(c1)之后调用Child.Parent,则将Child.ParentId = 1的Child附加到并且Child.Parent导航属性= null。属性会自动填充。 这就是为什么叫二传手的原因。

正如您所假设的那样,您的问题是每次访问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();
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM