I'm sure this was asked before, I don't know what to search for, so it's probably duplicate.
I have code that adds new entity to database. This entity has reference to another entity(Role), and I get it via service. Service creates another instance of dbContext, so I have to attach role to the context after I fetch it. The problem is, when I try to attach two same roles, I get this exception:
'Role' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'
How should I do it? Code below:
using (var context = new TenantContext(schemaName, connectionString))
{
ApprovalTemplates templates = new ApprovalTemplates();
ApprovalTemplate template = new ApprovalTemplate();
template.Approvers = new List<StageTemplate>();
foreach (var stage in request.Stages)
{
var temp = new StageTemplate();
temp.Order = stage.Order;
temp.Name = stage.Name;
var role = roleService.GetById(stage.RoleId, schemaName);//here I get the role
temp.AvailableActions = new List<ApprovalActionTemplate>();
foreach (var actionId in stage.Actions)
temp.AvailableActions.Add(context.ApprovalActions.First(a => a.Id == actionId));
//when I try to add already attached role, exception is thrown
context.TenantRoles.Attach(role);
temp.Role = role;
template.Approvers.Add(temp);
}
templates.PRApprovalTemplate = template;
context.ApprovalTemplates.Add(templates);
context.SaveChanges();
}
I would share potential approach for this and similar cases with Attach - the rule is very simple, you should never attach Entity with the same Id twice. Good point that there is an easy way to check if it's already attached and if it's attached, you can just use that entity, so best way is to always check local entities before attaching any Entity.
For your case in place of
var role = roleService.GetById(stage.RoleId, schemaName);//here I get the role
it may be:
var localRole = context.Set<TenantRole>().Local.FirstOrDefault(entry => entry.Id.Equals(stage.RoleId));
if (localRole == null)
{
localRole = new TenantRole
{
Id = stage.RoleId,
};
Context.TenantRoles.Attach(localRole);
}
...
temp.Role = localRole;
Because if you know RoleId, you do not need to make DB call just to attach TenantRole to the Context.
Given code works fine, but once someone have many-many places like this, it's becomes to heavy. Potential solution for this would be creating extension method for your Context:
public static class RepositoryExtensions
{
public static T LocalContextEntitiesFinder<T>(this TenantContext context, Guid id) where T : class, ISomeInterfaceThatAllYourDBModelsImplements, new()
{
var localObj = context.Set<T>().Local.FirstOrDefault(entry => entry.Id.Equals(id));
if (localObj != null)
{
return localObj;
}
localObj = new T
{
Id = id
};
context.Set<T>().Attach(localObj);
return localObj;
}
}
So you will be able to re-write your code to something like:
...
temp.Role = context.LocalContextEntitiesFinder<TenantRole>(id: stage.RoleId);
...
To make it work, you should add interface ISomeInterfaceThatAllYourDBModelsImplements similar to this (in place of Guid you can use any other type you like):
public interface ISomeInterfaceThatAllYourDBModelsImplements
{
public Guid Id { get; set; }
}
And update TenantRole
public class TenantRole: ISomeInterfaceThatAllYourDBModelsImplements
{
[Key]
public Guid Id { get; set; }
...
I hope this may help somebody.
Related
Usually, I create new database entites with dbContext.MyClass.Create(). In my current application, I need to create such an entity from within an extension method of another model class.
I have the two classes DataEntry and WorkSchedule, where one DataEntry can contain multiple WorkSchedules. Therefore, I added a method DataEntry.FillOrUpdateFromWCF(), which calls a web service and updates some fields. So far, so good.
This method also needs to create new WorkSchedules for this same DataEntry in some cases. The problem is, that I, as far as I know, have no reference to the current DataEntry's database context. Sure, I could just create them with new WorkSchedule(), but that would not update them after saving, right?
So is there something like a this.ThisEntitysDatabaseContext.WorkSchedule.Create() method from within the DataEntry class?
public partial class DataEntry {
public async Task FillOrUpdate() {
WcfData[] data = GetSomeDataFromWCF();
foreach(WcfData wd in data) {
WorkSchedule ws = this.PathToContext.WorkSchedule.Create();
ws.Stuff = "test";
this.WorkSchedules.Add(ws);
}
}
}
Sure, I could just create them with new WorkSchedule(), but that would not update them after saving, right?
It would, as long as you attach them to a tracked entity. You really don't want access to the DbContext in an entity class, that's an antipattern. You also should reconsider whether you want your entity classes to contain any logic, but that's up for debate.
So if you have something like this:
public class DataEntry
{
public ICollection<WorkSchedule> Schedules { get; set; }
public void DoWork()
{
Schedules.Add(new WorkSchedule
{
Start = DateTime.Now
});
}
}
Then this will add the proper record and foreign key (assuming that's all set up properly):
using (var db = new YourContext())
{
var dataEntry = db.DataEntries.Single(d => d.Id == 42);
dataEntry.DoWork();
db.SaveChanges();
}
Actually, you don't need to ask the DbContext to Create a DataEntry for you. In fact, this is quite uncommon.
Usually you create the object using new, then fill all the properties, except the primary keys and Add the object to the dbContext
using (var dbContext = new MyDbContext())
{
DataEntry entryToAdd = new DataEntry()
{
// fill the properties you want, leave the primary key zero (default value)
Name = ...
Date = ...
WorkShedules = ...
};
// add the DataEntry to the database and save the changes
dbContext.Add(entryToAdd);
dbContext.SaveChanges();
}
For the WorkSchedules you can use your own function, but you can also assign a value using operator new:
WorkSchedules = new List<WorkSchedule>()
{
new WorkSchedule() {Name = "...", ...},
new WorkSchedule() {Name = "...", ...},
new WorkSchedule() {Name = "...", ...},
},
Note: do not fill the primary key of the work schedule, nor the foreign key of the DataEntry that this Workschedule belongs to, after all, you don't know the value yet.
Entity framework is smart enough to understand the one-to-many relationship, and will add the proper items to the database, with the proper values for the foreign keys.
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/
let's say I have an application, for example a web site, where my objectcontext leaves during the time of a request. Some datas I load with EF should be cached to avoid to read in DB and improve performance.
Ok, I read my datas with EF, I put my object in cache (says AppFabric, not in memory cache), but related datas that can be lazy loaded are now null (and access to this property results in a nullreferenceexception). I don't want to load everything in one request, because it's going to be too long, so I want to keep the loading on demand and as soon as it's read, I would like to complete the cache with the new fetched datas.
Note :
only read operations, no create/update/delete.
Don't want to use second level cache like "EF Provider Wrappers" made by Jarek Kowalski
How can I do that ?
EDIT : I've built this samples with northwind database, it's working :
class Program
{
static void Main(string[] args)
{
// normal use
List<Products> allProductCached = null;
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
allProductCached = db.Products.ToList().Clone<DbSet<Products>>();
foreach (var product in db.Products.Where(e => e.UnitPrice > 100))
{
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
// try to use cache, but missing Suppliers
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
{
if (product.Suppliers == null)
product.Suppliers = db.Suppliers.FirstOrDefault(s => s.SupplierID == product.SupplierID).Clone<Suppliers>();
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
// try to use full cache
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
{
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
}
}
public static class Ext
{
public static List<Products> Clone<T>(this List<Products> list)
{
return list.Select(obj =>
new Products
{
ProductName = obj.ProductName,
SupplierID = obj.SupplierID,
UnitPrice = obj.UnitPrice
}).ToList();
}
public static Suppliers Clone<T>(this Suppliers obj)
{
if (obj == null)
return null;
return new Suppliers
{
SupplierID = obj.SupplierID,
CompanyName = obj.CompanyName
};
}
}
The problem is that I have to copy everything (without missing a property) and test everywhere if the property is null and load the needed property. My code is of course more and more complex, so that will be a problem if I miss something. No other solution ?
You cannot access the database in EF without an ObjectContext or a DbContext.
You can still use caching effectively, even if you don't have the original context any more.
Maybe your scenario is something like this... Imagine that you have some reference data that you use frequently. You do not want to hit the database each time you need it, so you store it in a cache. You also have per-user data that you don't want to cache. You have navigation properties from your user data to your reference data. You want to load your user data from the database, and have EF automatically "fix up" the navigation properties to point to the reference data.
For a request:
Create a new DbContext.
Retrieve reference data from the cache.
Make a deep copy of the reference objects. (You probably don't want to have the same entities attached to multiple contexts simultaneously.)
Attach each of the reference objects to the context. (e.g. with DbSet.Attach())
Execute whatever queries are required to load the per-user data. EF will automatically "fix up" the references to the reference data.
Identify newly loaded entities that could be cached. Ensure that they contain no references to entities that should not be cached, then save them to the cache.
Dispose of the context.
Cloned Objects and Lazy Loading
Lazy loading in EF is usually accomplished using dynamic proxies. The idea is that you make all properties that could potentially be loaded dynamically virtual. Whenever EF creates an instance of your entity type, it actually substitutes a derived type instead, and that derived type has the lazy loading logic in its overridden version of your properties.
This is all well and good, but in this scenario you are attaching entity objects to the context that were not created by EF. You created them, using a method called Clone. You instantiated the real POCO entity, not some mysterious EF dynamic proxy type. That means you won't get lazy loading on these entities.
The solution is simple. The Clone method must take an additional argument: the DbContext. Don't use the entity's constructor to create a new instance. Instead, use DbSet.Create(). This will return a dynamic proxy. Then initialize its properties to create a clone of the reference entity. Then attach it to the context.
Here is the code you might use to clone a single Products entity:
public static Products Clone(this Products product, DbContext context)
{
var set = context.Set<Products>();
var clone = set.Create();
clone.ProductName = product.ProductName;
clone.SupplierID = product.SupplierID;
clone.UnitProce = product.UnitPrice;
// Initialize collection so you don't have to do the null check, but
// if the property is virtual and proxy creation is enabled, it should get lazy loaded.
clone.Suppliers = new List<Suppliers>();
return clone;
}
Code Sample
namespace EFCacheLazyLoadDemo
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
class Program
{
static void Main(string[] args)
{
// Add some demo data.
using (MyContext c = new MyContext())
{
var sampleData = new Master
{
Details =
{
new Detail { SomeDetail = "Cod" },
new Detail { SomeDetail = "Haddock" },
new Detail { SomeDetail = "Perch" }
}
};
c.Masters.Add(sampleData);
c.SaveChanges();
}
Master cachedMaster;
using (MyContext c = new MyContext())
{
c.Configuration.LazyLoadingEnabled = false;
c.Configuration.ProxyCreationEnabled = false;
// We don't load the details here. And we don't even need a proxy either.
cachedMaster = c.Masters.First();
}
Console.WriteLine("Reference entity details count: {0}.", cachedMaster.Details.Count);
using (MyContext c = new MyContext())
{
var liveMaster = cachedMaster.DeepCopy(c);
c.Masters.Attach(liveMaster);
Console.WriteLine("Re-attached entity details count: {0}.", liveMaster.Details.Count);
}
Console.ReadKey();
}
}
public static class MasterExtensions
{
public static Master DeepCopy(this Master source, MyContext context)
{
var copy = context.Masters.Create();
copy.MasterId = source.MasterId;
foreach (var d in source.Details)
{
var copyDetail = context.Details.Create();
copyDetail.DetailId = d.DetailId;
copyDetail.MasterId = d.MasterId;
copyDetail.Master = copy;
copyDetail.SomeDetail = d.SomeDetail;
}
return copy;
}
}
public class MyContext : DbContext
{
static MyContext()
{
// Just for demo purposes, re-create db each time this runs.
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
}
public DbSet<Master> Masters { get { return this.Set<Master>(); } }
public DbSet<Detail> Details { get { return this.Set<Detail>(); } }
}
public class Master
{
public Master()
{
this.Details = new List<Detail>();
}
public int MasterId { get; set; }
public virtual List<Detail> Details { get; private set; }
}
public class Detail
{
public int DetailId { get; set; }
public string SomeDetail { get; set; }
public int MasterId { get; set; }
[ForeignKey("MasterId")]
public Master Master { get; set; }
}
}
Here is a sample model, different from yours, that shows how to get this working in principle.
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