EF Code First Edit has no effect on mapped entities - entity-framework

I am using Entity Framework Code First.
I have two entities:
public class Meal
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MealID { get; set; }
public virtual List<MealCategory> MealCategories { get; set; }
}
public class MealCategory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MealCategoryID { get; set; }
public virtual List<Meal> Meals { get; set; }
}
They are mapped in the config:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Meal>()
.HasMany(c => c.MealCategories)
.WithMany(x => x.Meals)
.Map(a =>
{
a.ToTable("MealCategoryMapping");
a.MapLeftKey("MealID");
a.MapRightKey("MealCategoryID");
});
}
When adding a new meal with an existing Category it works perfectly, the ids are correctly inserted in the mapping table
Meal Meal = new Meal();
Meal.MealCategories = new List<MealCategory>();
var categoryToBeAdded = (from x in Context.MealCategoreis where x.MealCategoryID == 1 select x).FirstOrDefault();
Meal.MealCategories.Add(categoryToBeAdded);
MealDataContext.Add(Meal);
MealDataContext.SaveChanges();
However, if I try to edit the meal and add new categories nothing happen in the mapping table. The old entries stay the same and the new are not added.
Meal Meal = (from x in MealDataContext where x.MealID == 1 select x).FirstOrDefault();
Meal.MealCategories = new List<MealCategory>();
var categoryToBeAdded = (from x in Context.MealCategoreis where x.MealCategoryID == 4 select x).FirstOrDefault();
Meal.MealCategories.Add(categoryToBeAdded);
MealDataContext.Attach(entityToUpdate);
MealDataContext.Entry(entityToUpdate).State = EntityState.Modified;
MealDataContext.SaveChanges();
No Exception or somethink is thrown.
Can you please tell me how I can remove all old mapping entries and add new one when editing?

Try this. The call to Collection("MealCategories").Load(); should get EF to create a tracked collection.
Meal Meal = (from x in MealDataContext where x.MealID == 1 select x).FirstOrDefault();
MealDataContext.Attach(entityToUpdate);
MealDataContext.Entry(entityToUpdate).State = EntityState.Modified;
MealDataContext.Entry(entityToUpdate).Collection("MealCategories").Load();
var categoryToBeAdded = (
from x in Context.MealCategories
where x.MealCategoryID == 4
select x).FirstOrDefault();
Meal.MealCategories.Add(categoryToBeAdded);
MealDataContext.SaveChanges();

