Using a Web API, I want to update one record and two m-to-n relation tables in the PUT method. For this I found the update method of Entity Framework. However, my code further down does nothing at all. Is it possible that I have fundamentally misunderstood something there?
[HttpPut("Roles/{roleID}")]
public async Task<ActionResult> UpdateRole(int roleID, Role updatedRole) {
// Validate data
if (roleID != updatedRole.ID)
return BadRequest("ID missmatch.");
if (!updatedRole.Validate(out string problems))
return BadRequest(problems);
// Apply modified relations
updatedRole.ApplyModifiedRelations();
// Setup db connection
MpaContext db = _mpaContext;
Response.RegisterForDispose(db);
// Save changes
db.Update(updatedRole);
// I've added these two afterwords, but it also doesn't update anything
if (updatedRole.HasModifiedClientIDs)
db.UpdateRange(updatedRole.ClientRoles);
if(updatedRole.HasModifiedViewerIDs)
db.UpdateRange(updatedRole.RoleViewers);
try {
await db.SaveChangesAsync();
} catch (Exception ex) {
return Problem(ex.Message);
}
return Ok();
}
public void ApplyModifiedRelations() {
if (HasModifiedClientIDs) {
ClientRoles = new List<ClientRole>(
_ClientIDs.Select(c => new ClientRole() { ClientID = c, RoleID = ID })
);
}
if (HasModifiedViewerIDs) {
RoleViewers = new List<RoleViewer>(
_ViewerIDs.Select(v => new RoleViewer() { RoleID = ID, ViewerID = v })
);
}
}
I think your object didn't exist in db context (because you get it from request). So EF can't update it.
Try this
db.Entry(updatedRole).State = EntityState.Modified;
db.SaveChanges();
I found the solution. You have to actually load the relationship before updating it. I modified my ApplyModifiedRelations method accordingly:
public async Task ApplyModifiedRelations(MpaContext db) {
if (HasModifiedClientIDs) {
await db.Entry(this).Collection(r => r.ClientRoles).LoadAsync();
ClientRoles = new List<ClientRole>(
_ClientIDs.Select(c => new ClientRole() { ClientID = c, RoleID = ID })
);
}
if (HasModifiedViewerIDs) {
await db.Entry(this).Collection(r => r.RoleViewers).LoadAsync();
RoleViewers = new List<RoleViewer>(
_ViewerIDs.Select(v => new RoleViewer() { RoleID = ID, ViewerID = v })
);
}
}
I also moved that method call below the db.Update call. But I don't know, if this makes any difference.
Related
I am using EF Core 5.0.
I have a class called JobApplicationManager that OwnsMany() of JobApplications.
modelBuilder.Entity<JobApplicationManager>().OwnsMany(s => s.JobApplicationCategories, a =>
{
a.Property<DateTime>("CreatedDate");
a.Property<DateTime>("UpdatedDate");
a.ToTable("JobApplicationManagerCategories");
});
modelBuilder.Entity<JobApplication>().OwnsMany(s => s.JobApplicationCategories, a =>
{
a.Property<DateTime>("CreatedDate");
a.Property<DateTime>("UpdatedDate");
a.ToTable("JobApplicationCategories");
});
Current state: There is one JobApplicationManager in the DB with one JobApplication.
Action: I add another JobApplication to the JobApplicationManager.
In my DBContext.SaveChangesAsync() I call ChangeTracker.Entries() and the following is the entities:
As you can see the first entry is the JobApplication with status of Added.
Also in this DBContect you can see the JobApplicationManager has two JobApplications:
The next step I can ChangeTracker.HasChanges() which returns false so therefor base.SaveChangesAsync() returns 0 number of entities written.
I dont know what to do.
Here is the code that creates my entity and calls my repository:
var mgr = await AMRepository.GetJobApplicationManagerAsync(command.JobApplicationManagerID);
var newJobApplication = mgr.AddJobApplication(command.JobDescriptionURL, command.JobTitle, command.FileName, command.CompanyName, command.GeographicLocation, command.RecruiterName, command.JobApplicationCategories);
// Update the Repository
AMRepository.Update(mgr);
commandResult = await AMRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
Here is the code in my Repository:
public void Update(JobApplicationManager applicationManager)
{
try
{
AppManagerContext.Entry(applicationManager).State = EntityState.Modified;
}
Here is the code in my DBContext for SaveEntitiesAsync():
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var changesFlag1 = ChangeTracker.HasChanges();
// Dont save the enumeration Value Object types
try
{
var enumerationEntries = ChangeTracker.Entries()
.Where(x => EnumerationTypes.Contains(x.Entity.GetType()));
foreach (var enumerationEntry in enumerationEntries)
{
enumerationEntry.State = EntityState.Unchanged;
}
}
catch (Exception exc)
{
Console.WriteLine(exc.ToString());
}
UpdateShadowProperties();
var numberOfStateEntitiesWritten = 0;
try
{
numberOfStateEntitiesWritten = await base.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateException exc)
{
Console.WriteLine(exc.ToString());
}
if (numberOfStateEntitiesWritten == 0)
{
return false;
}
return true;
}
Here is the code from the Repository for GetJobApplicationManagersAsync():
public async Task<IEnumerable<JobApplicationManager>> GetJobApplicationManagersAsync() => await AppManagerContext.JobApplicationManagers.ToListAsync();
Within mgr.AddApplication() all I do is add POCO classes to the mgr. There is no infrastructure related activities (i.e. using DBContext)>
The instance of entity type 'Bot' 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 us...
I know what this problem means. It is happening right here _context.Bots.Update(bot);.
The question is: is that a good way to solve this by adding .AsNoTracking() to all GetByXXX methods? Any suggestions?
[HttpPut("{id}")]
public async Task<ActionResult> UpdateAsync([FromRoute] int id, [FromBody] BotCreateUpdateDto botCreateUpdateDto)
{
if (id != botCreateUpdateDto.Id)
{
return BadRequest(new { Error = "Invalid ID." });
}
var user = await _userService.GetByEmailAsync(botCreateUpdateDto.Email);
if (user == null)
{
return BadRequest(new { Error = "Invalid e-mail." });
}
var cryptoPair = await _cryptoPairService.GetBySymbolAsync(botCreateUpdateDto.Symbol);
if (cryptoPair == null)
{
return BadRequest(new { Error = "Invalid crypto pair." });
}
var timeInterval = await _timeIntervalService.GetByIntervalAsync(botCreateUpdateDto.Interval);
if (timeInterval == null)
{
return BadRequest(new { Error = "Invalid time interval." });
}
var bot = new Bot
{
Id = botCreateUpdateDto.Id,
Name = botCreateUpdateDto.Name,
Status = botCreateUpdateDto.Status,
UserId = user.Id,
CryptoPairId = cryptoPair.Id,
TimeIntervalId = timeInterval.Id
};
bool updated;
try
{
updated = await _botService.UpdateAsync(bot);
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
if (updated)
{
return NoContent();
}
return NotFound();
}
public async Task<User> GetByEmailAsync(string email)
{
return await _context.Users
.Include(e => e.UserRoles)
.ThenInclude(e => e.Role)
.Include(e => e.Bots)
.SingleOrDefaultAsync(e => e.Email == email);
}
public async Task<CryptoPair> GetBySymbolAsync(string symbol)
{
return await _context.CryptoPairs
.Include(e => e.Bots)
.AsNoTracking()
.SingleOrDefaultAsync(e => e.Symbol == symbol);
}
public async Task<TimeInterval> GetByIntervalAsync(KlineInterval interval)
{
return await _context.TimeIntervals
.Include(e => e.Bots)
.AsNoTracking()
.SingleOrDefaultAsync(e => e.Interval == interval);
}
public async Task<bool> UpdateAsync(Bot bot)
{
_context.Bots.Update(bot);
var updated = await _context.SaveChangesAsync();
return updated > 0;
}
Sorry, I really didn't realise that I was creating a new Bot object instead of just getting it by id. There is also no point of .AsNoTracking.
var bot = await _botService.GetByIdAsync(id);
bot.Name = botCreateUpdateDto.Name;
bot.Status = botCreateUpdateDto.Status;
bot.CryptoPairId = cryptoPair.Id;
bot.TimeIntervalId = timeInterval.Id;
This solves the problem for anyone having same problem as me. Keep in mind that my all services are scoped as well as the DbContext. If yours are singleton, that will cause this problem as well.
I got same issue like :
The instance of entity type 'ClassName' cannot be tracked because another instance with the key value '{Id: 1}'
is already being tracked. When attaching existing entities, ensure that only one entity instance
with a given key value is attached.
I resolved with remove this .AsNoTracking() when fetch data from DB.
I need to update a table of my ApplicationDbContext within a Task; however, I'm not getting it done. Here is the error message I've got:
ex.Message = "Cannot access a disposed object. A common cause of this
error is disposing a context that was resolved from dependency
injection and then later trying to use the same context instance
elsewhere in your application. This may occur if you are calling
Dispose...
I understand it has something to do with threading which I'm not so familiar.
Here's the code:
[HttpPost]
public ActionResult WebHook([FromBody] BotRequest data)
{
Task.Factory.StartNew(() =>
{
//Read value from Table
ContextWatsonFB contextWatsonFB = _context.ContextWatsonFB.Where(m => m.RecipientId == recipientid).FirstOrDefault();
if (contextWatsonFB == null)
{
contextWatsonFB = new ContextWatsonFB()
{
RecipientId = recipientid
};
_context.Add(contextWatsonFB);
_context.SaveChanges();
}
else
{
if (!string.IsNullOrEmpty(contextWatsonFB.Context))
{
model = JsonConvert.DeserializeObject<Context>(contextWatsonFB.Context);
}
}
///DO SOME STUFF ////////////////
///Here I need to update my table using some values processed above in "some stuff"
ContextWatsonFB contextWatsonFB = _context.ContextWatsonFB.Where(m => m.RecipientId == recipientid).FirstOrDefault();
contextWatsonFB.Context = JsonConvert.SerializeObject(context);
_context.Update(contextWatsonFB);
_context.SaveChanges();
}
}
As you can figure it out, its a webhook connection for Facebook, which requires the process to be handled in a Task. Within "some stuff", basically I'm consuming IBM Watson Conversation service whom persist a conversation "context" that I'm not able to send back and forth to Facebook, that's why I figure it out to persist such data in a table to keep the differences among multiple request from facebook messenger.
Luckily, the following code did the trick:
private readonly IServiceProvider _provider;
public FacebookBotController(ApplicationDbContext context, IServiceProvider provider)
{
_provider = provider;
}
[HttpPost]
public ActionResult WebHook([FromBody] BotRequest data)
{
if (data == null || data?.entry?.Count == 0)
{
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
try
{
var task = Task.Factory.StartNew(async () =>
{
using (IServiceScope scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
ApplicationDbContext _contx = _provider.GetService<ApplicationDbContext>();
ContextWatsonFB contextWatsonFB = await _contx.ContextWatsonFB.Where(m => m.SenderId == senderId).FirstOrDefaultAsync();
if (contextWatsonFB == null)
{
context = null;
}
else
{
context = JsonConvert.DeserializeObject<Context>(contextWatsonFB.Context);
}
}
}
}
I have a project I am using a database that is connected thru the entity data model and I am using the DBcontext to create and update entities.
My Create method (below) is working fine.
[HttpPost]
public IHttpActionResult PostCustomer([FromBody] Customer Customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var dbCtx = new CustomerDBEntities())
{
dbCtx.Customers.Add(Customer);
dbCtx.SaveChanges();
}
return CreatedAtRoute("DefaultApi", new { id = Customer.id }, Customer);
}
But my update method is doing nothing. I am not getting an error or anything just nothing seems to happen. and it is not updating the values.
The code is this
[HttpPut]
public IHttpActionResult UpdateCustomer([FromBody] Customer Customer)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Customer cust;
var ctx = new CustomerDBEntities();
cust = ctx.Customers.Where(s => s.id == Customer.id).FirstOrDefault<Customer>();
if (cust != null)
{
cust.Customer_Name = Customer.Customer_Name;
cust.email = Customer.email;
cust.Customer_address = Customer.Customer_address;
}
ctx.Entry(cust).State = System.Data.EntityState.Modified;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = Customer.id }, Customer);
}
When I was using an SQL command before to go directly to database it was working fine but ever since I changed it it stopped working.
Any help would be much appreciated.
Remove this line of code.
ctx.Entry(cust).State = System.Data.EntityState.Modified;
EF tracks the changes made to entities in the context, you do not need to tell it that it was modified.
Add changes at Property Level
context.Entry(Customer).Property(cust => cust.Customer_Name).IsModified = true;
More Details here https://msdn.microsoft.com/en-us/data/jj592677
Also similar question answered here https://stackoverflow.com/a/15339512/1481690
I created a post method in my Web API to do a partial update of one table - to change Order Status field in the table. here is what I got so far:
public IHttpActionResult UpdateOrderStatus(Order ord)
{
try
{
if (!ModelState.IsValid)
{
throw new ProcessException("One or more values are invalid, please check.");
}
using (MyContext ctx = new MyContext())
{
ord.StatusID = 3;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = ord.OrderID }, ord);
}
}
catch (Exception ex)
{
throw new ProcessException(ex.Message);
}
}
I am stepping through the code and everything seems to be working - no errors, but the database is not getting updated. What am I doing wrong?
Update:
Set the StatusID in the object BEFORE passing it to the Web API method:
var ord = { "OrderID": 1, "OrderDate": CurrentDate, "StatusID": 3};
public IHttpActionResult UpdateOrderStatus(Order ord)
{
try
{
if (!ModelState.IsValid)
{
throw new ProcessException("One or more values are invalid, please check.");
}
using (MyContext ctx = new MyContext())
{
ctx.Entry(ord).State = EntityState.Modified;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = ord.OrderID }, ord);
}
}
catch (Exception ex)
{
throw new ProcessException(ex.Message);
}
}
If you're using EntityFramework then you need to inform the context about the Order object you are updating. In EF6 you would typically have a DbSet property on the context called Orders, so I'd advise adding ctx.Orders.Attach(Order) before you set the StatusID
Try this code:
Get orderEntity from database by comparing with ord.OrderID, Then update StatusID of that record.
using (MyContext ctx = new MyContext())
{
var orderEntity = ctx.Order.FirstOrDefault(x=> x.OrderID == ord.OrderID);
orderEntity.StatusID = 3;
ctx.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = ord.OrderID }, ord);
}