实体框架自动外键填充

Mat*_*les 5 c# entity-framework

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

DbSet只需引用上下文中的any就足以强制 EFParent填充Parent_Name添加的Children. 但似乎没有什么比SaveChanges强制 EF 填充Referenceor 更重要Reference_Name了。

我真的很想Reference_Name[Required]ttribute 进行标记,这样它将Not Null在数据库中,但如果我这样做,当我尝试调用时,SaveChanges除非我显式设置了,否则我会收到验证错误Reference_Name,即使如果 Reference 是,SaveChanges它本身也会正确填充Reference_Name放。

我真的希望能够设置其中一个ReferenceReference_Name并能够立即使用另一个。同样,我希望能够立即使用ParentParent_Name添加Child对象后,而不必首先从上下文中访问其他元素。

谁能帮助我理解为什么 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)
Run Code Online (Sandbox Code Playgroud)

Cod*_*und 4

当实体添加到上下文时,是否有任何方法可以强制实体框架立即填充外键,而不是延迟它直到上下文发生其他情况?

选项1:

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

选项2:

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

无论如何,在利用此功能之前,您需要修改类上的一些内容:

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

按照此步骤,您的实体类必须如下所示:

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; }
}
Run Code Online (Sandbox Code Playgroud)

您的点击事件处理程序实现将如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

此实现中有两个明显的变化:

  • 如前所述,您必须使用 DbContext.DbSet.Create 来获取 T 的生成代理的实例,而不是使用绕过代理的默认构造函数。
  • 关于延迟加载需要了解的一件事是它检查导航属性是否已加载。如果没有,则检查数据库以加载实体。在您的情况下,您的所有实体都处于“已添加”状态,那么这样做Reference_Name = "Reference"不会帮助您的上下文延迟加载导航属性Refererence。这就是为什么延迟加载alphatwo.Reference_Name = "Reference";不会在数据库上找到任何内容,而alphatwo.Reference = reference;不是因为reference处于状态而这样做。Added