First let me say that you can use Find to fetch an object from the database (or the DbContext cache if it's already present in the local collection):
Meal meal = MealDataContext.Meals.Find(4);
// (lower case variable name is conventional)
You should realize that the meal object at this point has a MealCategories collection that's tracked by Entity Framework. You don't have to, no, should not replace it by a new one. If the collection is loaded at that point, replacing it cuts the categories from the owner meal and when you save changes the junction records are deleted!
In your case the collection is not loaded, so this does not happen.
It is not entirely clear to me why the category that you add to the MealCategories collection is not saved. Maybe because it seems to come from a different context (or is that a typo?). But that should throw an exception. Anyway, make sure that
You do not replace the MealCategories collection, and
Add a category to it that is fetched by the same context instance as the meal
and you should be OK.

Related

Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF - EF Core many-to-many (child) data

Following through Julie Lerman's Pluralsight course EF Core 6 Fundamentals I've created two classes in my own project (my own design, but identical to the course in terms of class structure/data hierarchy):
Class 1: Events - To hold information about an event being held (e.g. a training course), with a title and description (some fields removed for brevity):
public class EventItem
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EventItemId { get; set; }
[Required(AllowEmptyStrings = false)]
public string EventTitle { get; set; }
public string? EventDescription { get; set; }
[Required]
public List<EventCategory> EventCategories { get; set; } = new();
}
Class 2: Event categories - Each event can be linked to one or more pre-existing (seeded) categories (e.g. kids, adult).
public class EventCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EventCategoryId { get; set; }
[Required]
public string EventCategoryName { get; set; }
public List<EventItem>? EventItems { get; set; }
}
In my Razor form to create the event, the user can select from multiple categories. Using EF Core I take the posted data (via a VM/DTO object) and construct the relevant parent/child entities. However upon saving to the database I get an exception as EF Core tries to re-create the categories when they already exist:
Cannot insert explicit value for identity column in table
'EventCategories' when IDENTITY_INSERT is set to OFF.
My code explicitly looks up the existing categories selected by the user, but the context tracker appears to still believe they need inserting, in addition to creating the many-to-many relationship.
I'd appreciate any input as to why this is happening please:
using (var dbcontext = DbFactory.CreateDbContext())
{
// Get selected categories from user's check box list
var selectedCategoryIds = _eventCagetories.Where(c => c.isSelected).Select(c => c.EventCategoryId).ToList();
// Create new Event
var newEventItem = new EventFinderDomain.Models.EventItem() {
EventTitle = _eventItemDTO.EventTitle,
EventDescription = _eventItemDTO.EventDescription,
EventUrl = _eventItemDTO.EventUrl,
TicketUrl = _eventItemDTO.TicketUrl
};
// Find categories from the database based on their ID value
var selectedEventCategories = dbcontext.EventCategories.Where(c => selectedCategoryIds.Contains(c.EventCategoryId)).ToList();
// Add the categories to the event
newEventItem.EventCategories!.AddRange(selectedEventCategories);
// Add the event to the change tracker
await dbcontext.EventItems.AddAsync(newEventItem); // <-- Created correctly with child list objects added
// Detect changes for debugging
dbcontext.ChangeTracker.DetectChanges();
var debugView = dbcontext.ChangeTracker.DebugView; // <-- Incorrectly shows newEventItem.Categories being added
// Save to database
await dbcontext.SaveChangesAsync(); // <-- Cannot insert explicit value for identity column
}
The Event entity appears to be correctly created in the debugger with its related child categories included:
The change tracker however incorrectly shows the selected categories being added again when they already exist:
After commenting out every line of code in the app and adding back in until it broke, it emerges the problem was elsewhere within Program.cs:
builder.Services.AddDbContextFactory<EventFinderContext>(
opt => opt.UseSqlServer(new SqlConnectionStringBuilder() {/*...*/}.ConnectionString)
.EnableSensitiveDataLogging()
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) // <-- THE CULPRIT
);
In the training video this method was described as a way of reducing overhead for disconnected apps. I had assumed that because of the disconnected nature of HTTP, this would be beneficial and that context would be re-established when creating the model's child data. This was incorrect on my part.
I should have used .AsNoTracking() only when retriving read-only data from my database. For example, loading in the child-data for a new model that wouldn't be modified directly, but used to create the many-to-many data (explicitly, for the category data option items only and not for the event data).

EF Model with collection and navigation to a particular element

