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? This default behavior is not very helpful when using data binding to display referenced entities.
Just referencing any DbSet
from the context is enough to force EF to populate the Parent
and Parent_Name
of the added Children
. But nothing short of SaveChanges
seems to force EF to populate the Reference
or Reference_Name
.
I'd really like to mark Reference_Name
with the [Required]
ttribute so it will be Not Null
in the database, but if I do that, I get validation errors when I try to call SaveChanges
unless I've explicitly set the Reference_Name
, even though SaveChanges
itself will correctly populate Reference_Name
if Reference is set.
I'd really like to be able to set either Reference
or Reference_Name
and be able to immediately use the other. Likewise, I'd like to be able to immediately use Parent
or Parent_Name
after adding the Child
object, without having to use the kludge of accessing some other element from the context first.
Can anyone help me understand why EF delays these things, and how I can force it to populate the foreign key properties or the foreign key columns, preferably immediately but at least without having to call SaveChanges
? I'd really prefer not to have to fully populate all the properties explicitly when EF is going to correctly populate them anyway.
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?
Option 1:
If you just want to fix the relationalship between entities without saving them to the database hence calling DbContext.SaveChanges()
then just call DbContext.ChangeTracker.DetectChanges()
.
Option 2:
EF can automatically fixup relationalship between entites without caling DbContext.SaveChanges()
or DbContext.ChangeTracker.DetectChanges()
. Those entites are called Proxy classes. A proxy class is a dynamically generated derived type that acts as a proxy for the entity. This proxy overrides some virtual properties of the entity to insert hooks for performing actions automatically when the property is accessed. Proxy creation is enabled by default for your DbContext
unless you disabled it by calling DbContext.Configuration.ProxyEnabled = false;
. You don't need to add that line of code because you need proxy creation to be enabled.
Anyway you need to modify some things on your classes before taking advantage of this feature:
ICollection<T>
DbContext.DbSet<T>.Create()
method.Following this steps your entites classes must look like this:
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; }
}
Your click event handler impelmentation will look like this:
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);
}
There are two noticeable changes in this implementation:
Reference_Name = "Reference"
will not help your context to lazy load the navigational property Refererence
. This is why instead of doing alphatwo.Reference_Name = "Reference";
I did alphatwo.Reference = reference;
because reference
is on Added
state, Lazy Load will find nothing on database. If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With