I have a one to many relationship with two tables, Parent with many Child.
I create a parent and add children to it. Then I either create it (if it's a new parent) or update it (if it exists already.) When I create it, everything works properly. However, if I update it, the children don't update.
using (var Repo = new ParentRepository(context))
{
var key = new AnnualFormKey(prnt.Year, prnt.UserId);
if (Repo.Retrieve(key) == null)
{
prnt.CreatedDate = DateTime.Now;
prnt.CreatedId = 1;
Repo.Create(prnt);
Repo.SaveChanges(); //creates parent and children
}
else
{
prnt.UpdatedDate = DateTime.Now;
prnt.UpdatedId = 2;
Repo.Update(prnt);
Repo.SaveChanges(); //updates parent but not children
}
}
(Note: Update calls _context.Entry(orginal).CurrentValues.SetValues(entity)
Is this a problem with my context or do I need to do something else?
Leave out your Repo.Update(prnt) call unless you're specifically detaching the object in Repo.Retrieve. It will already be being tracked.
Okay, I looked this up more in depth and discovered that Entity Framework doesn't actually update complex entities (as in, it won't save the children). There are lots of complicated workarounds, but mine was very simple. I just deleted the existing entity and created it again (using the updated version).
Related
I am using Entityframework 6, I am trying to insert a parent-child kind of data in the database.
I am using Entityframework.BulkInsert to insert data. I have autoIncrement int primary key in all the tables
My object is as follows :
var parentObjects= new List<parentObject>();
var childObjects= new List<childObject>();
for (int i = 0; i <= 100; i++)
{
var parentObj= new parentObject()
{
Name="p1",
Address="a1"
};
childObjects= SeedInitializer.ChildItems.OrderBy(x => new Random().Next()).Take(2).ToList();//this gets 2 child objects
foreach (var childObj in childObjects)
{
childObj .ParentObject= parentObj;
//childObj .CommissionPlanId = i; //tried this still not working
parentObj.ChildObjects.Add(childObj );
}
parentObjects.Add(parentObj);
}
//when I do a quickwatch on parentObjects, i see child objects in each parentObject, but
//with the last id of parentObject
context.BulkInsert(parentObjects, 1000);
context.SaveChanges();
On save only 2 records are created in the childObject are created with a wrong parentObject id i.e. 0
I am not able to understand why child items are not getting created, while parent objects are getting created. Can someone help me understand where I am doing the mistake ?
Disclaimer: I'm the owner of EntityFramework.BulkInsert
You cannot.
This feature has never been implemented.
Disclaimer: I'm the owner of Entity Framework Extensions
However, this new library (not free), can easily handle this kind of scenario.
The BulkSaveChanges work exactly like SaveChanges (handle parent/child) but way faster!
All methods are supported:
Bulk SaveChanges
Bulk Insert
Bulk Delete
Bulk Update
Bulk Merge
Example
// Easy to use
context.BulkSaveChanges();
// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);
I do not think there is an easy way to accomplish this task, because in order to insert the children, you have to actually finish inserting the parents and get their ids. Normal EF inserts have the advantage that each INSERT will also embed a SELECT to fetch just generated identifier, so that it can use to push it for children (if any).
One possible solution is the following:
Add a Guid RefProperty to the ParentObject type which is also persisted
Add a Guid BatchId to the ParentObject type which is also persisted
Add a Guid RefProperty to the ChildObject type which is not persisted
Save the whole structure by using the following (mainly pseudocode) sequence
var batchId = new Guid();
parentObjects.ForEach(item => item.BatchId = batchId);
// set RefProperty for all parents and children to reflect proper parentation
TransactionScope scope = null;
try
{
context.BulkInsert(parentObjects, 1000);
var newParents = context.ParentObjects.Where(_ => _.BatchId = batchId);
var refPropMap = newParents.ToDictionary(_ => _.RefProperty, _ => ParentId);
var childObjects.ForEach(item => item.ParentId = refPropMap[item.RefProperty]);
context.BulkInsert(childObjects, 1000);
DataAccess.SaveChanges();
scope.Complete();
}
catch (Exception exc)
{
scope?.Dispose();
}
Note: this is not tested
This is quite ugly, but it should do the trick: minimize round-trips to SQL Server and still be one single transaction.
In order to make the SELECT faster, an index on ParentObject table should be placed on BatchId including (covering) its key.
Alternative: change design for these tables to not use auto-increments, but UNIQUEIDENTIFIER columns. This way, all identifiers can be set before making the inserts.
I have read quite a few posts about this, and I can't see how my situation is different, but it must be because it still doesn't update.
Basically, my method receives a detached entity in a message. I check it's key to see if it already exists. If it does not exist I add it to the database. (this works fine) If it exists I would like to update its values.
Here is my code:
InteropObject clientObject = (InteropObject)message.ItemToAddUpdate;
bool exists = context.InteropObjects.Any(o => o.GUID == clientObject.GUID);
if (!exists)
{
context.InteropObjects.AddObject(clientObject);
}
else
{
context.Attach(clientObject);
context.ObjectStateManager.GetObjectStateEntry(clientObject).SetModified();
}
context.SaveChanges();
thanks for the help!
The problem is that in the old ObjectContext API, setting the state to Modified does not set the properties of the entity to Modified. In the DbContext API this is fixed internally by a call that does do that.
In the ObjectContext API, you can get the same effect by setting the properties of the attached entity:
context.Attach(clientObject);
var ose = context.ObjectStateManager.GetObjectStateEntry(clientObject);
// Obtain an object array containing current values
var values = new object[ose.CurrentValues.FieldCount];
ose.CurrentValues.GetValues(values);
// "Overwrite" CurrentValues by these values:
ose.CurrentValues.SetValues(values);
So you set the properties by the values they already have, but this triggers the state manager to mark the property as Modified.
I want to use EF DbContext/POCO entities in a detached manner, i.e. retrieve a hierarchy of entities from my business tier, make some changes, then send the entire hierarchy back to the business tier to persist back to the database. Each BLL call uses a different instance of the DbContext. To test this I wrote some code to simulate such an environment.
First I retrieve a Customer plus related Orders and OrderLines:-
Customer customer;
using (var context = new TestContext())
{
customer = context.Customers.Include("Orders.OrderLines").SingleOrDefault(o => o.Id == 1);
}
Next I add a new Order with two OrderLines:-
var newOrder = new Order { OrderDate = DateTime.Now, OrderDescription = "Test" };
newOrder.OrderLines.Add(new OrderLine { ProductName = "foo", Order = newOrder, OrderId = newOrder.Id });
newOrder.OrderLines.Add(new OrderLine { ProductName = "bar", Order = newOrder, OrderId = newOrder.Id });
customer.Orders.Add(newOrder);
newOrder.Customer = customer;
newOrder.CustomerId = customer.Id;
Finally I persist the changes (using a new context):-
using (var context = new TestContext())
{
context.Customers.Attach(customer);
context.SaveChanges();
}
I realise this last part is incomplete, as no doubt I'll need to change the state of the new entities before calling SaveChanges(). Do I Add or Attach the customer? Which entities states will I have to change?
Before I can get to this stage, running the above code throws an Exception:
An object with the same key already exists in the ObjectStateManager.
It seems to stem from not explicitly setting the ID of the two OrderLine entities, so both default to 0. I thought it was fine to do this as EF would handle things automatically. Am I doing something wrong?
Also, working in this "detached" manner, there seems to be an lot of work required to set up the relationships - I have to add the new order entity to the customer.Orders collection, set the new order's Customer property, and its CustomerId property. Is this the correct approach or is there a simpler way?
Would I be better off looking at self-tracking entities? I'd read somewhere that they are being deprecated, or at least being discouraged in favour of POCOs.
You basically have 2 options:
A) Optimistic.
You can proceed pretty close to the way you're proceeding now, and just attach everything as Modified and hope. The code you're looking for instead of .Attach() is:
context.Entry(customer).State = EntityState.Modified;
Definitely not intuitive. This weird looking call attaches the detached (or newly constructed by you) object, as Modified. Source: http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx
If you're unsure whether an object has been added or modified you can use the last segment's example:
context.Entry(customer).State = customer.Id == 0 ?
EntityState.Added :
EntityState.Modified;
You need to take these actions on all of the objects being added/modified, so if this object is complex and has other objects that need to be updated in the DB via FK relationships, you need to set their EntityState as well.
Depending on your scenario you can make these kinds of don't-care writes cheaper by using a different Context variation:
public class MyDb : DbContext
{
. . .
public static MyDb CheapWrites()
{
var db = new MyDb();
db.Configuration.AutoDetectChangesEnabled = false;
db.Configuration.ValidateOnSaveEnabled = false;
return db;
}
}
using(var db = MyDb.CheapWrites())
{
db.Entry(customer).State = customer.Id == 0 ?
EntityState.Added :
EntityState.Modified;
db.SaveChanges();
}
You're basically just disabling some extra calls EF makes on your behalf that you're ignoring the results of anyway.
B) Pessimistic. You can actually query the DB to verify the data hasn't changed/been added since you last picked it up, then update it if it's safe.
var existing = db.Customers.Find(customer.Id);
// Some logic here to decide whether updating is a good idea, like
// verifying selected values haven't changed, then
db.Entry(existing).CurrentValues.SetValues(customer);
Here's what I'd like to do:
var myCustomer = new Customer();
myCustomer.Name = "Bob";
myCustomer.HasAJob = true;
myCustomer.LikesPonies = false;
Then I'd like to pass it into an update method:
public UpdateCustomer(Customer cust)
{
using(var context = dbcontext())
{
var dbCust = context.Customers.FirstOrDefault(c => c.Name == cust.Name);
if(dbCust != null)
{
// Apply values from cust here so I don't have to do this:
dbCust.HasAJob = cust.HasAJob;
dbCust.LikesPonies = cust.LikesPonies
}
context.SaveChanges();
}
}
The reason for this is I'm working in multiple different parts of my application, and/or across DLLs. Is this possible?
EDIT: Found this question to be immensely useful:
Update Row if it Exists Else Insert Logic with Entity Framework
If you are sure that the entity is in the database and you have key you would just Attach the object you have to the context. Note that attached entities are by default in Unchanged state as the assumption is that all the values of properties are the same as in the database. If this is not the case (i.e. values are different) you need to change the state of the entity to modified. Take a look at this blog post: http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx it describes several sceanrios including the one you are asking about.
I have an entity with self reference (generated by Entity Designer):
public MyEntity: EntityObject
{
// only relevant stuff here
public int Id { get...; set...; }
public MyEntity Parent { get...; set...; }
public EntityCollection<MyEntity> Children { get...; set...; }
...
}
I've written a stored procedure that returns a subtree of nodes (not just immediate children) from the table and returns a list of MyEntity objects. I'm using a stored proc to avoid lazy loading of an arbitrary deep tree. This way I get relevant subtree nodes back from the DB in a single call.
List<MyEntity> nodes = context.GetSubtree(rootId).ToList();
All fine. But when I check nodes[0].Children, its Count equals to 0. But if I debug and check context.MyEntities.Results view, Children enumerations get populated. Checking my result reveals children under my node[0].
How can I programaticaly force my entity context to do in-memory magic and put correct references on Parent and Children properties?
UPDATE 1
I've tried calling
context.Refresh(ClientWins, nodes);
after my GetSubtree() call which does set relations properly, but fetches same nodes again from the DB. It's still just a workaround. But better than getting the whole set with context.MyEntities().ToList().
UPDATE 2
I've reliably solved this by using EF Extensions project. Check my answer below.
You need to assign one end of the relationship. First, divide the collection:
var root = nodes.Where(n => n.Id == rootId).First();
var children = nodes.Where(n => n.Id != rootId);
Now, fix up the relationship.
In your case, you'd do either:
foreach (var c in children)
{
c.Parent = root;
}
...or:
foreach (var c in children)
{
root.Children.Add(c);
}
It doesn't matter which.
Note that this marks the entities as modfied. You'll need to change that if you intend to call SaveChanges on the context and don't want this saved.
The REAL solution
Based on this article (read text under The problem), navigation properties are obviously not populated/updated when one uses stored procedures to return data.
But there's a nice manual solution to this. Use EF Extensions project and write your own entity Materilizer<EntityType> where you can correctly set navigation properties like this:
...
ParentReference = {
EntityKey = new EntityKey(
"EntityContextName.ParentEntitySetname",
new[] {
new EntityKeyMember(
"ParentEntityIdPropertyName",
reader.Field<int>("FKNameFromSP")
)
})
}
...
And that's it. Calling stored procedure will return correct data, and entity object instances will be correctly related to eachother. I advise you check EF Extensions' samples, where you will find lots of nice things.