Reputation: 151
This was asked in different forms in the past, but I am still unable to get it working.
I am building a site in dotnet core / ef core and on one page I want to use jqGrid to easily edit one table that will be changing very often. Table is called CoList (Company List)
I can already add new rows and I can delete them, however when I try to edit row I am getting this error:
The property 'AutoId' on entity type 'CoList' is part of a key and so cannot be modified or marked as modified.
I have created database first as it was used somewhere else before I started to work on this new site.
My model CoList.cs:
public partial class CoList
{
public int AutoId { get; set; }
public string CompanyName { get; set; }
public string CoLevel { get; set; }
public string CoCode { get; set; }
}
Database context file
modelBuilder.Entity<CoList>(entity =>
{
entity.HasKey(e => e.AutoId)
.HasName("PK_CoList");
entity.HasIndex(e => e.AutoId).IsUnique();
entity.Property(e => e.AutoId).HasColumnName("AutoID");
entity.Property(e => e.CoCode)
.IsRequired()
.HasColumnType("varchar(50)");
entity.Property(e => e.CoLevel)
.IsRequired()
.HasColumnType("varchar(50)");
entity.Property(e => e.CompanyName)
.IsRequired()
.HasColumnType("varchar(50)");
});
In controller for edit I have:
public string EditCoList(int? Id, [Bind(include: "CompanyName, CoLevel, CoCode")] CoList coList)
{
FPSDemoContext db = _context;
string msg;
try
{
if (ModelState.IsValid)
{
db.Entry(coList).State = EntityState.Modified;
db.SaveChanges();
msg = "Saved";
}
else
{
msg = "Did not validate";
}
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is CoList)
{
var databaseEntity = db.CoList.AsNoTracking().Single(p => p.AutoId == Id);
var databaseEntry = db.Entry(databaseEntity);
foreach (var property in entry.Metadata.GetProperties())
{
var proposedValue = entry.Property(property.Name).CurrentValue;
var originalValue = entry.Property(property.Name).OriginalValue;
var databaseValue = databaseEntry.Property(property.Name).CurrentValue;
entry.Property(property.Name).CurrentValue = proposedValue;
entry.Property(property.Name).OriginalValue = databaseEntry.Property(property.Name).CurrentValue;
}
}
else
{
msg = "Error occured:" + ex.Message;
throw new NotSupportedException("Concurrency conflict " + entry.Metadata.Name);
}
}
// Retry the save operation
db.SaveChanges();
msg = "Saved";
}
return msg;
}
Once I hit save, the code crashes on the 'Retry Save operation'.
jqGrid Code:
$(function () {
$("#jqGrid").jqGrid({
regional: 'en',
url: "/SiteOptions/GetCoList",
datatype: 'json',
mtype: 'Get',
colNames: ['Id', 'Company Name', 'Company Level', 'Company Code'],
colModel: [
{ key: true, name: 'autoId', index: 'autoId', editable: false },
{ key: false, name: 'companyName', index: 'companyName', editable: true },
{ key: false, name: 'coLevel', index: 'coLevel', editable: true },
{ key: false, name: 'coCode', index: 'coCode', editable: true }],
pager: jQuery('#jqControls'),
rowNum: 10,
rowList: [10, 20, 30, 40, 50],
height: '100%',
viewrecords: true,
caption: 'Company List - Grid',
emptyrecords: 'No Companies to display',
jsonReader: {
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
Id: "0"
},
autowidth: true,
multiselect: false
}).navGrid('#jqControls', { edit: true, add: true, del: true, search: false, refresh: true },
{
zIndex: 100,
url: '/SiteOptions/EditCoList',
closeOnEscape: true,
closeAfterEdit: true,
recreateForm: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
},
{
zIndex: 100,
url: "/SiteOptions/CreateCoList",
closeOnEscape: true,
closeAfterAdd: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
},
{
zIndex: 100,
url: "/SiteOptions/DeleteCoList",
closeOnEscape: true,
closeAfterDelete: true,
recreateForm: true,
msg: "Are you sure you want to delete this row? ",
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
});
});
I have read those answers but they did not help me, or I am misunderstanding something.
The property on entity type is part of a key and so cannot be modified or marked as modified
The property 'name' is part of the object's key information and cannot be modified. Entity Framework
https://github.com/aspnet/EntityFrameworkCore/issues/4560
EDIT #2
As per comments I have changed the edit method to this:
public async Task<IActionResult> EditCoList(int Id, CoList coList)
{
string msg;
msg = "Model state is not valid";
if (Id != coList.AutoId)
{
msg = "Not Found";
}
if (ModelState.IsValid)
{
try
{
_context.Update(coList);
await _context.SaveChangesAsync();
msg = "Saved";
}
catch (DbUpdateConcurrencyException)
{
if (!CoListExists(coList.AutoId))
{
msg = "Concurrency Exception - Not Found";
}
else
{
msg = "Error";
throw;
}
}
}
return Content(msg);
}
private bool CoListExists(int? id)
{
return _context.CoList.Any(e => e.AutoId == id);
}
I am using viewmodel too (with extra table I am going to need on that view later):
namespace ASPNET_Core_1_0.Models.SiteOptionsViewModels
{
public class SiteOptionsViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.SiteOptions> SiteOptions { get; set; }
public IEnumerable<ASPNET_Core_1_0.Models.CoList> CoList { get; set; }
public ASPNET_Core_1_0.Models.SiteOptions AutoId { get; set; }
public ASPNET_Core_1_0.Models.SiteOptions CurrentMonth { get; set; }
public ASPNET_Core_1_0.Models.SiteOptions CurrentYear { get; set; }
public ASPNET_Core_1_0.Models.SiteOptions SelectedCompanyId { get; set; }
public ASPNET_Core_1_0.Models.CoList CompanyName { get; set; }
public ASPNET_Core_1_0.Models.CoList CoLevel { get; set; }
public ASPNET_Core_1_0.Models.CoList CoCode { get; set; }
}
}
And in controller I call the view like so:
public async Task<IActionResult> Companylist()
{
ViewData["SubTitle"] = "Company List";
ViewData["Message"] = "Edit company list";
var model = new SiteOptionsViewModel
{
SiteOptions = await _context.SiteOptions.ToListAsync(),
CoList = await _context.CoList.ToListAsync()
};
return View(model);
}
I am still getting the concurrency error though:
Microsoft.EntityFrameworkCore.DbContext:Error: An exception occurred in the database while saving changes. Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.d__6.MoveNext() --- End of stack trace from previous location where exception was thrown ---
Interestingly when I generate CRUD controller for any table in my DB from VS it all always works, no issues at all in editing or saving the edits. I followed the generated code to create my own and still get the concurrency error.
EDIT #3
The jqGrid js script with added prmNames: { id: "AutoId" } (as per Oleg comment),
$(function () {
$("#jqGrid").jqGrid({
regional: 'en',
prmNames: { id: "AutoId" },
url: "/SiteOptions/GetCoList",
datatype: 'json',
mtype: 'Get',
colNames: ['Id', 'Company Name', 'Company Level', 'Company Code'],
colModel: [
{ key: true, name: 'autoId', index: 'autoId', editable: false },
{ key: false, name: 'companyName', index: 'companyName', editable: true },
{ key: false, name: 'coLevel', index: 'coLevel', editable: true },
{ key: false, name: 'coCode', index: 'coCode', editable: true }],
pager: jQuery('#jqControls'),
rowNum: 10,
rowList: [10, 20, 30, 40, 50],
height: '100%',
viewrecords: true,
caption: 'Company List - Grid',
emptyrecords: 'No Companies to display',
jsonReader: {
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
Id: "0"
},
autowidth: true,
multiselect: false
}).navGrid('#jqControls', { edit: true, add: true, del: true, search: false, refresh: true },
{
zIndex: 100,
url: '/SiteOptions/EditCoList',
closeOnEscape: true,
closeAfterEdit: true,
recreateForm: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
},
{
zIndex: 100,
url: "/SiteOptions/CreateCoList",
closeOnEscape: true,
closeAfterAdd: true,
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
},
{
zIndex: 100,
url: "/SiteOptions/DeleteCoList",
closeOnEscape: true,
closeAfterDelete: true,
recreateForm: true,
msg: "Are you sure you want to delete this row? ",
afterComplete: function (response) {
if (response.responseText) {
alert(response.responseText);
}
}
});
});
Method (as per Oleg comment):
public async Task<IActionResult> EditCoList(CoList coList)
{
string msg;
msg = "Model state is not valid";
/*if (Id != coList.AutoId)
{
msg = "Not Found";
}*/
if (ModelState.IsValid)
{
try
{
_context.Update(coList);
await _context.SaveChangesAsync();
msg = "Saved";
}
catch (DbUpdateConcurrencyException)
{
if (!CoListExists(coList.AutoId))
{
msg = "Concurrency Exception - Not Found";
}
else
{
msg = "Error";
throw;
}
}
}
return Content(msg);
}
Still no joy
Can someone please tell me what am I doing wrong? I am still learning so it might be simple. If possible at all could any of you show me correct solution to this?
Upvotes: 0
Views: 4170
Reputation: 221997
I'd recommend you to add prmNames: { id: "autoId" }
option to inform jqGrid to use autoId
property instead of id
property during sending the editing results to the server.
If prmNames
don't work because of some reasons (bug in Guriddo jqGrid or in your code), but you see that id
property with correct value be sent to the server then you can add int id
parameter to EditCoList
action and to assign coList.AutoId
based on id
before the line _context.Update(coList);
Upvotes: 1