Entity Framework - SaveChanges not reflected in database - entity-framework

SaveChanges is not being reflected in the database
I have the following code
public async Task<int> Post([FromBody] UserEntry userEntry)
{
UserEntry dbUserEntry;
using (DBContext db = new DBContext())
{
// update
dbUserEntry = this.db.UserEntries
.Where(u => u.UserEntryID == userEntry.UserEntryID)
.Include(u => u.EntryPlayers.Select(y => y.Player))
.FirstOrDefault();
dbUserEntry.TeamName = userEntry.TeamName;
dbUserEntry.EntryPlayers = userEntry.EntryPlayers;
//db.Entry(dbUserEntry).State = EntityState.Modified;
return db.SaveChanges();
}
}
I read somewhere that I need to set the state to modified but if I uncomment the line
//db.Entry(dbUserEntry).State = EntityState.Modified;
I get the error:-
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
Any ideas on how I can get SaveChanges to work?

Use Find() method instead of FirstOrDefault()
dbUserEntry = this.db.UserEntries
.Include(u => u.EntryPlayers.Select(y => y.Player))
.Find(u => u.UserEntryID == userEntry.UserEntryID)

Related

Blazor and EF Core: how to update a complex object

I'm trying some basic operations with Blazor WebAssembly and EF.
Suppose I have the following code (this is a simplified version of a more complex scenario):
var id = 1;
var q = await _db.Orders
.Include(x => x.Customer)
.Include(x => x.OrdersCarriers)
.ThenInclude(x => x.Carrier)
.Include(x => x.OrdersProducts)
.ThenInclude(x => x.Product)
.ThenInclude(x => x.Producer)
.Where(x => x.orderId == id)
.Select(x => x).SingleOrDefaultAsync();
var op = new OrderProduct()
{
OrderId = id,
Product = new Product() { ProducerId = 1, Description = "New Product" }
};
q.OrdersProducts.Add(op);
_db.Orders.Update(q);
_db.SaveChanges();
This code works fine, a new entry in Products table is created with description "New Product" and a new row in in OrdersProducts is also added.
Now I want to break this operation in three steps: order selection is done in the controller and result is passed to client app, then the client app edits the order and finally passes the order back to controller for db update operations.
I wrote this code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// [...]
services.AddDbContext<DataContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// [...]
}
OrdersController.cs
private readonly DataContext _db;
public OrdersController(DataContext db)
{
_db = db;
}
[HttpGet("getorder/{id}")]
public async Task<IActionResult> GetOrder(int id)
{
var q = await _db.Orders
.Include(x => x.Customer)
.Include(x => x.OrdersCarriers)
.ThenInclude(x => x.Carrier)
.Include(x => x.OrdersProducts)
.ThenInclude(x => x.Product)
.ThenInclude(x => x.Producer)
.Where(x => x.orderId == id)
.Select(x => x).SingleOrDefaultAsync();
if (q == null)
return BadRequest();
else
return Ok(q);
}
[HttpPut("saveorder")]
public async Task<IActionResult> SaveOrder(Order order)
{
_db.Orders.Update(order);
_db.SaveChangesAsync();
return Ok(true);
}
OrdersService.razor
public class OrdersService : IOrdersService
{
private readonly HttpClient _http;
public OrdersService(HttpClient http)
{
_http = http;
}
public async Task<Order> GetOrder(int id)
{
var r = await _http.GetFromJsonAsync<Order>($"api/Orders/getorder/{id}");
return r;
}
public async Task<bool> SaveOrder(Order order)
{
var r = await _http.PutAsJsonAsync<Order>("api/Orders/saveorder", order);
return bool.Parse(await r.Content.ReadAsStringAsync());
}
}
EditPage.razor
// [...]
private async void EditOrder()
{
var op = new OrderProduct()
{
OrderId = order.OrderId,
ProductId = 7
};
order.OrdersProducts.Add(op);
await OrdersService.SaveOrder(order);
}
This code throws the following exception in SaveOrder method (in the controller):
System.InvalidOperationException: The instance of entity type
'OrderCarrier' cannot be tracked because another instance with the
same key value for {'OrderId', 'CarrierId'} 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.
If I exclude these lines from the query:
.Include(x => x.OrdersCarriers)
.ThenInclude(x => x.Carrier)
The exception becomes:
System.InvalidOperationException: The instance of entity type
'Producer' cannot be tracked because another instance with the same
key value for {'ProducerId'} 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.
If I exclude this line from the query:
.ThenInclude(x => x.Producer)
Then no exception is thrown, but my changes are not saved anyway.
My actual object is more complex than the one I described above, so the ability to make all my changes persistent with a few instructions would be very handy!

