I am creating an API that will enable CRUD functionality to Create, Read, Update, and Delete record in database.
I am running into an issue that is not allowing me to update entry inside a table due to an instance is shared and I get below error.
cannot be tracked because another instance of this type with the same key is already being tracked. For new entities consider using an IIdentityGenerator to generate unique key values."
Here is my code:
[HttpPut("{id}")]
public JsonResult Put(int Id, [FromBody]PackageVersion updatePackage)
{
try
{
if (ModelState.IsValid)
{
if (updatePackage == null || updatePackage.Id != Id)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { status = "Bad Request" });
}
var package = _respository.GetPackageById(Id);
if (package == null)
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return Json(new { status = "Not Found", Message = package });
}
_respository.UpdatePackage(updatePackage);
if (_respository.SaveAll())
{
Response.StatusCode = (int)HttpStatusCode.Accepted;
return Json(updatePackage);
}
}
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { status = "Failed", ModelState = ModelState });
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { status = "Failed", Message = ex.Message });
}
}
In the above code you will notice I am first getting a record by using repository _repository.GetPackageById(Id), which allows me to validate that a record is in database and I can continue to update by using _repository.UpdatePackage(updatePackage) repository. If I comment out below code in my controller, I am able to save the data in the database.
//var package = _respository.GetPackageById(Id);
//if (package == null)
//{
// Response.StatusCode = (int)HttpStatusCode.NotFound;
// return Json(new { status = "Not Found", Message = package });
//}
I also made sure that I was using AddScoped in startup configuration as mentioned in this thread.
services.AddScoped<IAutomationRepository, AutomationRepository>();
I am not sure why I cant use multiple DBContext instances for same ID when it is called.
Any suggestion is really appreciated. :)
UPDATE 1:
public class AutomationRepository : IAutomationRepository
{
private AutomationDBContext _context;
public AutomationRepository(AutomationDBContext context)
{
_context = context;
}
public void AddPackage(PackageVersion newPackage)
{
_context.Add(newPackage);
}
public void DeletePackage(int id)
{
var package = _context.PackageVersions.SingleOrDefault(p => p.Id == id);
_context.PackageVersions.Remove(package);
}
public IEnumerable<PackageVersion> GetAllPackages()
{
return _context.PackageVersions.OrderBy(p => p.PackageName).ToList();
}
public object GetPackageById(int id)
{
return _context.PackageVersions.SingleOrDefault(p => p.Id == id);
}
public bool SaveAll()
{
return _context.SaveChanges() > 0;
}
public void UpdatePackage(PackageVersion updatePackage)
{
_context.Update(updatePackage);
}
Change your repository method like below. This may help. Let us know what happens
public object GetPackageById(int id)
{
return _context.PackageVersions.AsNoTracking().SingleOrDefault(p => p.Id ==id);
}
Related
The context: An AspNetCore controller I've been asked to maintain contains methods similar to the following:
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<ActionResult<ItemViewModel>> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
if (string.IsNullOrWhiteSpace(accountId))
{
return BadRequest("accountId must be provided");
}
if (itemNumber < 0)
{
return BadRequest("itemNumber must be positive");
}
if (!await CanAccessAccountAsync(accountId))
{
return Forbid();
}
// Returns null if account or item not found
var result = _fooService.GetItem(accountId, itemNumber);
if (result == null)
{
return NotFound();
}
return result;
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<ActionResult<IEnumerable<ItemViewModel>>> GetFoos([FromRoute] string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
return BadRequest("accountId must be provided");
}
if (!await CanAccessAccountAsync(accountId))
{
return Forbid();
}
// Returns null if account not found
var results = _fooService.GetItems(accountId);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
You may assume that there are more than 2 such methods with very similar parts.
Looking at this code makes me itchy—there appears to be a lot of repetition, but the repeated parts can't be extracted to their own methods because they contain return statements.
To me, it would make sense for these early exits to be exceptions rather than return values. Say, for the sake of argument, that I define an exception to wrap an IActionResult:
internal class ActionResultException : Exception
{
public ActionResultException(IActionResult actionResult)
{
ActionResult = actionResult;
}
public IActionResult ActionResult { get; }
}
Then I can extract some specific validations:
private void CheckAccountId(string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
throw new ActionResultException(BadRequest("accountId must be provided"));
}
}
private async Task CheckAccountIdAccessAsync(string accountId)
{
if (!await CanAccessAccountAsync(accountId))
{
throw new ActionResultException(Forbid());
}
}
private void CheckItemNumber(int itemNumber)
{
if (itemNumber < 0)
{
throw new ActionResultException(BadRequest("itemNumber must be positive"));
}
}
And rewrite the controller to use them:
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
try
{
CheckAccountId(accountId);
CheckItemNumber(itemNumber);
await CheckAccountIdAccessAsync(accountId);
// Returns null if account or item not found
var result = _fooService.GetItem(accountId, itemNumber);
if (result == null)
{
return NotFound();
}
return Ok(result);
}
catch (ActionResultException e)
{
return e.ActionResult;
}
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<IActionResult> GetFoos([FromRoute] string accountId)
{
try
{
CheckAccountId(accountId);
await CheckAccountIdAccessAsync(accountId);
// Returns null if account not found
var results = _fooService.GetItems(accountId);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
catch (ActionResultException e)
{
return e.ActionResult;
}
}
To get this to work, I had to wrap the controller bodies in a try to unwrap the action result from the exception.
I also had to revert the return types to IActionResult—there are reasons I may prefer not to do that. The only thing I can think of to address that problem is to go more specific with the exceptions and catches, but this seems only to shift the WET-ness from the validation code to the catch blocks.
// Exceptions
internal class AccessDeniedException : Exception { ... }
internal class BadParameterException : Exception { ... }
// Controller
private void CheckAccountId(string accountId)
{
if (string.IsNullOrWhiteSpace(accountId))
{
throw new BadParameterException("accountId must be provided");
}
}
private async Task CheckAccountIdAccessAsync(string accountId)
{
if (!await CanAccessAccountAsync(accountId))
{
throw new AccessDeniedException();
}
}
private void CheckItemNumber(int itemNumber)
{
if (itemNumber < 0)
{
throw new BadParameterException("itemNumber must be positive");
}
}
// Get api/Foo/ABCXXX/item/12345
[HttpGet("{accountId}/item/{itemNumber}")]
public async Task<IActionResult> GetFoo([FromRoute] string accountId, [FromRoute] int itemNumber)
{
try
{
...
}
catch (AccessDeniedException)
{
return Forbid();
}
catch(BadParameterException e)
{
return BadRequest(e.Message);
}
}
// GET api/Foo/ABCXXX
[HttpGet("{accountId}")]
public async Task<IActionResult> GetFoos([FromRoute] string accountId)
{
try
{
...
}
catch (AccessDeniedException)
{
return Forbid();
}
catch (BadParameterException e)
{
return BadRequest(e.Message);
}
}
There's a few simple things you can do. No need to go overboard at this point.
First and foremost, checking whether accountId is null or whitespace is completely superfluous. It's part of the route; if something isn't stuck in there, you wouldn't get here in the first place.
Second, you can make judicious use of route constraints where appropriate. For example, for your itemNumber being positive:
[HttpGet("{accountId}/item/{itemNumber:min(0)}")]
Though, honestly, I'm not sure something like /XXXX/item/-1 would even work in the first place. Regardless, specifying a min value will cover you.
Third, your CanAccessAccount check should actually be handled via resource-based authorization, built-in to ASP.NET Core.
Long and short, if you use what's already available to you, you actually don't need to do much additional validation, in the first place, negating the need to find some way to "factor it out".
I have a simple api method to insert data to db, the method in postman man is showing success, but the data is not inserted in database, here is my code
private Utilities uti = new Utilities();
private readonly ApplicationDBContext db;
public AppraisalController(ApplicationDBContext context)
{
db = context;
}
//INSERT API FOR AppraisalIdentity table
[AllowAnonymous]
[Route("api/appraiseinsert")]
[HttpPost]
public IActionResult Create([FromBody] AppraisalIdentity cre)
{
if (cre == null)
{
return BadRequest();
}
using (var transaction = db.Database.BeginTransaction())
{
try
{
#region Appraisal Insert
var apprais = new AppraisalIdentity
{
AppraisalName = cre.AppraisalName,
IsCurrent = cre.IsCurrent,
CompanyID = cre.CompanyID,
DateAdded = cre.DateAdded
};
db.AppraisalIdentity.Add(apprais);
db.SaveChanges();
#endregion
}
catch (Exception ex)
{
transaction.Rollback();
return Json(new
{
statusCode = ex.Message
});
}
}
return Json(new
{
statusCode = "Success"
});
}
I don't know, maybe there's an error somewhere in my code that I don't know about, but the fact is that in postman, the api is returning success, but its not inserting anything in db. Thanks
Since you are using a transaction i guess you need to commit the transaction after calling db.SaveChanges() like this:
db.SaveChanges();
transaction.Commit();
I have a problem when calling API for update and savechanges() is not working (the data is not update).
However, when I add Thread.Sleep(1000); the data update correctly.
Working Methods
public async Task<ResponseBaseModel> AddOrderRemark2(AddOrderRemarkRequestModel model)
{
try
{
using (ChatEntities context = new ChatEntities(CurrentUsername))
{
List<string> statusList = getPendingStatus(context).Result;
OrderHeader orderHeader = getOrderHerderByOrderCode(context, model.OrderCode, model.SalesChannelId).Result;
if (statusList.Contains(orderHeader.Status))
{
if (orderHeader != null)
{
Thread.Sleep(1000);
orderHeader.Remark = model.Remark;
context.DBEntry(orderHeader, EntityState.Modified);
context.SaveChanges();
}
}
}
return new ResponseBaseModel(MessageCode.OK);
}
catch (Exception ex)
{
return new ResponseBaseModel(MessageCode.Fail, ex.InnerException.Message);
}
}
Fail Methods
public async Task<ResponseBaseModel> AddOrderRemark2(AddOrderRemarkRequestModel model)
{
try
{
using (ChatEntities context = new ChatEntities(CurrentUsername))
{
List<string> statusList = getPendingStatus(context).Result;
OrderHeader orderHeader = getOrderHerderByOrderCode(context, model.OrderCode, model.SalesChannelId).Result;
if (statusList.Contains(orderHeader.Status))
{
if (orderHeader != null)
{
orderHeader.Remark = model.Remark;
context.DBEntry(orderHeader, EntityState.Modified);
context.SaveChanges();
}
}
}
return new ResponseBaseModel(MessageCode.OK);
}
catch (Exception ex)
{
return new ResponseBaseModel(MessageCode.Fail, ex.InnerException.Message);
}
}
Edit
I have realise that there are two APIs call at the same times from client sides. Moreover, these two APIs update on the same table 'OrderHeader' which contain both receiver info and remark that why it causes this issue!!. How can I prevent this issue guys?
[HttpPost]
[ActionName("AddReceiverAddress")]
[ChatAuthentication]
public async Task<ResponseBaseModel> AddReceiverAddress(AddReceiverAddressRequestModel model)
{
return _orderService.Value.AddReceiverAddress(model).Result;
}
[HttpPost]
[ActionName("AddOrderRemark")]
[ChatAuthentication]
public async Task<ResponseBaseModel> AddOrderRemark(AddOrderRemarkRequestModel model)
{
return _orderService.Value.AddOrderRemark(model).Result;
}
You are not using async properly. Try this instead
public async Task<ResponseBaseModel> AddOrderRemark2(AddOrderRemarkRequestModel model)
{
try
{
using (ChatEntities context = new ChatEntities(CurrentUsername))
{
List<string> statusList = await getPendingStatus(context);
OrderHeader orderHeader = await getOrderHerderByOrderCode(context, model.OrderCode, model.SalesChannelId);
When you call this method, did you await or Wait() for a result?
When you call the method you have to do either one of them as below sample.
await AddOrderRemark2(model);
Or
AddOrderRemark2(model).Wait();
I have created a documentDB on Azure and can successfully create and get documents.
However, whilst documents are successfully created in the DB, I am not able to use the response from CreateDocumentAsync. The code immediately returns to the calling method on the controller. So the debug line is never reached.
Moreover I am setting the id to a guid, but the Document that is returned to the controller has an Id of 1.
Controller
[HttpPost]
[Route("")]
public IHttpActionResult CreateNewApplication(dynamic data)
{
if (data == null)
{
return BadRequest("data was empty");
}
try
{
var doc = _applicationResource.Save(data);
return Ok(doc.Id); //code hits this point and 'returns'
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
Resource
public async Task<Document> Save(dynamic application)
{
Document created;
using (Client)
{
application.id = Guid.NewGuid();
var database = await RetrieveOrCreateDatabaseAsync(Database);
var collection = await RetrieveOrCreateCollectionAsync(database.SelfLink, CollectionName);
//persist the documents in DocumentDB
created = await Client.CreateDocumentAsync(collection.SelfLink, application);
}
Debug.WriteLine("Application saved with ID {0} resourceId {1}", created.Id, created.ResourceId);
return created;
}
Get requests return data as expected:
[HttpGet]
[Route("{id}")]
public IHttpActionResult GetApplication(string id)
{
var application = _applicationResource.GetById(id);
return Ok(application);
}
That's because you're not awaiting an asynchronous method:
This:
var doc = _applicationResource.Save(data);
Needs to be:
var doc = await _applicationResource.Save(data);
Your method should look as follows:
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> CreateNewApplication(dynamic data)
{
if (data == null)
{
return BadRequest("data was empty");
}
try
{
var doc = await _applicationResource.Save(data);
return Ok(doc.Id); //code hits this point and 'returns'
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
Im currently using Jtable to handle a list of eventtypes, but everytime I try to delete via the table I get the error "Value cannot be null. parameter name: entity".
[HttpPost]
public JsonResult DeleteEventType(EventType eventType)
{
using (var db = new ICTTBEntities())
{
try
{
var newObj = db.EventTypes.SingleOrDefault(e => e.EventTypeID == eventType.EventTypeID);
if (newObj != null)
{
db.EventTypes.Remove(newObj);
db.SaveChanges();
return Json(new { Result = "OK" });
}
return Json(new { Result = "ERROR", Message = "eventType is null" });
}
catch (Exception ex)
{
return Json(new { Result = "ERROR", Message = ex.Message });
}
}
}
Any advice?
You get eventType via another ICTTBEntities context, that's why it probably can't found it in your new context.
Try getting eventType from new context via Id.
Also, don't forget to Call SaveChanges on your context after removing the object.
Use
var newObj = db.EventTypes.Single( e => e.Id == eventType.Id);
db.EventTypes.Remove(newObj);
db.SaveChanges();