实体框架自动外键填充
本文关键字:填充 框架 实体 | 更新日期: 2023-09-27 18:24:07
是否有任何方法可以强制实体框架在将实体添加到上下文时立即填充外键,而不是将其延迟到上下文发生其他事情?当使用数据绑定来显示引用的实体时,这种默认行为没有太大帮助。
仅从上下文引用任何DbSet
就足以迫使EF填充所添加的Children
的Parent
和Parent_Name
。但CCD_ 5似乎迫使EF填充CCD_ 6或CCD_ 7。
我真的很想用[Required]
属性标记Reference_Name
,这样它在数据库中就会是Not Null
,但如果我这样做,我在尝试调用SaveChanges
时会收到验证错误,除非我明确设置了Reference_Name
,即使如果设置了Reference,SaveChanges
本身也会正确填充Reference_Name
。
我真的希望能够设置Reference
或Reference_Name
中的任何一个,并能够立即使用另一个。同样,我希望在添加Child
对象后能够立即使用Parent
或Parent_Name
,而不必使用先从上下文访问其他元素的杂烩。
有人能帮助我理解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)
是否有任何方法可以强制实体框架在将实体添加到上下文时立即填充外键,而不是将其延迟到上下文发生其他事情?
选项1:
如果您只想修复实体之间的关系,而不将它们保存到数据库中,因此调用DbContext.SaveChanges()
,那么只需调用DbContext.ChangeTracker.DetectChanges()
。
选项2:
EF可以在不缩放DbContext.SaveChanges()
或DbContext.ChangeTracker.DetectChanges()
的情况下自动修复实体之间的关系。这些实体称为代理类。代理类是动态生成的派生类型,充当实体的代理。该代理覆盖实体的一些虚拟属性,以插入钩子,以便在访问该属性时自动执行操作。默认情况下,会为DbContext
启用代理创建,除非您通过调用DbContext.Configuration.ProxyEnabled = false;
禁用了它。您不需要添加那行代码,因为您需要启用代理创建。
无论如何,在利用这个功能之前,你需要修改类上的一些内容:
- 所有属性(标量、导航、集合)都必须标记为虚拟
- 所有导航集合都必须声明为
ICollection<T>
- 所有实体的建立都必须使用CCD_ 28方法
- 所有集合实例化都不能初始化为构造函数。代理类将负责实例化,如果您不遵循这一点,将抛出异常
按照以下步骤,实体类必须如下所示:
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; }
}
您的点击事件处理程序执行情况如下:
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代理的实例,而不是使用默认的构造函数来传递代理
- 关于LazyLoading需要知道的一点是,它检查导航属性是否已加载。如果没有,则检查数据库以加载实体。在您的情况下,所有实体都处于Added状态,那么执行
Reference_Name = "Reference"
将无助于上下文延迟加载导航属性Refererence
。这就是为什么我没有执行alphatwo.Reference_Name = "Reference";
,而是执行alphatwo.Reference = reference;
,因为reference
处于Added
状态,Lazy Load在数据库中找不到任何内容