How to load the navigation property with EF Core?

In EF6 we had such option:
context.Set<TEntity>().Attach(entity);
context.Entry(entity).Collection("NavigationProperty").Load();
Since EF Core "goes 100% strictly typed" they have removed Collection function. But what should be used instead?
Added:
I mean how to load includes/"navigation collection properties" for ATTACHED entity?
You have 3 methods:
1. Eager loading
e.g.
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ToList();
2. Explicit loading
e.g.
var blog = context.Blogs
.Single(b => b.BlogId == 1);
context.Posts
.Where(p => p.BlogId == blog.BlogId)
.Load();
3. Lazy loading (as of EF Core 2.1)
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString);
You can read more about it here : Loading Related Data
Update :
You can use TrackGraph API for that use case.Here is the link : graph behavior of Add/Attach
Another link : DbSet.Add/Attach and graph behavior
Explicit Loading was added in Entity Framework Core v1.1.
See Microsoft Docs
From the docs:
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
context.Entry(blog)
.Collection(b => b.Posts)
.Load();
context.Entry(blog)
.Reference(b => b.Owner)
.Load();
}
See below Code,
I'm inserting data in UserRef table as well another table which we have many 2 many relationships.
public void AddUser(User user, IEnumerable<UserSecurityQuestion> securityQuestion, string password)
{
var userModel = _mapper.Map<User, UserRef>(user);
userModel.CreateTime = DateTime.Now;
userModel.UserNewsLetterMaps.ToList().ForEach(u => this._context.UserNewsLetterMaps.Add(u));
this._context.RoleRefs.Attach(new RoleRef() { RoleId = (int)user.UserRole, UserRefs = new List<UserRef> { userModel } });
userModel.ResidenceStatusRefs.ToList().ForEach(u => this._context.ResidenceStatusRefs.Attach(u));
this._context.UserRefs.Add(userModel);
this.Save();
}

Clear related items followed by parent entity update - Many to Many EntityFramework with AutoMapper

I am trying to remove all references followed by adding them back from a list of disconnected objects.
using(var scope = new TransactionScope())
{
_autoIncidentService.AddNewCompanyVehicles(
autoIncidentModel.CompanyVehiclesInvolved.Where(v => v.Id == 0));
_autoIncidentService.ClearCollections(autoIncidentModel.Id);
_autoIncidentService.Update(autoIncidentModel);
scope.Complete();
}
return Json(ResponseView);
The ClearCollections removes items references. The GetAutoIncident includes the collection.
public void ClearCollections(int id)
{
var autoIncident = GetAutoIncident(id);
foreach (var vehicle in autoIncident.CompanyVehiclesInvolved.ToArray())
{
autoIncident.CompanyVehiclesInvolved.Remove(vehicle);
}
db.SaveChanges();
}
When I try to update the entity right after the ClearCollections method it fails.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
I am using a singleton to get the DbContext so there shouldn't be any situation where the context is different. It is being stored in the HttpContext.Current.Items.
The update method is as follows:
public override void Update(AutoIncidentModel model)
{
var data = GetData(model.Id);
Mapper.CreateMap<AutoIncidentModel, AutoIncident>()
.ForMember(m => m.CompanyVehiclesInvolved, opt => opt.ResolveUsing(m =>
{
var ids = m.CompanyVehiclesInvolved.Select(v => v.Id);
return db.Vehicles.Where(v => ids.Contains(v.Id)).ToList();
}));
Mapper.Map(model, data);
db.SaveChanges();
}
Obviously, I am missing something important here. Do the entites from my ResolveUsing method need to somehow be associated with the parent entity or is automapper overwriting the property (CompanyVehiclesInvolved) and causing a problem?

Entity Framework Saving many to many changes

