与实体框架的1对1可选关系

时间:2015-09-02 05:07:57

标签: entity-framework entity-framework-6

我很想知道为什么实体框架在以下情境中不允许1对1的关系;

ClassA {
   public int ID;
   [ForeignKey("ClassB")]
   public int ClassBID;
   public ClassB classB;
}

ClassB {
   public int ID;
   [ForeignKey("ClassA")]
   public int ClassAID;
   public ClassA classa;
}

即。一对一的关系,我可以从linq导航到。

我的背景是我有一辆车。每辆车都可以有一个设备(如果没有,则为空)。每个设备都有一个可选的车辆。

如果有人能解释为什么他的上述内容无效(或支持)并解释我如何解决我的问题,我会真的对此进行解释。

提前致谢。

2 个答案:

答案 0 :(得分:1)

您必须确定一端是非可选的(即,允许1到0..1 ... 0..1到0..1不是)。一旦你这样做,EF通过强制依赖方不拥有它自己的Key来支持1到0..1,而是定义依赖类' Key与其ForeignKey相同(如果您考虑它,对于应该是1比1的关系是有意义的):

class A
{
    public int Id { get; set; }
    public int? BId { get; set; }
    public virtual B B { get; set; }
}

class B
{
    [Key, ForeignKey("A")]
    public int Id { get; set; }
    public virtual A A { get; set; }
}

另请注意,SQL中BIdint?NULL的详细信息对应于C#中的Nullable<>

答案 1 :(得分:0)

如果我理解设备可以有零个或一个车辆,反之亦然 在旧的DB模型中,两个表(设备或车辆)中的一个应该具有引用另一个表的可空字段 要在EF中配置它,您必须使用数据注释或流畅的界面。 这里是模型和上下文的代码

public class ClassA
{
    public int Id { get; set; }
    public string Description { get; set; }
    public virtual ClassB ClassB { get; set; }
}

public class ClassB
{
    public int Id { get; set; }
    public string Description { get; set; }
    public virtual ClassA ClassA { get; set; }
}

class Context : DbContext
{
    public Context(DbConnection connection)
        : base(connection, false)
    { }

    public DbSet<ClassA> As { get; set; }
    public DbSet<ClassB> Bs { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ClassB>().HasOptional(c => c.ClassA).WithOptionalDependent(c => c.ClassB);
    }
}

你可以想象一下,使用这个模型有一些限制。即模型(POCO模型)允许你让classA1引用引用classA2的classB1(相同类型但不同的classA1实例)。 DB和EF没有。这里有一个关于EF如何在这种情况下工作的查询转储的例子(我认为非常有趣!!!)

        using (Context context = new Context(connection))
        {
            ClassA classA;
            ClassB classB;


            // Very simple behaviour (as expected). You can see the queries after SaveChanges()
            classA = new ClassA {Description = "B empty"};
            context.As.Add(classA);

            classA = new ClassA { Description = "B full",  ClassB = new ClassB(){Description = "ClassB full"}};
            context.As.Add(classA);

            classB = new ClassB { Description = "B empty"};
            context.Bs.Add(classB);

            context.SaveChanges();
            /*
            insert into [ClassAs]([Description])
                values (@p0);

                @p0 = B full

            insert into [ClassAs]([Description])
                values (@p0);

                @p0 = B empty

            insert into [ClassBs]([Description], [ClassA_Id])
                values (@p0, @p1);

                @p0 = ClassB full
                @p1 = 1

            insert into [ClassBs]([Description], [ClassA_Id])
                values (@p0, null);

                @p0 = B empty
            */




            // Here a new classB references an already referenced classA. But we don't want this!!!
            // EF works like we want, the classA is detached from the old classB then attached to the
            // new classB. Below you can see the queries
            classB = new ClassB { Description = "B full with the wrong A", ClassA = classA};
            context.Bs.Add(classB);
            /*
            update [ClassBs]
                set [ClassA_Id] = null
                where (([Id] = @p0) and ([ClassA_Id] = @p1))

                @p0 = 1
                @p1 = 1

            insert into [ClassBs]([Description], [ClassA_Id])
                values (@p0, @p1);

                @p0 = B full with the wrong A
                @p1 = 1
            */

            context.SaveChanges();
        }

现在最后一步...... 查看数据库的结构,这个POCO模型是

ClassBs(Id, Description, ClassA_Id : ClassAs)  
ClassAs(Id, Description)  

在DB模型中,我们可以有2个不同的ClassB实例,它们具有相同的ClassA实例(EF不允许我们这样做但我们可以从SQL执行此操作)。 在使用SQL进行黑客攻击后,您可以运行此测试

        using (Context context = new Context(connection))
        {
            foreach (var classB in context.Bs.ToList())
            {
                if (classB.ClassA == null)
                    continue;
                Console.WriteLine("{0} {1} {2}", classB.Id, classB.ClassA.Id, classB.ClassA.ClassB.Id);
            }
        }

此测试引发异常

===
EntityFramework.dll中出现未处理的“System.InvalidOperationException”类型异常

附加信息:发生了关系多重性约束违规:EntityReference只能有一个相关对象,但查询返回了多个相关对象。这是一个不可恢复的错误。

===

我们可以避免来自SQL的人这样做吗?是的,在ClassA_Id字段上插入唯一约束。

相关问题