How to configure a EF6 migration with a model class having?
A collection o items
A navigation property to one particular item
public class MyModel
{
[Key]
public int Id { get; set; }
// My collection of elements
public virtual ICollection<MyCollectionElement> MyCollection { get; set; }
// Optional navigation to a particular element from the collection
[ForeignKey("CurrentElement")]
public int? CurrentElementId { get; set; }
public virtual MyCollectionElement CurrentElement { get; set; }
}
public class MyCollectionElement
{
[Key]
public int Id { get; set; }
// Required navigation to MyClass
[ForeignKey("MyModel")]
public int MyModelID { get; set; }
public virtual MyModel Model { get; set; }
}
Configuration
modelBuilder.Entity<MyModel>()
.HasMany(x => x.MyCollection)
.WithRequired(x => x.Model)
.HasForeignKey(x => x.MyModelID)
.WillCascadadeOnDelete(false);
Throws several errors on Update-Database, like
Unable to determine a valid ordering for dependent operations.
I would like a solution which doesn't involve a boolean IsCurrent in MyCollectionElement to make another query later and find which element is the current; instead, I would like to store the current element's id with my model, like exposed.
Also, I don't mind making int CurrentElementId non nullable (required) if it's easier.
Thanks.
This chicken-and-egg problem always looms when there are circular relationships. The error...
Unable to determine a valid ordering for dependent operations.
...is not thrown when the database is created. The database can be created just fine. It occurs when you try to insert a MyModel record and a MyCollectionElement referring to one another in the same unit of work. In the Seed method you probably have something like
var element = new MyCollectionElement();
var model = new MyModel();
model.MyCollection.Add(element);
model.CurrentElement = element;
The statement model.MyCollection.Add(element); requires model to be inserted first, so element can refer to it in its foreign key. But model.CurrentElement = element; requires element to be inserted first.
You can only avoid this situation by calling SaveChanges twice, and wrapping everything in a TransactionScope if you want the assignments to be transactional:
using(var ts = new TransactionScope())
{
using(var db = new MyContext()
{
var element = new MyCollectionElement();
var model = new MyModel();
model.MyCollection.Add(element);
db.MyModels.Add(model);
db.SaveChanges();
model.CurrentElement = element;
db.SaveChanges();
}
ts.Complete();
}
This also means that int CurrentElementId should remain nullable.

EF many-to-many relationship and data duplication

I have a trouble with EF (6.1.3)
I have created next classes (with many-to-many relationship):
public class Record
{
[Key]
public int RecordId { get; set; }
[Required]
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Record> Records{ get; set; }
}
And method:
void AddTags()
{
Record[] records;
Tag[] tags;
using (var context = new AppDbContext())
{
records = context.Records.ToArray();
}//remove line to fix
tags = Enumerable.Range(0, 5).Select(x => new Tag()
{
Name = string.Format("Tag_{0}", x),
Records= records.Skip(x * 5).Take(5).ToArray()
}).ToArray();
using (var context = new AppDbContext()){ //remove line to fix
context.Tags.AddRange(tags);
context.SaveChanges();
}
}
If I use two contexts, the records (which were added to created tags) will be duplicated. If I remove marked rows - problem disappears.
Is there any way to fix this problem without using the same context?
If you can, better reload entities or not detach them at all. Using multiple context instances in application is overall making things much more complicated.
The problem for you comes from the Entity Framework entity change tracker. When you load entitites from your DbContext and dispose that context, entities get detached from entity change tracker, and Entity Framework has no knowledge of any changes made to it.
After you reference detached entity by an attached entity, it (detached entity) immediately gets into entity change tracker, and it has no idea that this entity was loaded before. To give Entity Framework an idea that this detached entity comes from the database, you have to reattach it:
foreach (var record in records) {
dbContext.Entry(record).State = EntityState.Unchanged;
}
This way you will be able to use records to reference in other objects, but if you have any changes made to these records, then all these changes will go away. To make changes apply to database you have to change state to Added:
dbContext.Entry(record).State = EntityState.Modified;
Entity Framework uses your mappings to determine row in database to apply changes to, specifically using your Primary Key settings.
A couple examples:
public class Bird
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Tree
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class BirdOnATree
{
[Column(Order = 0), Key, ForeignKey("Bird")]
public int BirdId { get; set; }
public Bird Bird { get; set; }
[Column(Order = 1), Key, ForeignKey("Tree")]
public int TreeId { get; set; }
public Tree Tree { get; set; }
public DateTime SittingSessionStartedAt { get; set; }
}
Here's a small entity structure so that you could see how it works. You can see that Bird and Tree have simple Key - Id. BirdOnATree is a many-to-many table for Bird-Tree pair with additional column SittingSessionStartedAt.
Here's the code for multiple contexts:
Bird bird;
using (var context = new TestDbContext())
{
bird = context.Birds.First();
}
using (var context = new TestDbContext())
{
var tree = context.Trees.First();
var newBirdOnAtree = context.BirdsOnTrees.Create();
newBirdOnAtree.Bird = bird;
newBirdOnAtree.Tree = tree;
newBirdOnAtree.SittingSessionStartedAt = DateTime.UtcNow;
context.BirdsOnTrees.Add(newBirdOnAtree);
context.SaveChanges();
}
In this case, bird was detached from the DB and not attached again. Entity Framework will account this entity as a new entity, which never existed in DB, even though Id property is set to point to existing row to database. To change this you just add this line to second DbContext right in the beginning:
context.Entry(bird).State = EntityState.Unchanged;
If this code is executed, it will not create new Bird entity in DB, but use existing instead.
Second example: instead of getting bird from the database, we create it by ourselves:
bird = new Bird
{
Id = 1,
Name = "Nightingale",
Color = "Gray"
}; // these data are different in DB
When executed, this code will also not create another bird entity, will make a reference to bird with Id = 1 in BirdOnATree table, and will not update bird entity with Id = 1. In fact you can put any data here, just use correct Id.
If we change our code here to make this detached entity update existing row in DB:
context.Entry(bird).State = EntityState.Modified;
This way, correct data will be inserted to table BirdOnATree, but also row with Id = 1 will be updated in table Bird to fit the data you provided in the application.
You can check this article about object state tracking:
https://msdn.microsoft.com/en-US/library/dd456848(v=vs.100).aspx
Overall, if you can avoid this, don't use object state tracking and related code. It might come to unwanted changes that are hard to find source for - fields are updated for entity when you don't expect them to, or are not updated when you expect it.

Adding a new entity to collection in attached entity causes ConcurrencyException

I have simplified the code below to show the root of the problem. My real code is using GenericRepository and UnitOfWork pattern but I get the same exception with this simplified code too.
I am using Entity Framework 6, Code First
It uses the following POCO entities
public class Order
{
public int Id {get;set;}
public virtual List<OrderProducts> OrderProducts {get;set;}
...
}
public class Product
{
public int Id {get;set;}
...
}
public class OrderProduct
{
public int OrderId {get;set;}
public int ProductId {get;set;}
public int Quantity
public virtual Order Order { get; set; }
public virtual Product Product{ get; set; }
}
The user is able to create a new product and add it to the order products on the same screen.
//Pull an order from the database:
var existingOrder = db.Orders.FirstOrDefault(x => x.Id == inputModel.OrderId);
//Iterate the OrderProductInputModels (IMs) in the Inputmodel
foreach (var orderProductIM in inputModel.OrderProductIMs )
{
var orderProduct = existingOrder.OrderProducts.SingleOrDefault(o => o.Id == orderProductIM.Id);
//if its an existing order product (already in db)
if (orderProduct != null)
{
//just update its property values
}
//if it has been added
else
{
//we need to create a new product first
var newProduct= new Product() { <set some properties> };
orderProduct= new OrderProduct()
{
Product=newProduct,
Order=existingOrder
}
//Add the OrderProduct to the order
existingOrder.OrderProducts.Add(orderProduct);
}
db.SaveChanges();
On save changes, I get the following error.
[System.Data.Entity.Infrastructure.DbUpdateConcurrencyException] = {"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."}
Why is this?
I expected entity framework to see that the existingOrders nested properties were newly added and unattached, update the order and create the new OrderProduct and Product.
Should it not be other way around in your if clause as you are checking for null ( then only it is a new order product else update. Issue is here:
//if its an existing order product (already in db)
if (orderProduct == null)
{
//just update its property values
}
//if it has been added
else
{
When you are looping around all the OrderProducts, you are constantly updating the database but the existingOrder object is not getting refreshed. Update that or add all the objects first and then update the database.
Finally solved it by creating a test project and reverse code first engineering the database. Noticed that OrderProduct entity was not generated. On inspecting the database, the primary key was not set. Once I set the primary key in the database, the issue was resolved. Thanks for all the suggestions.

Many to Many Relationships not saving

I have two entities with a fairly standard Many to Many relationship that I created in EF 5 Code First. These are Service and ServiceItem. The Service entity contains a collection of ServiceItems and the ServiceItem contains a collection of Services. I can create, change and save data to either of the entities basic properties with no problems. When I try to add a ServiceItem to a Service or a Service to a ServiceItem it seems to work, but nothing is saved. I have verified that all the proper database tables are created, including a ServiceItemService table with the cross keys. The database ServiceItemService table doesn't get any entry when I add the items. There is no error and everything else seems to work perfectly.
I am a bit stumped and could use some help. Below are the classes.
The Service class;
public class Service
{
//Default constructor
public Service()
{
//Defaults
IsActive = true;
ServicePeriod = ServicePeriodType.Monthly;
ServicePeriodDays = 0;
ServiceItems = new Collection<ServiceItem>();
}
public int ServiceID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<ServiceItem> ServiceItems { get; set; }
public string TermsOfService { get; set; }
public ServicePeriodType ServicePeriod { get; set; }
public int ServicePeriodDays { get; set; }
public bool IsActive { get; set; }
}
The ServiceItem class;
public class ServiceItem
{
public ServiceItem()
{
IsActive = true;
}
public int ServiceItemID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<Service> Services { get; set; }
public string UserRole { get; set; }
public bool IsActive { get; set; }
}
This is the Fluent mapping I did while trying to debug this issue. The same problem happened before and after adding this mapping.
public DbSet<Service> Services { get; set; }
public DbSet<ServiceItem> ServiceItems { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Service>()
.HasMany(p => p.ServiceItems)
.WithMany(r => r.Services)
.Map(mc =>
{
mc.MapLeftKey("ServiceItemID");
mc.MapRightKey("ServiceID");
mc.ToTable("ServiceItemService");
});
}
Here is the code I use to save the Service item that includes 2-3 ServiceItems in the Service.ServiceItems collection. I have carefully verified that the ServiceItems were in the proper collection.
db.Entry(dbService).State = EntityState.Modified;
db.SaveChanges();
The dbService object doesn't seem to get affected in any way. The ServiceItems are still in the proper collection, but no update are made to the ServiceItemService database table. Any advice would be very welcome.
-Thanks
It is expected that nothing happens.
What you want to change or add is a relationship between the entities Service and ServiceItem. But you cannot manipulate relationships by setting the state of an entity to Modified. This only updates scalar and complex properties but no navigation properties (= relationships). (For example setting the state of a Service entity to Modified will mark Service.Title and Service.Description, etc. as modified and ensure that those properties are saved to the database. But it doesn't care about the content of Service.ServiceItems.)
The only exception where you can change a relationship by setting the state to Modified are Foreign Key Associations. These are associations that have foreign key properties exposed in your model entity and they can only occur for one-to-many or one-to-one associations. Many-to-many relationships are always Independent Associations which means they can never have a foreign key property in an entity. (Because the FKs are in the join table, but the join table is not an entity and "hidden" from your model classes.)
There is a way to directly manipulate relationships for a many-to-many association but it requires to go down to the ObjectContext and its RelationshipManager which is - in my opinion - pretty advanced and tricky.
The usual and straight-forward way to add and remove relationship entries to/from a many-to-many association is by just adding items to and removing items from the collections while the entities are attached to the context. EF's change tracking mechanism will recognize the changes you have done and generate the appropriate INSERT, UPDATE and DELETE statements when you call SaveChanges.
The exact procedure depends on if you also want to save Service and/or ServiceItem as new entities or if you only want to add relationships between existing entities. Here are a few examples:
service should be INSERTed, all serviceItems should be INSERTed and the relationships between the entities should be INSERTed into the join table as well:
using (var context = new MyContext())
{
var service = new Service();
var serviceItem1 = new ServiceItem();
var serviceItem2 = new ServiceItem();
service.ServiceItems.Add(serviceItem1);
service.ServiceItems.Add(serviceItem2);
context.Services.Add(service);
context.SaveChanges();
}
Adding the "root" service of the object graph is enough because EF will recognize that all other entities in the graph are not attached to the context and assume that they have to be INSERTed into the database.
service already exists and should NOT be INSERTed, all serviceItems should be INSERTed and the relationships between the entities should be INSERTed into the join table as well:
using (var context = new MyContext())
{
var service = new Service { ServiceID = 15 };
context.Services.Attach(service);
var serviceItem1 = new ServiceItem();
var serviceItem2 = new ServiceItem();
service.ServiceItems.Add(serviceItem1);
service.ServiceItems.Add(serviceItem2);
context.SaveChanges();
}
EF recognizes here (when SaveChanges is called) that service is attached but the other entities are not. No INSERT for service happens but the serviceItem1/2 will be INSERTed together with the relationship entries.
service already exists and should NOT be INSERTed, all serviceItems already exist and should NOT be INSERTed, but the relationships between the entities should be INSERTed into the join table:
using (var context = new MyContext())
{
var service = new Service { ServiceID = 15 };
context.Services.Attach(service);
var serviceItem1 = new ServiceItem { ServiceItemID = 23 };
context.ServiceItems.Attach(serviceItem1);
var serviceItem2 = new ServiceItem { ServiceItemID = 37 };
context.ServiceItems.Attach(serviceItem2);
service.ServiceItems.Add(serviceItem1);
service.ServiceItems.Add(serviceItem2);
context.SaveChanges();
}
For completeness: How to remove relationships between existing entities?
using (var context = new MyContext())
{
var service = context.Services
.Include(s => s.ServiceItems) // load the existing Items
.Single(s => s.ServiceID == 15);
var serviceItem1 = service.ServiceItems
.Single(s => s.ServiceItemID == 23); // query in memory, no DB query
var serviceItem2 = service.ServiceItems
.Single(s => s.ServiceItemID == 37); // query in memory, no DB query
service.ServiceItems.Remove(serviceItem1);
service.ServiceItems.Remove(serviceItem2);
context.SaveChanges();
}
The two relationship rows in the join table that link service 15 with serviceItem 23 and 37 will be deleted.
Alternativly instead of calling Attach you can load the existing entities from the database. It will work as well:
var service = context.Services.Single(s => s.ServiceID == 15);
And the same for existing ServiceItems.