Entity Framework entity state management Confusion - c#

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.

Related

How do I keep Entity Framework from saving unmodified navigational properties

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.

Entity framework 6.0 : How can i ensure transactional integrity between two entities where one need assigned Id of the other entity?

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();
}

How to share EF6 objects in code-first

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

Reference remains null

I think i ran into a bug, it seems that EF is not handling references well after deleting and reinserting an entity. I've managed to reproduce it with the code below (assume all asserts pass except the one i talk about in the comments):
var database = new TestEntities();
// select and delete the info record
var info = database.Info.First(i => i.ID == 1);
Assert.AreEqual(1, info.MemberID);
// when i uncomment the line below the last Assert fails
// Assert.IsNotNull(info.Member);
database.Info.Remove(info);
// add it again and persist it to the database
database.Info.Add(new Info {
ID = 1,
MemberID = 1
});
database.SaveChanges();
// should not be null ? EDIT: i guess i understand this becoming null
Assert.IsNull(info.Member);
// and even here its still null
info = database.Info.First(i => i.ID == 1);
Assert.IsNull(info.Member);
Can anyone tell me whats going on here?
EDIT:
My entities are generated using database first and im using the DbContext/POCO generator.
public partial class Member
{
public Member()
{
this.Info = new HashSet<Info>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Info> Info { get; set; }
}
public partial class Info
{
public int ID { get; set; }
public int MemberID { get; set; }
public virtual Member Member { get; set; }
}
It turns out that it had nothing to do with deleting and reinserting, not really anyway. It was was so obvious ...
I was inserting using a POCO which is not eagerly loaded and does not have any lazy loading capabilities ...
The second time i queried for the same record i was expecting a proxy, but it seems that the POCO was cached by EF and that is what it returned meaning still no eager or lazy loading.
I can fix it by making sure EF doesn't retrieve the second query from cache, inserting using a proxy (var info = database.Info.Create()) or including member in the query (database.Info.Include(i => i.Member).First(i => i == 1)).
Given this
var info = database.Info.First(i => i.ID == 1);
Assert.AreEqual(1, info.MemberID);
Aren't you comparing info.ID to info.MemberID here? Isn't it possible that ID and MemberID actually are different?
Also, shouldn't you be using .SaveChanges() after
database.Info.Remove(info);
?
Also, info does not have .member available if none have been instantiated. Is there a correlating Member with MemberID equal to info.MemberId?

Speeding Up Entity Framework 4.2 POCO

I am using the Entity Framework 4.2 and I have a fairly serious performance issue. I am using the POCO approach, inheriting from DbContext and here is a small sample that explains the problem:
I have a database that has 2 tables - A and B:
A
AId (int - not null - identity - primary key)
Name (nvarchar(50) - not null)
B
BId (int - not null - identity - primary key)
SomeValue (int - not null)
AId (int - not null - foreign key connecting to AId in the table A)
A has a single row in it (1, 'Test') and B has 6000 rows (SomeValue is just a number from 0 to 5999) - all of which reference the A row via the foreign key column.
I create an edmx from the database and turn off code generation. I then create the following classes:
public class DatabaseContext : DbContext
{
public DatabaseContext(string name) : base(name)
{
Configuration.AutoDetectChangesEnabled = false;
As = Set<A>();
Bs = Set<B>();
}
public DbSet<A> As { get; private set; }
public DbSet<B> Bs { get; private set; }
}
public class A
{
public virtual int AId { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<B> Bs { get; private set; }
public void AddB(B b)
{
if (b == null)
{
throw new ArgumentNullException("b");
}
if (Bs == null)
{
Bs = new List<B>();
}
if (!Bs.Contains(b))
{
Bs.Add(b);
}
b.A = this;
}
}
public class B
{
public virtual int BId { get; set; }
public virtual A A { get; set; }
public virtual int SomeValue { get; set; }
}
Now I simply do the following:
var ctx = new DatabaseContext("ScalabilityTestEntities");
var a = ctx.As.FirstOrDefault();
a.Bs.Add(new B { SomeValue = 987 });
The last line (where I add a new B) takes something in the region of 6 seconds on my quad core, 4gb RAM 64 bit Windows 7 machine that has the database running locally.
The really bad thing is that it seems to degrade something like exponentially since if you double the number of rows in B, it takes nearer to 20 seconds!
I would really appreciate any tips to make this happen faster. Thanks very much!
The world of navigation properties can be a painful one. We essentially had to phase out their use because they cause so many performance problems behind your back (especially when you get into attaching and detaching entities, but that's a different story).
What's happening is that when you access a.Bs it loads all of the B's for that A.
In this specific case, if you don't actually need the full list of B's and you just want to add a new one, it's better to simply create a B and set its AId to a's ID.
In such a case you should disable lazy loading:
var ctx = new DatabaseContext("ScalabilityTestEntities");
ctx.Configuration.LazyLoadingEnabled = false;
var a = ctx.As.FirstOrDefault();
a.Bs = new List<B>();
a.Bs.Add(new B { SomeValue = 987 });
You have lazy loading enabled by default (which causes all 6000 Bs to be loaded when you access the collection) because your navigation collection is declared as virtual. If you never need or want to use lazy loading you should remove the virtual keyword altogether or disable lazy loading in the context constructor.

Categories

Resources