EDIT: It was solved by setting
aUpdate.b = null;
aUpdate.c = null;
before calling update. But this does not seem like the right way to do it.
Also, currently the way I'm saving is:
Set.Update(model);
Context.SaveChanges();
EDIT II:
Where I'm including the related entities:
protected override IQueryable<A> GetQueryWithIncludes(Include level)
{
var q = Query;
if (level == Include.Detail)
{
q = q.Include(x => x.c).ThenInclude(y => y.d);
q = q.Include(x => x.b);
}
return q;
}
My update method:
public A Update(A update)
{
// Commented is current fix
// update.b = null;
// update.c = null;
Update(update, true);
return update;
}
public void Update(Model model, bool save)
{
SetQuery();
Set.Update(model); //Set is DbSet
if (save)
{
Context.SaveChanges(); // Context is DBContext
}
}
START OF ORIGINAL THREAD:
I have a Database-first application/API. The model can be simplified(there are move navigational properties) to:
public class A
{
// some properties
public int SomeValue {get; set;}
// navigational properties
public virtual B b {get; set;}
public virtual C c {get; set;}
}
public class B
{
// some properties
// navigational properties
public virtual D d {get; set;}
}
public class C
{
//some properties
}
In my repository query to get class A, I'm including the properties B, C, D and so on.
The properties are used to create DTOs and so on.
The problem arises when trying to update an entity of class A.
The database has triggers that are used for logging.
When updating class A, there are appearing log entries(entities are unaltered) for changes to each of the navigational properties' tables. As an example, if I get an entity of A, and update the value of SomeValue, there will be changes to the tables for B, C and D.
The classes B, C and D in this case are never edited, and are only used to create DTOs and connecting relationships between entities.
How can I save changes to an entity of A without touching B, C, D and thus not trigger the database to make a log entry?
I have tried AsNoTracking() after the ".Include().thenInclude()..." part without success.
Related
I'm running into a strange situation:
public Class A
{
public int Id { get; set; }
}
public class B
{
public int Id { get; set; }
[ForeignKey("A1")]
public virtual int A_Id { get; set; }
public virtual A A1 { get; set; }
}
When I update an entity of type B, by modifying A1, A1.Id is updated to the new entity Id, but B.A_Id still remains assigned to the old Id. This causes Entity Framework to throw an error.
I had read that by marking both properties as virtual, EF change tracker would automatically detect the change and update the related foreign key, but this doesn't happen for me. What else can I check?
How you map the relationship between A & B will determine the behavior. You do not need to mark the A_Id as Virtual.
Provided your real schema is mapped out like above, it should just work as a typical many-to-one mapping. (Many B's can reference an A) The Key point is that the A_Id on B will not be updated until you call SaveChanges on the DbContext. Once SaveChanges is called, the FK will be updated to reflect the different A.
For instance:
using (var context = new TestDbContext())
{
var a = context.As.Single(x => x.Id == 1);
var b = context.Bs.Single(x => x.Id == 1);
Assert.AreEqual(2, b.A_id);
b.A = a;
context.SaveChanges();
Assert.AreEqual(1, b.A_Id);
}
When B was loaded it was referencing an A with ID = 2. We load A ID #1 and associate it to B using the reference. Once SaveChanges is called we can assert that B's A_Id now reflects the link to A ID 1.
Beyond that you may be encountering issues depending on how/where your A and B references are loaded. Ensure that they are coming from the same DbContext instance. A big problem I see people having is by passing references to entities around. This often leads to exceptions when trying to update references within the scope of a DbContext using entities that were loaded elsewhere.
If you are still running into issues or suspect something like above, include a copy of your exception message and actual code and we can take it from there.
Let's assume that I have an object and I would like to clear its id and all navigation properties. Is is possible by detach? If so, then how can I perform this operation in EF core?
class Car
{
int Id {get;set;}
int CarTypeId {get;set;}
[ForeignKey(nameof(CarTypeId))]
CarType CarType{get;set;}
...
}
I did something like this recently. The normal DbContext does not have a detached method, so I added one.
public void Detach<T>(T entity)
where T : class
{
Entry( entity ).State = EntityState.Detached;
}
Next I made a method to detach and reset an entity. I made it specifically for one entity, but concept is like this.
public void DetachAndResetKeys(Car entity)
{
// Load references if needed
// Detach
_dbContext.Detach( entity );
// Reset Key. 0 equals not set for int key
entity.Id = 0;
entity.CarType = null;
}
Update: To reset keys on detach. If keys can be different types, need to handle that as well. Here only int
public void Detach<T>(T entity)
where T : class
{
Entry( entity ).State = EntityState.Detached;
foreach(var prop in Entry( entity ).Properties) {
if (prop.Metadata.IsKey()) {
prop.CurrentValue = 0;
}
}
}
I have seen numerous questions and advice on calling dbContext.SaveChanges multiple Times in a transaction.
Some say this should be avoided. This in depth post is really worth a read http://mehdi.me/ambient-dbcontext-in-ef6/
In my particular scenario entity B has Id reference to Entity A
During a creation scenario i Create A and call savechanges in order to get A.Id assigned by database.
Then I create Entity B like new B(A.Id,....)
and call savechanges again. It could look like this in pseudo code
using(var tx = dbContext.BeginTransaction())
{
var a = new A();
dbContext.Add(a); //a.Id is null
dbContext.saveChanges(); // a.Id has now been initialized
var b = new B(a.Id); //I want to create b in a valid state so a.Id cannot be null
dbContext.SaveChanges();
tx.Commit();
}
(I know the pseudo code lacks exception handling logic...)
Why is this a problem ?
Is it because the savechanges cannot be rolled back?
I know I could remodel but that is not possible because we share database with some legacy systems, so the database cannot easily be changed, if possible at all!
What is an alternative solution ?
Use a navigation property like this:
public class A
{
[Key]
public int Id { get; set; }
public int BId { get; set; }
[ForeignKey("BId")]
public B B { get; set; }
}
public class B
{
[Key]
public int Id { get; set; }
}
then simply assign the newly created B to A's navigation property:
using (var transaction = dbContext.BeginTransaction())
{
var a = new A();
a.B = new B();
dbContext.Add(a);
dbContext.saveChanges();
transaction.Commit();
}
I have used EF (now with 6.1.1) for a relevant time. But every time I need to work with more than one entity inside a controller (using MVC) I lose myself.
The state management of entities causes me great confusion and when I think I understand the operation then comes another surprise.
For example (my current confusion):
I have the following classes:
public class A
{
public int Id { get; set; }
public C c { get; set; }
}
public class B
{
public int Id { get; set; }
public C c { get; set; }
}
public class C
{
public int Id { get; set; }
public string anotherProperty { get; set; }
}
And I have a Controller:
[HttpPost]
public void CopyEntityAction(A a){
var b = new B() {
c = a.c // <== here is the problem
};
db.Bs.Add(b);
}
What I want to do is copy (create another entity) C from A to B, and not link to the same C.Id
How can I achieve this?
Thanks for your time
* I think somethings EF could take care automatically
Edit 1
I tried this too:
[HttpPost]
public void CopyEntityAction(A a){
var b = new B();
var c2 = a.c;
db.Entry(c2).State = EntityState.Added;
b.c = c2;
db.Bs.Add(b);
db.SaveChanges();
}
I think you need something like this. Otherwise you are dealing with the same exact C object, not creating a new one.
var b = new B() {
c = new C { Id = a.c.Id, anotherProperty = a.c.Anotherproperty } // <== here is the problem
};
You may also need to explicitly need to add the new C to the context's C collection.
It's really not all that confusing. It boils down to where that data comes from. When you retrieve an object from Entity Framework it is "attached" to your context. However, when you post data to an action and have the modelbinder new up an instance of your entity with that data (via including it as a parameter), it is not attached. That means Entity Framework knows nothing about this object. It doesn't know where it came from, whether it's ever been saved before or not (is this going to be an update or an insert?), etc. So, it's on you to tell it what to do at that point. That is what the EntityState enum is for.
I am just starting to teach myself Entity Framework (6) from the code-first scenario. My problem is I don't understand how to correctly instruct EF that certain entities may be shared among other entities.
Here's a very simple scenario with a three-level tree structure. Class A objects owns class B objects, which owns, or references, class C objects:
public class A
{
[Key]
public string Id { get; set; }
public ICollection<B> Bees { get; set; }
public override string ToString() { return Id; }
}
public class B
{
[Key]
public string Id { get; set; }
public ICollection<C> Cees { get; set; }
public override string ToString() { return Id; }
}
public class C
{
[Key]
public string Id { get; set; }
public override string ToString() { return Id; }
}
I now create a scenario where a single A object owns five B objects which, in turn, shares a collection of five C objects. Here's the DbContext I've used:
public class Context : DbContext
{
public DbSet<A> As { get; set; }
public DbSet<C> Cs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// SHOULD I INSTRUCT EF HOW TO HANDLE THIS HERE?
base.OnModelCreating(modelBuilder);
}
public Context(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
And here's some simple code to set up this scenario:
internal class Program
{
private static void Main(string[] args)
{
var context = new Context(#"Data Source=(localdb)\v11.0; Initial Catalog=TestEF6.Collections; Integrated Security=true");
var sharedCees = createCees("A1.B1", 5, context);
context.As.Add(new A { Id = "A1", Bees = createBees("A1", 5, sharedCees) });
context.SaveChanges();
}
private static ICollection<B> createBees(string aId, int count, ICollection<C> cees = null)
{
var list = new List<B>();
for (var i = 0; i < count; i++)
{
var id = aId + ".B" + (i + 1);
list.Add(new B { Id = id, Cees = cees ?? createCees(id, count) });
}
return list;
}
private static ICollection<C> createCees(string bId, int count, Context saveInContext = null)
{
var list = new List<C>();
for (var i = 0; i < count; i++)
{
var id = bId + ".C" + (i+1);
var c = new C {Id = id};
list.Add(c);
if (saveInContext != null)
saveInContext.Cs.Add(c);
}
if (saveInContext != null)
saveInContext.SaveChanges();
return list;
}
}
As EF creates the database ('TestEF6.SharedEntities') it assumes that A -> B, as well as B -> C are one-to-many relationships and that the child entities are components, fully owned and controlled by its master entity.
So, how do I make EF "see" that instances of C can be shared by instances of B?
In all honesty, I'm asking to get a quick start into EF. I do realize I can plow through the web and figure this out but if anyone can point me in the right direction I would very much appreciate it.
Cheers
Gert set me on track by suggesting I'd add a collection property to the C class. Indeed, this is enough for EF/CF to automatically recognize the many-to-many relationship between B and C. But the whole point with code-first is to design a domain model of classes and then have EF persist them, without having to bog them down with peristency information.
I have no issues with annotating Key properties, or even Required ones. This kind of information actually makes sense in the domain model. They can be used by the domain code, but even if they're not they add clarityto the code. But to add properties that serves no other function than to act as hints to EF's auto-magic didn't seem like a very elegant solution.
So I experimented some more with the fluent API and realized I had missed the parameterless overload of .WithMany(). I was looking for a generic form, that would make it possible to specify the other type of a many-to-many relationship. In my mind I would then write: modelBuilder.Entity<B>.HasMany(b => b.Cees).WithMany<B>(). As it turned out, the parameterless overload of WithMany() serves this exact purpose, as type C in this scenario is already specified by the first HasMany() part.
So, to summarize, EF will recognize the many-to-many relationship between classes B and C, without the need to "litter" the C clas with an extra generic collection of type B items. So, this is how we can describe the many-to-many relationsship between the two classes without having to touch the domain classes at all:
public class Context : DbContext
{
public DbSet<A> As { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasMany(b => b.Cees).WithMany(); // <-- THE SOLUTION
base.OnModelCreating(modelBuilder);
}
public Context(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
I realize this is very basic stuff to anyone seasoned in EF but I'll go ahead and mark this as an answer so that others may save some time looking for this information in the future.
Thanks