Suppose I have the following model classes in an Entity Framework Code-First setup:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
The database created from this code includes a TeamPersons table, representing the many-to-many relationship between people and teams.
Now suppose I have a disconnected Person object (not a proxy, and not yet attached to a context) whose Teams collection contains one or more disconnected Team objects, all of which represent Teams already in the database. An object such as would be created by the following, just for example, if a Person with Id 1 and a Team with Id 3 already existed in the db:
var person = new Person
{
Id = 1,
Name = "Bob",
Teams = new HashSet<Team>
{
new Team { Id = 3, Name = "C Team"}
}
};
What is the best way of updating this object, so that after the update the TeamPersons table contains a single row for Bob, linking him to C Team ? I've tried the obvious:
using (var context = new TestContext())
{
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
but the Teams collection is just ignored by this. I've also tried various other things, but nothing seems to do exactly what I'm after here. Thanks for any help.
EDIT:
So I get that I could fetch both the Person and the Team[s] from the db, update them and then commit changes:
using (var context = new TestContext())
{
var dbPerson = context.People.Find(person.Id);
dbPerson.Name = person.Name;
dbPerson.Teams.Clear();
foreach (var id in person.Teams.Select(x => x.Id))
{
var team = context.Teams.Find(id);
dbPerson.Teams.Add(team);
}
context.SaveChanges();
}
This is a pain if Person's a complicated entity, though. I know I could use Automapper or something to make things a bit easier, but still it seems a shame if there's no way of saving the original person object, rather than having to get a new one and copy all the properties over...
The general approach is to fetch the Team from the database and Add that to the Person's Teams collection. Setting EntityState.Modified only affects scalar properties, not navigation properties.
Try selecting the existing entities first, then attaching the team to the person object's team collection.
Something like this: (syntax might not be exactly correct)
using (var context = new TestContext())
{
var person = context.Persons.Where(f => f.Id == 1).FirstOrDefault();
var team = context.Teams.Where(f => f.Id == 3).FirstOrDefault();
person.Teams.Add(team);
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
That's where EF s**ks. very inefficient for disconnected scenario. loading data for the update/delete and every for re-attaching updated, one cannot just attached the updated entity to the context as an entity with the same key might already existed in the context already, in which case, EF will just throw up. what need to be done is to check if an entity with the same key is already in the context and attached or updated accordingly. it's worse to update entity with many to many relationship child. removing deleted child is from the child's entity set but not the reference property, it's very messy.
You can use the Attach method. Try this:
using (var context = new TestContext())
{
context.People.Attach(person);
//i'm not sure if this foreach is necessary, you can try without it to see if it works
foreach (var team in person.Teams)
{
context.Teams.Attach(team);
}
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
I didn't test this code, let me know if you have any problems
Related
I'm running into an issue when inserting entities with one-to-many relationships into an SQLite database using Entity Framework 6. Let's consider this example:
Model
public class Box {
public int BoxId { get; set; }
public ICollection<Item> Items{ get; set; }
}
public class Item {
public int ItemId { get; set; }
public Box Box { get; set; }
}
Insertion
Box box = new Box(){BoxId = 1};
Item item = new Item(){ItemId = 1, Box = box};
using (var db = new MyDbContext()) {
db.Boxes.Add(box);
db.SaveChanges();
db.Items.Add(item);
db.SaveChanges();
}
This snippet works.
Box box = new Box(){BoxId = 1};
Item item = new Item(){ItemId = 1, Box = box};
using (var db = new MyDbContext()) {
db.Boxes.Add(box);
db.SaveChanges();
}
using (var db = new MyDbContext()) {
db.Items.Add(item);
db.SaveChanges();
}
This snippet doesn't work, throws an Exception with message UNIQUE constraint failed
I understand why it doesn't work, but I can't figure out a way to make it work. Using the same context is not an option, because the application could be restarted between the creation of the Box and the addition of an Item. I found an answer to the same question but using EF core instead of EF6 (right there (Stack Overflow)) but I can't access the TrackGraph property of the ChangeTracker with EF6.
I would also need to have a fully generic way of solving this issue, because I could possibly have other objects in my Boxes than Items.
Okay so I figured a way.
Before calling db.SaveChanges() I execute this code, where entities is the list of entities I just added to the DbContext. This way only entities that I explicitly wanted to add are added, other related entities are not added, and thus I don't run into Unique constraint failed because DbContext is not recreating related entities.
db.ChangeTracker.Entries().ToList().ForEach(dbe => {
if (!entities.Contains(dbe.Entity)) {
dbe.State = EntityState.Unchanged;
}
});
I have created a simple one to zero/one relationship inside of code first. The code below works in that I can have a Person instance and optionally have an Account and its modeled fine in the database.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Account Account { get; set; }
}
public class Account
{
public int Id { get; set; }
public string Location { get; set; }
public virtual Person Owner { get; set; }
}
//Mapping
modelBuilder.Entity<Person>().HasOptional(x => x.Account).WithRequired(x => x.Owner);
What I would like to do is to be able to delete the optional child from the parent. I would expect this to work.
using (Context ctx = new Context())
{
var personToDeleteFrom = ctx.Persons.Single(x => x.Id == <personid>);
personToDeleteFrom.Account = null;
ctx.SaveChanges();
}
However, the child object of the relationship is simply left in the database. Is there a way to make this work? If not, what is the best practice for handling this type of relationship?
You aren't actually removing the child data just by setting the navigation property equal to null. You need to actually delete the data to get it to go away.
Just change the setting of the null to a Remove on the Accounts collection instead.
using (Context ctx = new Context())
{
var personToDeleteFrom = ctx.Persons.Single(x => x.Id == <personid>);
ctx.Accounts.Remove(personToDeleteFrom.Account);
ctx.SaveChanges();
}
This will remove the Account.
This is due to the behavior of how Entity Framework handles 1:1 relationships. EF doesn't actually add foreign key fields in the database as they are unnecessary. Instead it just maintains the relationship that the primary key for an Account always equals the primary key for the associated Person.
You can see this behavior arise if you attempt to do the following.
using (Context ctx = new Context())
{
var person = ctx.Persons.Single(x => x.Id == <personid>);
person.Account = null;
ctx.SaveChanges();
person.Account = new Account();
ctx.SaveChanges();
}
This will throw a System.Date.Core.Entity.UpdateException as it attempts to add an entry to the Accounts table with a primary key set to <personid> when one already exists.
As such, nulling out the navigation property doesn't actually do anything. The relationship is maintained by keeping the primary keys of each entity in sync. To actually remove the Account you need to delete it from the table.
What is the correct way to save a graph of objects whose state you don't know? By state I mean whether they are new or existing database entries that are being updated.
For instance, if I have:
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
public virtual ICollection<Automobile> Automobiles { get; set; }
}
public class Automobile
{
public int Id { get; set; }
public int Name { get; set; }
public short Seats { get; set; }
public virtual ICollection<MaintenanceRecord> MaintenanceRecords { get; set ;}
public virtual Person Person { get; set; }
}
public class MaintenanceRecord
{
public int Id { get; set; }
public int AutomobileId { get; set; }
public DateTime DatePerformed { get; set; }
public virtual Automobile Automobile{ get; set; }
}
I'm editing models, similar to these objects above, and then passing those models into the data layer to save, where for this instance I happen to be using entity framework. So I'm translating these models into POCO entities internal to the DAL.
It appears that unless my models have a state indicating whether they are new or updated, I have quite a bit of work to do to "Save" the changes. I have to first select the Person entity, update it, then match any existing Automobiles and update those and add any new, then for each automobile check for any new or updated maintenance records.
Is there a faster/easier way of doing this? It's possible I can keep track of the Model state, which I guess would be helpful with this, but it would mean changes to code outside of the data layer which i would prefer to avoid. I'm just hoping there is a pattern of usage out there that I can follow for updates like this.
I ran into this issue a while back and have been following this thread on the EF Codeplex site. https://entityframework.codeplex.com/workitem/864
Seems like it is being considered for the next release, I'm assuming EF 7, which apparently is a pretty large internal overhaul of EF. This may be worth checking out... http://www.nuget.org/packages/RefactorThis.GraphDiff/
Back when I was working on this I found another EF post on SO, and someone had an example of how to do this manually. At the time I decided to do it manually, not sure why, GraphDiff looks pretty cool. Here is an example of what I did.
public async Task<IHttpActionResult> PutAsync([FromBody] WellEntityModel model)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var kne = TheContext.Companies.First();
var entity = TheModelFactory.Create(model);
entity.DateUpdated = DateTime.Now;
var currentWell = TheContext.Wells.Find(model.Id);
// Update scalar/complex properties of parent
TheContext.Entry(currentWell).CurrentValues.SetValues(entity);
//We don't pass back the company so need to attached the associated company... this is done after mapping the values to ensure its not null.
currentWell.Company = kne;
// Updated geometry - ARGHHH NOOOOOO check on this once in a while for a fix from EF-Team https://entityframework.codeplex.com/workitem/864
var geometryItemsInDb = currentWell.Geometries.ToList();
foreach (var geometryInDb in geometryItemsInDb)
{
// Is the geometry item still there?
var geometry = entity.Geometries.SingleOrDefault(i => i.Id == geometryInDb.Id);
if (geometry != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(geometryInDb).CurrentValues.SetValues(geometry);
else
// No: Delete it
TheContext.WellGeometryItems.Remove(geometryInDb);
}
foreach (var geometry in entity.Geometries)
{
// Is the child NOT in DB?
if (geometryItemsInDb.All(i => i.Id != geometry.Id))
// Yes: Add it as a new child
currentWell.Geometries.Add(geometry);
}
// Update Surveys
var surveyPointsInDb = currentWell.SurveyPoints.ToList();
foreach (var surveyInDb in surveyPointsInDb)
{
// Is the geometry item still there?
var survey = entity.SurveyPoints.SingleOrDefault(i => i.Id == surveyInDb.Id);
if (survey != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(surveyInDb).CurrentValues.SetValues(survey);
else
// No: Delete it
TheContext.WellSurveyPoints.Remove(surveyInDb);
}
foreach (var survey in entity.SurveyPoints)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != survey.Id))
// Yes: Add it as a new child
currentWell.SurveyPoints.Add(survey);
}
// Update Temperatures - THIS IS A HUGE PAIN = HOPE EF is updated to handle updating disconnected graphs.
var temperaturesInDb = currentWell.Temperatures.ToList();
foreach (var tempInDb in temperaturesInDb)
{
// Is the geometry item still there?
var temperature = entity.Temperatures.SingleOrDefault(i => i.Id == tempInDb.Id);
if (temperature != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(tempInDb).CurrentValues.SetValues(temperature);
else
// No: Delete it
TheContext.WellTemperaturePoints.Remove(tempInDb);
}
foreach (var temps in entity.Temperatures)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != temps.Id))
// Yes: Add it as a new child
currentWell.Temperatures.Add(temps);
}
await TheContext.SaveChangesAsync();
return Ok(model);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
return InternalServerError();
}
This is a huge pain to me too. I extracted the answer from #GetFuzzy to a more reusable method:
public void UpdateCollection<TCollection, TKey>(
DbContext context, IList<TCollection> databaseCollection,
IList<TCollection> detachedCollection,
Func<TCollection, TKey> keySelector) where TCollection: class where TKey: IEquatable<TKey>
{
var databaseCollectionClone = databaseCollection.ToArray();
foreach (var databaseItem in databaseCollectionClone)
{
var detachedItem = detachedCollection.SingleOrDefault(item => keySelector(item).Equals(keySelector(databaseItem)));
if (detachedItem != null)
{
context.Entry(databaseItem).CurrentValues.SetValues(detachedItem);
}
else
{
context.Set<TCollection>().Remove(databaseItem);
}
}
foreach (var detachedItem in detachedCollection)
{
if (databaseCollectionClone.All(item => keySelector(item).Equals(keySelector(detachedItem)) == false))
{
databaseCollection.Add(detachedItem);
}
}
}
With this method in place I can use it like this:
public void UpdateProduct(Product product)
{
...
var databaseProduct = productRepository.GetById(product.Id);
UpdateCollection(context, databaseProduct.Accessories, product.Accessories, productAccessory => productAcccessory.ProductAccessoryId);
UpdateCollection(context, databaseProduct.Categories, product.Categories, productCategory => productCategory.ProductCategoryId);
...
context.SubmitChanges();
}
However when the graph gets deeper, I have a feeling this will not be sufficient.
What your looking for is the Unit of Work pattern:
http://msdn.microsoft.com/en-us/magazine/dd882510.aspx
You can either track UoW on the client and pass it in with the DTO or have the server figure it out. Both the veritable DataSet and EF Entities have their own internal implementation of UoW. For something stand alone there is this framework, but I have never used it so have no feedback:
http://genericunitofworkandrepositories.codeplex.com/
Alternatively another option is to do real time updates with undo functionality, kind of like when you go into Gmail contacts and it saves the changes as you make them with the option to undo.
It depends HOW you are accomplishing adding/changing the entities.
I think you may be trying to do too much with an entity at any given time. Allowing editing and adding at the same time can get you into a situation where your not sure what is being done with the entity, especially in a disconnected scenario. You should only perform a single action on a single entity at a time, unless you are deleting entities. Does this seem monotonous, sure, but 99% of your users want a clean and easily understandable interface. Many time we end up making screens of our applications "god" screens where everything and anything can be done. Which 9/10 times isn't needed (YAGNI).
This way, when you edit a user, you know you are doing an update operation. If you are adding a new maintenance record, you know you are creating a new record that is attached to an automobile.
To summarize, you should limit how many operations you are making available for a single screen and make sure you provide some type of unique information for the entity so you can try to look up the entity to see if it exists.
I had the similar problem, and couldnt find my own solution. I think that problem is complex. Complete solution for updating graphs in disconected scenario with EF6 I find in extension method RefactoringThis.GraphDiff produced by Brent McKendric.
Exemple brings by author is:
using (var context = new TestDbContext())
{
// Update the company and state that the company 'owns' the collection Contacts.
context.UpdateGraph(company, map => map
.OwnedCollection(p => p.Contacts, with => with
.AssociatedCollection(p => p.AdvertisementOptions))
.OwnedCollection(p => p.Addresses)
);
context.SaveChanges();
}
See more at:
http://blog.brentmckendrick.com/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/
I've seen this question asked a few times but nothing solves my problem. I've created the most simple version I can:
public class Order
{
public int OrderId { get; set; }
public virtual List<Item> Items { get; set; } //One to many navigation property
}
public class Item
{
public int ItemId { get; set; }
public string Description { get; set; }
}
An order can have many items. Simple. Now to interact with the EF:
using (var context = new TestContext())
{
Order test = new Order();
context.Orders.Add(test);
context.SaveChanges();
test.Items.Add(new Item {Description = "test"}); // no good - test.Items is null!
}
If I do this test.Items will be null and I cannot interact with it. However if I "refresh" the context all is well:
using (var context = new TestContext())
{
context.Orders.Add(new Order());
context.SaveChanges();
}
using (var context = new TestContext())
{
Order test = context.Orders.First();
test.Items.Add(new Item {Description = "test"}); // Happy days - test.Items is NOT null :)
}
What am I missing? Or do I really need to get a fresh context every time I add an item with a one to many navigation property?
Thanks in advance, oh wise guru of ether that know-eth the truth on this subject!
You need to create your entities using DbSet.Create() when using proxy entities (the default setting).
Entity Framework Code First will create new classes at runtime that inherit from your model classes. So you're not working with your Order class, but one that inherits from it. This new implementation will override your virtual navigation properties to implement additional features (such as lazy loading).
The specific feature you require here is to notice when the collection has changed. Your class does not provide any features like this (and should not), but the proxy class does.
Now when you new Class() your model object, it will be exactly your object. It won't be the proxy entity and therefor won't have the additional features. DbSet.Create() on the other hand will return the proxy entity, upcasted to your class, so you're working with the proxy.
I am using Entity Framework 4.3 Code First, and I have problem with updating many-to-many relationships.
I defined the following classes:
public abstract class Entity
{
[Column(Order = 0)]
[Key]
public int Id { get; set; }
[Timestamp]
[Column(Order = 1)]
public byte[] Version { get; set; }
}
public class Video : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public TimeSpan Length { get; set; }
public virtual ICollection<Coworker> Coworkers { get; set; }
}
public class Coworker : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
When the database is created, the schema look right:
There is a Videos, Coworkers and VideoCoworkers table too, without
I use repository pattern in an N-Tier application to access database, my Insert and Update method looks like this:
public T Insert(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
session.Context.Set<T>().Add(entity);
}
}
public T Update(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<T>().Attach(entity);
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
When I update an entity, I create the entity object from a DTO, that's why use DbSet.Attach instead of selecting it and updating the properties one-by-one.
When I initialize the database, I add some test data:
Create 3 Coworkers, where I set first and last name. (A, B, C)
Create 3 Videos, where I set title, description and length, and also set some coworkers. First video has A,B, second has B,C and third has A,C.
When I list the Videos from code, I can see that Video.Coworkers collection is filled with good values, and when I query the link table (VideoCoworkers) in SQL Server Management Studio, it also looks good.
My problem is
when I update for example the title of the Video, it works. But when I try to delete from Video2 the existing coworkers (B and C), and try to add coworker A, then the relationship is not updated. It also does not work when I only try to add new coworker, or only try to delete one. I create the entity which is used as the parameter of the Update() method by creating a new Video entity with a new collection of Coworkers (which are selected from the database with Find() method by Id).
What is the correct way to update many-to-many relationships?
But when I try to delete from Video2 the existing coworkers (B and C),
and try to add coworker A, then the relationship is not updated.
Without using a generic repository the correct procedure would be:
using (var session = new DatabaseSession())
{
video2 = session.Context.Set<Video>().Include(v => v.Coworkers)
.Single(v => v.Id == video2Id);
coworkerA = new Coworker { Id = coworkerAId };
session.Context.Set<Coworker>().Attach(coworkerA);
video2.Coworkers.Clear();
video2.Coworkers.Add(coworkerA)
session.Context.SaveChanges();
}
The essential part is that you must load or attach the entity in its original state, change the entity, i.e. remove and add children, and then save the changes. EF's change detection will create the necessary INSERT and DELETE statements for the link table entries. The simple procedure to set the state to Modified you are trying in your generic Update method is suited only for updating scalar properties - like changing the video title - but won't work for updating relationships between entities.
For solve this problem:
attach the entity to context
load the collection(the collection is not loaded, because )
change the state of entity to modified
save changes
So your code for update should be like this:
public Video Update(Video entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<Video>().Attach(entity);
session.Context.Entry(entity).Collection(p => p.Coworkers).Load();
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
Please refer here to see how to save master detail in asp.net mvc with database first. Hopefully it will give you the idea about the code first. You may also have a look at knokout.js example