c# – Entity Framework自动外键人口

是否有任何方法可以强制Entity Framework在将实体添加到上下文时立即填充外键,而不是将其延迟,直到上下文发生其他情况为止?使用数据绑定显示引用的实体时,此默认行为不是很有用.

只需从上下文中引用任何DbSet就足以强制EF填充添加的Children的Parent和Parent_Name.但是,SaveChanges似乎迫使EF填充Reference或Reference_Name.

我真的想用[Required] ttribute标记Reference_Name,所以它在数据库中将是Null,但是如果我这样做,当我尝试调用SaveChanges时,我会得到验证错误,除非我明确设置了Reference_Name,甚至虽然如果设置了Reference,SaveChanges本身将正确填充Reference_Name.

我真的希望能够设置Reference或Reference_Name并能够立即使用另一个.同样,我希望能够在添加Child对象后立即使用Parent或Parent_Name,而不必首先使用从上下文访问其他元素的kludge.

任何人都可以帮助我理解为什么EF会延迟这些事情,以及我如何强制它填充外键属性或外键列,最好是立即但至少不必调用SaveChanges?当EF要正确地填充它们时,我真的不想完全填充所有属性.

public class OracleContext : DbContext
{
    public virtual DbSet<Parent> Parents { get; set; }
    public virtual DbSet<Child> Children { get; set; }
    public virtual DbSet<Reference> References { get; set; }
    public virtual DbSet<SomethingElse> SomethingElses { get; set; }
}

public class Parent
{
    [Key, MaxLength(30)]
    public string Name { get; set; }

    [InverseProperty("Parent")]
    public virtual List<Child> Children { get; set; } = new List<Child>();
}

public class Child
{
    [Key, Column(Order = 1), MaxLength(30)]
    public string Parent_Name { get; set; }

    [Key, Column(Order = 2), MaxLength(30)]
    public string Name { get; set; }

    public string Reference_Name { get; set; }

    [ForeignKey("Parent_Name")]
    public virtual Parent Parent { get; set; }

    [ForeignKey("Reference_Name")]
    public virtual Reference Reference { get; set; }

    public Child Clone()
    {
        return new Child
        {
            Parent_Name = this.Parent_Name,
            Name = this.Name,
            Reference_Name = this.Reference_Name,
            Parent = this.Parent,
            Reference = this.Reference
        };
    }
}

public class Reference
{
    [Key, MaxLength(30)]
    public string Name { get; set; }
}

public class SomethingElse
{
    [Key, MaxLength(30)]
    public string Name { get; set; }
}

private void button1_Click(object sender, EventArgs e)
{
    OracleContext context = new OracleContext();

    Reference reference = context.References.Add(new Reference { Name = "Reference" });

    Parent alpha = context.Parents.Add(new Parent { Name = "Alpha" });

    Child alphaOne = new Child { Name = "AlphaOne" };
    Child alphatwo = new Child { Name = "AlphaTwo", Reference_Name = "Reference" };
    alpha.Children.AddRange(new List<Child> { alphaOne, alphatwo });
    alphaOne.Reference = reference;

    var list = (
            from child in alpha.Children
            select new
            {
                Time = "Before referencing SomethingElses.Local",
                Child = child.Clone()
            }
        ).ToList();

    var x = context.SomethingElses.Local;

    list.AddRange(
            from child in alpha.Children
            select new
            {
                Time = "After referencing SomethingElses.Local",
                Child = child.Clone()
            }
        );

    list.AddRange(
            from parent in context.Parents.Local
            from child in parent.Children
            select new
            {
                Time = "Before SaveChanges",
                Child = child.Clone()
            }
        );

    context.SaveChanges();

    list.AddRange(
            from parent in context.Parents.Local
            from child in parent.Children
            select new
            {
                Time = "After SaveChanges",
                Child = child.Clone()
            }
        );

    foreach (var item in list)
    {
        Console.WriteLine("{0}:\r\n\tName = '{1}'\r\n\tParent = '{2}' ({3})\r\n\tReference = '{4}' ({5})",
            item.Time, item.Child.Name, item.Child.Parent_Name, item.Child.Parent, item.Child.Reference_Name, item.Child.Reference);
    }
}

Before referencing SomethingElses.Local:
    Name = 'AlphaOne'
    Parent = '' ()
    Reference = '' (WindowsFormsApplication2.Reference)
Before referencing SomethingElses.Local:
    Name = 'AlphaTwo'
    Parent = '' ()
    Reference = 'Reference' ()
After referencing SomethingElses.Local:
    Name = 'AlphaOne'
    Parent = 'Alpha' (WindowsFormsApplication2.Parent)
    Reference = '' (WindowsFormsApplication2.Reference)
After referencing SomethingElses.Local:
    Name = 'AlphaTwo'
    Parent = 'Alpha' (WindowsFormsApplication2.Parent)
    Reference = 'Reference' ()
Before SaveChanges:
    Name = 'AlphaOne'
    Parent = 'Alpha' (WindowsFormsApplication2.Parent)
    Reference = '' (WindowsFormsApplication2.Reference)
Before SaveChanges:
    Name = 'AlphaTwo'
    Parent = 'Alpha' (WindowsFormsApplication2.Parent)
    Reference = 'Reference' ()
