PawelKosi
PawelKosi

Reputation: 151

The property on entity type is part of a key and so cannot be modified or marked as modified. EF Core Dotnet Core

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

Answers (1)

Oleg
Oleg

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

Related Questions