I have a Users 1-* UserGroupLinks *-1 UserGroups Table structure and have created the following method:
public static string SaveUser(User user, List<UserGroup> newUserGroups)
{
using (var dbContext = new DCSEntities())
{
var existingUserGroups = user.UserGroups.ToList<UserGroup>();
existingUserGroups.ForEach(d => user.UserGroups.Remove(d));
newUserGroups.ForEach(a => user.UserGroups.Add(a));
dbContext.ObjectStateManager.ChangeObjectState(user, EntityState.Modified);
dbContext.SaveChanges();
return user.UserCode;
}
}
I would like to remove all the usergrouplinks for that user, and then add the new list of usergroups. When I run this method I get a violation of primary key on the UserGroups object/UsergroupLink table, indicating that my attempt at removing the existing usergrouplinks has failed. How can I resolve this error?
So I've changed the original code to confirm a suspicion.
public static string SaveUser(User user, List<UserGroup> newUserGroups)
{
using (var dbContext = new DCSEntities())
{
User dbUser = dbContext.Users.Where(u => u.UserCode == user.UserCode).Include(ug => ug.UserGroups).Include(s => s.Status).FirstOrDefault();
var existingUserGroups = dbUser.UserGroups.ToList<UserGroup>();
existingUserGroups.ForEach(d => dbUserUserGroups.Remove(d));
newUserGroups.ForEach(a => dbContext.UserGroups.Attach(a));
newUserGroups.ForEach(a => dbUser.UserGroups.Add(a));
dbContext.ObjectStateManager.ChangeObjectState(dbUser, EntityState.Modified);
dbContext.SaveChanges();
return dbUser.UserCode;
}
}
What I can confirm is that this code works when and only when adding new groups for that user. As soon as you try and add an existing group, it gives the primary key violation. It is almost as if the line
existingUserGroups.ForEach(d => dbUserUserGroups.Remove(d));
Is not taking effect.
My solution below is not elegant and therefore I have not marked it as an answer.
The idea to use 2 different dbcontexts actually worked, but as per my comment above, I don't think it is an elegant or the correct solution:
public static string SaveUser(User user, List<UserGroup> newUserGroups)
{
using (var dbContext = new DCSEntities())
{
User dbUser = dbContext.Users.Where(u => u.UserCode == user.UserCode).Include(ug => ug.UserGroups).Include(s => s.Status).FirstOrDefault();
var existingUserGroups = dbUser.UserGroups.ToList<UserGroup>();
existingUserGroups.ForEach(d => dbContext.UserGroups.Detach(d));
existingUserGroups.ForEach(d => dbUser.UserGroups.Remove(d));
dbContext.ObjectStateManager.ChangeObjectState(dbUser, EntityState.Modified);
dbContext.SaveChanges();
}
using (var dbContext = new DCSEntities())
{
User dbUser = dbContext.Users.Where(u => u.UserCode == user.UserCode).Include(ug => ug.UserGroups).Include(s => s.Status).FirstOrDefault();
newUserGroups.ForEach(a => dbContext.UserGroups.Attach(a));
newUserGroups.ForEach(a => dbUser.UserGroups.Add(a));
dbContext.ObjectStateManager.ChangeObjectState(dbUser, EntityState.Modified);
dbContext.SaveChanges();
return dbUser.UserCode;
}
}
I even went as far as to copy this solution verbatim and still got the primary key violation issue entity framework update many to many relationship: virtual or not
If anyone can explain why I need to do this with 2 different contexts I would greatly appreciate it. Thanx

How to remove the data fetch in this Entity Framework Update?

Can I restructure this query so I don't have to fetch from the database? I have tried various techniques, but none of them work.
public void Update(CartEntryViewModel entry)
using (var context = new MyContext())
{
User user = Auth.GetUser(context);
CartEntry model = context.CartEntries.Find(entry.Id);
// Change the item and update quantity
model.Item = context.Items.Find(entry.Item.Id);
model.Quantity = entry.Quantity;
context.Entries(model).EntityState = EntityState.Modified;
context.SaveChanges();
}
}
The Attach() method takes the model you already have and attaches it to the context as if it were just read from the DB. You just have to change the state so the context knows to update the corresponding row in the DB when SaveChanges() is called.
public void Update(CartEntryViewModel entry)
{
CartEntry model = new CartEntry
{
model.Id = entry.Id,
model.Item = entry.Item,
model.Quantity = entry.Quantity,
// Set other properties.
};
using (MyContext context = new MyContext())
{
context.CartEntries.Attach(model);
context.Entry(model).State = EntityState.Modified;
context.SaveChanges();
}
}