After SaveChanges:
    Name = 'AlphaOne'
    Parent = 'Alpha' (WindowsFormsApplication2.Parent)
    Reference = 'Reference' (WindowsFormsApplication2.Reference)
After SaveChanges:
    Name = 'AlphaTwo'
    Parent = 'Alpha' (WindowsFormsApplication2.Parent)
    Reference = 'Reference' (WindowsFormsApplication2.Reference)

最佳答案

Is there any way to force Entity Framework to populate the foreign keys immediately when an entity is added to the context, rather than delaying it until something else happens with the context?

选项1:

如果您只想修复实体之间的关系,而不将它们保存到数据库,因此调用DbContext.SaveChanges()然后只需调用DbContext.ChangeTracker.DetectChanges().

选项2:

EF可以自动修复entites之间的关系,而无需调用DbContext.SaveChanges()或DbContext.ChangeTracker.DetectChanges().这些entites称为代理类.代理类是动态生成的派生类型,充当实体的代理.此代理会覆盖实体的某些虚拟属性,以插入用于在访问属性时自动执行操作的挂钩.默认情况下,为DbContext启用代理创建,除非您通过调用DbContext.Configuration.ProxyEnabled = false;禁用它.您无需添加该行代码,因为您需要启用代理创建.

无论如何,在使用此功能之前,您需要修改课程中的一些内容:

>必须将所有属性(标量,导航,集合)标记为虚拟
>必须将所有导航集合声明为ICollection< T>
>所有实体实例必须使用DbContext.DbSet< T> .Create()方法完成.
>不能将所有集合实例化初始化为构造函数.代理类将负责实例化,如果不遵循这一点,将抛出异常.

按照这些步骤,您的entites类必须如下所示:

public class Parent
{
    [Key, MaxLength(30)]
    public virtual string Name { get; set; }

    [InverseProperty("Parent")]
    public virtual ICollection<Child> Children { get; set; }
}

public class Child
{
    [Key, Column(Order = 1), MaxLength(30)]
    public virtual string Parent_Name { get; set; }

    [Key, Column(Order = 2), MaxLength(30)]
    public virtual string Name { get; set; }

    public virtual string Reference_Name { get; set; }

    [ForeignKey("Parent_Name")]
    public virtual Parent Parent { get; set; }

    [ForeignKey("Reference_Name")]
    public virtual Reference Reference { get; set; }

    public Child Clone()
    {
        return new Child
        {
            Parent_Name = this.Parent_Name,
            Name = this.Name,
            Reference_Name = this.Reference_Name,
            Parent = this.Parent,
            Reference = this.Reference
        };
    }
}

public class Reference
{
    [Key, MaxLength(30)]
    public virtual string Name { get; set; }
}

public class SomethingElse
{
    [Key, MaxLength(30)]
    public virtual string Name { get; set; }
}

您的单击事件处理程序impelmentation将如下所示:

Reference reference = context.References.Create();
reference.Name = "Reference";
context.References.Add(reference);

Parent alpha = context.Parents.Create();
alpha.Name = "Alpha"; 
context.Parents.Add(alpha);

Child alphaOne = context.Children.Create();
alphaOne.Name = "AlphaOne";

Child alphatwo = context.Children.Create();
alphatwo.Name = "AlphaTwo";
alphatwo.Reference = reference; // Notice we use the navigational property.

alpha.Children.Add(alphaOne);
alpha.Children.Add(alphatwo);
alphaOne.Reference = reference;

var list = (
        from child in alpha.Children
        select new
        {
            Time = "Before referencing SomethingElses.Local",
            Child = child.Clone()
        }
    ).ToList();

var x = context.SomethingElses.Local;

list.AddRange(
        from child in alpha.Children
        select new
        {
            Time = "After referencing SomethingElses.Local",
            Child = child.Clone()
        }
    );

list.AddRange(
        from parent in context.Parents.Local
        from child in parent.Children
        select new
        {
            Time = "Before SaveChanges",
            Child = child.Clone()
        }
    );

context.SaveChanges();

list.AddRange(
        from parent in context.Parents.Local
        from child in parent.Children
        select new
        {
            Time = "After SaveChanges",
            Child = child.Clone()
        }
    );

foreach (var item in list)
{
    Console.WriteLine("{0}:\r\n\tName = '{1}'\r\n\tParent = '{2}' ({3})\r\n\tReference = '{4}' ({5})",
        item.Time, item.Child.Name, item.Child.Parent_Name, item.Child.Parent, item.Child.Reference_Name, item.Child.Reference);
}

这个实现有两个明显的变化:

>如上所述,您必须使用DbContext.DbSet.Create来获取生成的T代理的实例,而不是使用通过代理传递的默认构造函数.
>关于Lazy Loading的一件事是检查是否加载了导航属性.如果没有,则检查数据库以加载实体.在您的情况下,您的所有实体都处于已添加状态,然后执行Reference_Name =“Reference”将无法帮助您的上下文延迟加载导航属性Refererence.这就是为什么而不是做alphatwo.Reference_Name =“Reference”;我做了alphatwo.Reference = reference;因为引用是在添加状态,Lazy Load将在数据库中找不到任何内容.

点赞