0

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?

PawelKosi
  • 151
  • 1
  • 3
  • 13
  • Could you verify whether `AutoId` property is set in `coList` inside of `EditCoList` method? Could you include jqGrid code, which you use? – Oleg Oct 22 '17 at 17:34
  • Hi Oleg, I have included the jqGrid code I use in the edit. AutoId is set in CoList but I do not understand what you mean inside of EditCoList method? – PawelKosi Oct 22 '17 at 17:59
  • 2
    1) Use a view model 2) Don't use `[Bind]`. Seriously. Never. Stop it now. 3) Fetch the entity you're modifying fresh from the database, and map over posted values onto that. You should never ever directly save anything created via something a user posted. Do those three things, and you won't have any issues. – Chris Pratt Oct 22 '17 at 18:20
  • @ChrisPratt Ok! I Wont use bind, I promise. Regarding the rest, would you be kind enough to provide me with an example? I don't know where to start with the approach you mention. I used ViewModels to combine several different tables for one view, but that's about it. I am not asking you to write the whole code for me, but could you start me off? – PawelKosi Oct 22 '17 at 18:25
  • It's straight-forward. Just create a class that has just the properties you need to post from your view, excluding the id. Ids should be part of the URL, and should never be posted. Change your action to accept this class as a param (instead of your entity class, which then allows you to remove all the `[Bind]` garbage, as well). In your action, fetch the entity to modify from the database using the id, then map over the posted values on your view model instance onto that entity instance. Finally, save the entity instance. – Chris Pratt Oct 22 '17 at 18:28
  • No chance for over-post, as you explicitly control what does and doesn't make it to the entity instance, and no problems saving, since the entity came from Entity Framework instead of the modelbinder. – Chris Pratt Oct 22 '17 at 18:29
  • @PawelKosi: First of all, I would add `prmNames: { id: "AutoId" }` option to jqGrid. It informs jqGrid about the name of id property. Seconds you use `url: '/SiteOptions/EditCoList'` in edit options of `navGrid`. Thus `EditCoList` action be called on editing. After adding `prmNames: { id: "autoId" }` option of jqGrid you can change parameters of `EditCoList` action to `public string EditCoList(CoList coList)` because `AutoId` will be filled too. Finally you can just use `db.Update(coList); db.SaveChanges();` to update the item. – Oleg Oct 22 '17 at 19:36
  • @PawelKosi: You could change `public string EditCoList(CoList coList)` to `public async Task EditCoList(CoList coList)` and to use `await db.SaveChangesAsync();` instead of `db.SaveChanges();` – Oleg Oct 22 '17 at 19:38
  • @Oleg I have edited my question with the items I tried, I still get the same error. – PawelKosi Oct 24 '17 at 13:19
  • @ChrisPratt I did some edits but still get the same error, I've updated my question – PawelKosi Oct 24 '17 at 13:20
  • @PawelKosi: Do you added `prmNames: { id: "AutoId" }` option (or `prmNames: { id: "autoId" }` depend on other options of your MVC project) to jqGrid like I recommended you? Do you set breakpoint inside of `EditCoList` action? Is it be called? Which value have `AutoId` property of `coList`? If you add `prmNames: { id: "AutoId" }` then `id` parameter will be **not filled** and it will be filled `AutoId` and you have to remove `int Id` parameter of `EditCoList` action. – Oleg Oct 24 '17 at 13:27
  • @Oleg Still no joy mate, I did the edits you suggested and same error occurs – PawelKosi Oct 24 '17 at 13:36
  • @PawelKosi: If you use `name: 'autoId'` and the column will be filled then the data returned from the server contains `autoId` property instead of `AutoId`. In the case you should use `prmNames: { id: "autoId" }` instead of `prmNames: { id: "AutoId" }`. I want to repeat that you have to **debug** your code. Did you set breakpoint inside of `EditCoList` action? If `AutoId` property of `coList` parameter is not filled that you should fix your client code (JavaScript code). I recommend you to use Developer Tools of Chrome/IE (Network tab) to see which information will be send to `EditCoList` – Oleg Oct 24 '17 at 13:41
  • @Oleg I just did set the breakpoints, rookie error I know and yes it seems that the Id is not being passed and always set at 0. I will amend the AutoId value and will report back. – PawelKosi Oct 24 '17 at 13:43
  • @PawelKosi: You should use Developer Tools of Chrome/IE to see which information will be sent to the server (to `EditCoList`). Moreover you shoule use Developer Tools to see which values have `id` attribute of the row (``), which you edit. It should be equal to `AutoId`. One more question: which **version** of jqGrid you use (can use) and from which **fork** of jqGrid ([free jqGrid](https://github.com/free-jqgrid/jqGrid), commercial [Guriddo jqGrid JS](http://guriddo.net/?page_id=103334) or an old jqGrid in version <=4.7) – Oleg Oct 24 '17 at 13:46
  • @Oleg I can confrm that the 'Id' from the tr tags in page have correct id of 6 I can see it also in debugging when using breakpoints in VS :`Id 6 int` but AutoId stays at 0: `Colist: AutoId 0 int` I did change the AutoId to autoId too. **EDIT** I am using `"version": "5.0.2"` – PawelKosi Oct 24 '17 at 13:56
  • @PawelKosi: Do you tried to use `prmNames: { id: "autoId" }` instead of `prmNames: { id: "AutoId" }`? Do you tried to use Developer Tools to see which information will be sent to `EditCoList`? You can use free [Fiddler](https://www.telerik.com/fiddler) tool alternatively. Could you include the HTTP body of the POST request? – Oleg Oct 24 '17 at 14:02
  • @Oleg Yes I did change the AutoId to autoId, from developer tools this is my POST: `companyName:Some Company 2 coLevel:Some Level coCode:Some Code oper:edit id:6` – PawelKosi Oct 24 '17 at 14:08
  • @PawelKosi: It's very strange that `id` property instead of `autoId` will be send. It means that `prmNames: { id: "autoId" }` not work. You use **commercial** Guriddo version of jqGrid, which you have to buy for the price found [here](http://guriddo.net/?page_id=103334). It could be a bug in Guriddo or you do use wrong parameter `prmNames`. I develop **alternative** fork [free jqGrid](https://github.com/free-jqgrid/jqGrid), which can be used absolutely for free. You can try it. If you do get `id` on the server then you can add `int id` and to use `coList.AutoId: id;` before `_context.Update` – Oleg Oct 24 '17 at 14:14
  • @Oleg I might need to try it. I got jqGrid as a part of paid bootstrap theme with seed project attached. I will try your other suggestion now. – PawelKosi Oct 24 '17 at 14:16
  • @PawelKosi: First of all I recommend you to make your code working. See my last comment to add `int id` parameter and to set `coList.AutoId = id;` before `_context.Update(coList);`. After that I'd recommend you to read [the article](https://free-jqgrid.github.io/getting-started/index.html#bootstrap), which shows how to use free jqGrid with Bootstrap. Additionally you should read carefully the information about the license agreement of both "Guriddo jqGrid" and "free jqGrid" and decide, which one to use. I can help you mostly with free jqGridm fork, which I develop. – Oleg Oct 24 '17 at 14:22
  • @Oleg - It did indeed work! Thank you! Now I would gladly move on to using your version at the same time it seems that I've got paid version of jqGrid with the template. If the syntax is mostly the same then why cause myself any problems by using version that I cannot update – PawelKosi Oct 24 '17 at 14:29
  • @PawelKosi: It's a good news. By the way, I use ASP.NET Core myself too (see [here](https://stackoverflow.com/search?q=user%3A315935+%5Basp.net-core%5D) for example). You could prepare the demo project and send me. I could help to reduce your code and improve it. – Oleg Oct 24 '17 at 14:41
  • @Oleg It is very generous offer from you - I might take you up on it! – PawelKosi Oct 24 '17 at 14:42
  • @Oleg Hi Mate, is there a way of contacting you? I need little of your help regarding jqGrid. – PawelKosi Dec 16 '17 at 10:10
  • @PawelKosi: What kind of help you need exactly? You can ask new question for example. It's always good if you would prepare a working demo, which reproduces your problem. – Oleg Dec 16 '17 at 10:50
  • @Oleg I can create additional question if that is better for you. I can't seem to edit above code to have inline editing that is the main issue. When I have the inline editing done I need to be able to drag and drop items onto the table input fields - I have question for that here: https://stackoverflow.com/questions/47793208/drag-and-drop-interface-for-sql-database-table-mapping-dotnet-core I was thinking about using your jqGrid for it. – PawelKosi Dec 16 '17 at 16:31

1 Answers1

1

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);

Oleg
  • 220,925
  • 34
  • 403
  • 798