Kendo UI Grid Inserts/Updates create Duplicate Records (again)

12,906

Solution 1

I haven't seen this problem in my code. I do however have a "complete" event handler on my create and update events that refreshed the grid - it may help you:

  dataSource: {

    type: "jsonp",
    transport: {
        read: UrlBase + "getAll",
        update: {
            url: UrlBase + "Update",
            dataType: "jsonp",
            complete: function (e) {
                $("#grid").data("kendoGrid").dataSource.read();

            }
        },
        create: {
            url: UrlBase + "create",
            dataType: "jsonp",
            complete: function (e) {
                $("#grid").data("kendoGrid").dataSource.read();
            }
        },
        destroy: {
            url: UrlBase + "destroy",
            dataType: "jsonp",
            complete: function (e) {
                $("#grid").data("kendoGrid").dataSource.read();
            }
        }
    },
   ...

Solution 2

Yes, hamed is correct. Your "create" action result passes in the object from your model that is to be saved to your database. Have the INSERT in your data access layer return the newly created key ("ID") in the database. Use this key to now set the "ID" field on your model that's passed in to the action result and then passed back to the view as JSON. Now the grid should know it's just created this record and does not need to do anything more with it. Otherwise, the model object returns with the "ID" field set to 0 so the grid thinks it still needs to add this record.

[AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Grid_Create([DataSourceRequest] DataSourceRequest request, MyObject obj)
    {
        if (obj != null && ModelState.IsValid)
        {
            obj.Id = _myService.Create(obj);
        }

        return Json(new[] { obj }.ToDataSourceResult(request, ModelState));
    }

Solution 3

This error occur when you did not pass Primary key to view in read action.

Regards

Solution 4

An alternative to Quinton Bernhardt's complete event: bind the dataSource.read() to the kendo sync event.

I'm using kendo's C# MVC's html helpers which don't expose the sync event, so I had to modify it after setting up the grid.

On window load:

var grid = $("#GridName").data("kendoGrid");
grid.dataSource.bind("sync", function () {
     $("#GridName").data("kendoGrid").dataSource.read();
});

The sync event fires after the save request has been completed. The dataSource.read() gets the latest from the server, including the id that was set server side.

Solution 5

I had a similar issue, did various trials but fixed with the following trial

Jquery

   create: {
                type: "POST",
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                url: "../../ajax/ajaxGv.aspx/addstaff"
            },

            parameterMap: function (options, operation) {
                if (operation == "create" && options.models) {  
                    return JSON.stringify({ "oStaff": options.models });
                }

VB.Net

   'Adding Staff
   <System.Web.Services.WebMethod()> _
 Public Shared Sub addStaff(ByVal oStaff As Object)
    Dim strJson As String = JsonConvert.SerializeObject(oStaff)
    Dim lstStaff As List(Of Staff) = JsonConvert.DeserializeObject(Of List(Of Staff))(strJson)
    Dim db As New LiveB2cDataContext
    Try

            db.Staff.InsertAllOnSubmit(lstStaff)
        Next
        db.SubmitChanges()

      'Fix is that you need to return the objects you have added in the database back as json to kendo

        strJson = JsonConvert.SerializeObject(lstStaff, Formatting.None)
        WriteJson(strJson) ' Returning the objects added as json back to Kendo
    Catch ex As Exception
        ErrorLog(ex)
    End Try

End Sub

Public Shared Sub WriteJson(strJson As String)
    Try

        HttpContext.Current.Response.Write(strJson)
        HttpContext.Current.Response.Flush()
        HttpContext.Current.ApplicationInstance.CompleteRequest()
        HttpContext.Current.Response.SuppressContent = True

    Catch ex As Exception
        ErrorLog(ex)
    End Try
End Sub

Fix is that you need to return the objects you have added in the database back as json to kendo

Share:
12,906
Artem A
Author by

Artem A

I am a software engineer (.net). I like good code which could be easily maintained and tested, use best practices and effective solutions. I work in a US e-commerce company, but opened for any new opportunities.

Updated on June 04, 2022

Comments

  • Artem A
    Artem A almost 2 years

    I have same problem as Daniel had in this topic, but his solution doesn't work for me:

    http://www.kendoui.com/forums/ui/grid/kendo-ui-grid-inserts-updates-create-duplicate-records.aspx#-jhxqRrNAUGsTFJaC-Ojwg

    So use-case. Users adds 2 new records one after another:

    1. Presses "Add new record" button of a grid
    2. Fills the fields (name="Alex", amount=10, comment="first").
    3. Record one is ready. Press 'Save'. (data goes to controller and than to Database)
    4. User see one record in a grid

    5. Press "Add new record" button again

    6. Fills fields (name="Bob", amount=20, comment = "second").
    7. Record one is ready. Press 'Save'. Data goes to controller and than to Database. In this moment something happens and grid send Ajax request again with record one data to controller.

    8. User updates grid and see three records

      "Alex | 10 | first" (duplicated record) ID = 1

      "Bob | 20 | second" ID = 2

      "Alex | 10 | first" ID = 1

    They recommend to return an ID for correct binding\update of data source with new record. And I return it (new ID from database comes in response with bouns entity)! and this doesn't help. Only if I add first record and refresh page with F5 and after that add second record everything is ok. But if add another one, the third records - problems appears again

    Code in controller:

    [HttpPost]
        public JsonResult Create(BonusDto bonusDto)
        {
            BonusAggregate bonus;
    
            if (bonusDto.Amount <= 0)
                throw new ArgumentOutOfRangeException("Amount should be more than 0");
    
            if (bonusDto.EmployeeId <= 0)
                throw new ArgumentNullException("You should specify an existing employee");
    
            using (var dbContext = new DatabaseContext())
            {
                BonusesRepository = new BonusesRepository(dbContext);
                var employeeRepository = new EmployeesRepository(dbContext);
                bonus = new BonusFactory(employeeRepository).Create(bonusDto);
    
                BonusesRepository.Save(bonus);
            }
    
            HttpContext.Response.StatusCode = (int)HttpStatusCode.Created;
            return Json(bonus); // try to return ID after bonus was saved
        }
    

    UI Code

    // creates bonuses grid control
    $("#bonusesGrid").kendoGrid({
        dataSource: bonusesDataSource,
        toolbar: ["create"],
        editable: "inline",
        columns: [
            "BonusId",
            "EmployeeId",
            {
                field: "EmployeeLastName",
                editor: employeeAutocompletingEditor,
                template: "#=EmployeeLastName#"
            },
            "Amount",
            {
                field: "Comment",
                titel: "Comment",
                editor: textareaEditor,
                filterable: {
                    operators: {
                        number: {
                            contains: "Contains"
                        }
                    }
                }
            },
            {
                command: ["edit"],
                title: " "
            }
        ],
        save: function(e) {
            if (newValueEmployeeId !== undefined && 
                                newValueEmployeeLastName !== undefined && 
                                newValueEmployeeLastName !== "") {
                                  setNewValueEmployeeIdAndLastName(newValueEmployeeId, newValueEmployeeLastName);
                                  gridDataSource.model.EmployeeId = newValueEmployeeId; // it's a hack to bind model and autocomplete control
                                  gridDataSource.model.EmployeeLastName = newValueEmployeeLastName;
                            } else {
                                  gridDataSource.model.EmployeeId = currentValueEmployeeId;
                                  gridDataSource.model.EmployeeLastName = currentValueEmployeeLastName;
    
                            }
        },
        edit: function(e) {
            setCurrentValueEmployeeIdAndLastName(e.model.EmployeeId, e.model.EmployeeLastName);
        },
        cancel: function(e) {
            setCurrentValueEmployeeIdAndLastName(e.model.EmployeeId, e.model.EmployeeLastName);
        }
    });
    

    Bonus data source:

    // bind json result from /Bonuses/GetPagedJsonBonuses
            var bonusesDataSource = new kendo.data.DataSource({
                    transport: {
    
                        read: {
                             url: "@Url.Action("GetPagedJsonBonuses", "Bonuses")",
                             type : "GET",
                             contentType: "application/json",
                             dataType: "json",
                             cache: false
                        },
                        create: {
                            url: "@Url.Action("Create", "Bonuses")",
                            dataType: "json",
                            type: "POST"
                        },
                        parameterMap: function(options, operation) {
                            if (operation === "update" || operation === "create") {
    
                                // correct format for conversion 
                                var d = new Date(options.Date);
                                options.Date = kendo.toString(d, dateFormat);
    
                                // updates the BonusDTO.EmployeeId with selected value
                                if (newValueEmployeeId !== undefined)
                                    options.EmployeeId = newValueEmployeeId;
                            }
                            if(operation === "read") {
                                options.filter = setFormattedFilterDate(options.filter);
                            }
                            return options;
                        }
                    },
                    pageSize: 15,
                    serverPaging: true,
                    serverSorting: true,
                    serverFiltering: true,
                    error: showErrorMessage,
                    schema: {
                        data: "Data", // PagedResponse.Data
                        total: "TotalCount", // PagedResponse.TotalCount
                        model: {
                            id: "BonusId",  // Data
                            fields: {
                                EmployeeId: { type: "number" },
                                EmployeeLastName: {
                                    type: "string",
                                    editable: true,
                                    nulable: false,
                                    validation: { required: {message: "Employee's last name is required"}}
                                },
                                Date: {
                                    type: "date",
                                    editable: true,
                                    nullable: false,
                                    validation: {
                                        required: { message: "Date is required to be set" }
                                    }
                                },
                                Amount: {
                                    type: "number",
                                    editable: true,
                                    nullable: false,
                                    defaultValue: 1,
                                    validation: {
                                        required: { message: "Amount is required to be set" }
                                    }
                                },
                                Comment: { type: "string", editable: true }
                            } // fields
                        } // model
                    }// schema 
                });
    
  • Artem A
    Artem A almost 11 years
    thanks, I'll try it. Is '#grid' an element name of a grid? and what is kendoGrid in data("kendoGrid") ???
  • Quinton Bernhardt
    Quinton Bernhardt almost 11 years
    It's the element id of the grid. <div id="grid" style="width: 960px">. The kendoGrid etc. are internal KendoGrid values so they will work for you.
  • Artem A
    Artem A almost 11 years
    thanks again. It works. But I explored documentation and found no inforamtion about event "complete". How did you find it?
  • Quinton Bernhardt
    Quinton Bernhardt almost 11 years
    I wen't back to look and i can't find it either! it must of either been in the older docs or an answer on SO somewhere. The closest info i could find was on a Kendo forum - but not the same events. kendoui.com/forums/framework/data-source/…
  • Artem A
    Artem A almost 11 years
    do you mean this part of code: schema: { model: { id: "BonusId", // THIS ONE? fields: { EmployeeId: { ...}, EmployeeLastName: {...}.... If so, that's correct. ID is not a part of the list of fields. ID has to be specified directly in "model" area.
  • GP24
    GP24 almost 10 years
    This answer is better. You do not need to have the grid read again just because you inserted - it will deal with that in the UI.
  • Warren Parks
    Warren Parks over 9 years
    Yes you need to make sure that the id is set and is named the same as it is defined in your schema. See the important note in this link for details. docs.telerik.com/kendo-ui/api/javascript/data/…
  • callisto
    callisto about 9 years
    This worked for my non-hierarchical grids only. The hierarchical grids still duplicate inserts when user adds multiple times.