Cannot Serialize Navigation Properties Correctly When Performing API Patch Operation from Kendo Grid

I am currently using Web API v2 with OData v3 linked to Kendo grid. I'm having trouble getting the mesh to serialize the model correctly for a method PatchEntityAsync

per AsyncEntitySetController<TEntity, TKey>

class
. Delta<TEntity>

which is passed to the method PatchEntityAsync

, null

which is obviously wrong.

First, the Entity Framework models. I have a definition GameSeries

:

[Table("stats.GameSeries")]
public class GameSeries
{
    [Key]
    public int GameSeriesId { get; set; }

    [MaxLength(500)]
    [Required]
    public string Description { get; set; }

    public string Notes { get; set; }
}

      

And then there is a definition Game

, each instance Game

has an instance reference GameSeries

:

[Table("stats.Game")]
public class Game
{
    [Key]
    public int GameId { get; set; }

    [MaxLength(500)]
    [Required]
    public string Description { get; set; }

    public int GameSeriesId { get; set; }

    [ForeignKey("GameSeriesId")]
    public virtual GameSeries GameSeries { get; set; }

    public int Revision { get; set; }

    [MaxLength(100)]
    public string Tag { get; set; }

    public string Notes { get; set; }
}

      

When requesting for Game

using JSON and issuing $expand

in a property, GameSeries

I get the following, which is expected / correct:

{
    "odata.metadata":
        "http://localhost:7566/odata/$metadata#Games",
    "odata.count":"58",
    "value":[
    {
        "GameSeries": {
            "GameSeriesId": 1,
            "Description":"Street Fighter IV",
            "Notes":null
        },
        "GameId": 1,
        "Description": "Street Fighter IV",
        "GameSeriesId": 1,
        "Revision": 1,
        "Tag": null,
        "Notes": null
    }, {
        "GameSeries": {
            "GameSeriesId":1,
            "Description": "Street Fighter IV",
            "Notes": null
        },
        "GameId": 2,
        "Description": "Super Street Fighter IV",
        "GameSeriesId": 1,
        "Revision": 2,
        "Tag": null,
        "Notes": null
    },
    // And so on...
  ]
}

      

I view them through the OData Web API endpoint (Microsoft.AspNet.WebApi.OData 5.2.0) into the Kendo UI grid. Here's the mesh configuration:

function initializeGrid(selector, entitySet, key, modelFields, columns, expand) {
    // Edit and destroy commands.
    columns.push({ command: ["edit", "destroy"], title: "Operations" });

    // The main key is not editable.
    modelFields[key].editable = false;
    modelFields[key].defaultValue = 0;

    var baseODataUrl = "/odata/" + entitySet,
        options = {
            dataSource: {
                type: "odata",
                pageSize: 50,
                //autoSync: true,
                transport: {
                    read: {
                        url: baseODataUrl,
                        dataType: "json",
                        data: {
                            $expand: expand
                        }
                    },
                    update: {
                        url: function(data) {
                            return baseODataUrl + "(" + data[key] + ")";
                        },
                        type: "patch",
                        dataType: "json"
                    },
                    destroy: {
                        url: function (data) {
                            return baseODataUrl + "(" + data[key] + ")";
                        },
                        dataType: "json"
                    },
                    create: {
                        url: baseODataUrl,
                        dataType: "json",
                        contentType: "application/json;odata=verbose"
                    }
                },
                batch: false,
                serverPaging: true,
                serverSorting: true,
                serverFiltering: true,
                schema: {
                    data: function (data) {
                        return data.value;
                    },
                    total: function (data) {
                        return data["odata.count"];
                    },
                    model: {
                        id: key,
                        fields: modelFields
                    }
                }
            },
            height: 550,
            toolbar: ["create"],
            filterable: true,
            sortable: true,
            pageable: true,
            editable: "popup",
            navigatable: true,
            columns: columns
        };

    selector.kendoGrid(options);
}

$(function () {
    var baseODataUrl = "/odata/",
        gameSeriesIdDataSource = new kendo.data.DataSource({
            type: "odata",
            schema: {
                data: function (data) {
                    return data.value;
                },
                total: function (data) {
                    return data["odata.count"];
                }
            },
            transport: {
                read: {
                    url: baseODataUrl + "GameSeries",
                    dataType: "json"
                }
            }
        }),
        gameSeriesIdAutoCompleteEditor = function(container, options) {
            $('<input data-text-field="Description" data-value-field="GameSeriesId" data-bind="value:GameSeriesId"/>')
                .appendTo(container)
                .kendoDropDownList({
                    autoBind: false,
                    dataSource: gameSeriesIdDataSource,
                    dataTextField: "Description",
                    dataValueField: "GameSeriesId"
                });
        };

    initializeGrid($("#grid"), "Games", "GameId", {
        GameId: {
            title: "Game ID",
            editable: false
        },
        Description: { type: "string" },
        GameSeriesId: { type: "integer" },
        Revision: { type: "integer" },
        Tag: { type: "string" },
        Notes: { type: "string" }
    }, [
        { field: "GameId", title: "Game ID" },
        "Description",
        { field: "GameSeries.Description", title: "Game Series", editor: gameSeriesIdAutoCompleteEditor },
        "Revision",
        "Tag",
        "Notes"
    ], "GameSeries");
});
}(jQuery));

      

This will lead to the correct grid where I get the GameSeries.Description

numeric ID instead GameSeries

.

However, I believe that part of the problem has to do with how I define the custom editor, specifically the attributes data

that Kendo requires:

$('<input data-text-field="Description" data-value-field="GameSeriesId" data-bind="value:GameSeriesId"/>')

      

It seems to me that I should be using dot notation to refer to a property GameSeries

on an instance Game

, but I'm not sure how.

Also, I believe the binding here is causing the create command to fail. There must be some way to set the data-binding attributes that will allow new creations to be created as well as editing existing ones.

However, when I get the editor for the popup for an existing instance, it does so correctly with the dropdown populated with all instances GameSeries

.

I can make changes and when I do, I notice through Fiddler that the body is going through, although I do notice some inconsistencies:

{
    "GameSeries": {
        "GameSeriesId": 1,
        "Description": "Street Fighter IV",
        "Notes": null
    },
    "GameId": "1",
    "Description":
    "Street Fighter IV",
    "GameSeriesId": "4",
    "Revision": "1",
    "Tag": "Test",
    "Notes": null
}

      

In this case, the property is GameSeriesId

populated correctly with the change (I want 4), but the extended property GameSeries

has a "GameSeriesId" of 1.

When this call is made, the instance Delta<Game>

that passed is null.

What I have tried:

I noticed that the property GameSeriesId

in the extended property is GameSeries

not a string. I changed the value to "1"

"and the instance Delta<Game>

is still zero.

I have replicated the call to the OData point to not include the extended property GameSeries

, so the payload looks like this:

{
    "GameId": "1",
    "Description":
    "Street Fighter IV",
    "GameSeriesId": "4",
    "Revision": "1",
    "Tag": "Test",
    "Notes": null
}

      

And it fills up Delta<Game>

. I'm not sure if getting an extended property GameSeries

falling out of the payload is the right approach or should be done server side or Kendo grid.

+3


source to share


1 answer


Since the foreign key ID has been successfully changed, you can simply exclude the navigation property GameSeries

when doing the update.

EF works so well when updating a relationship with only foreign key id.

So let OData point to include GameSeries

, but exclude it when doing an update. You can use the Map parameter to intercept the update operation.

parameterMap: function (data, type) {
    if (type === "update") {
        delete data.GameSeries;
        return JSON.stringify(data);
    }

    // Returns as it is.
    return data;
}

      



Update

To keep the editor in sync with the grid, you need to bind the change event and manually change the model property on the grid.

gameSeriesIdAutoCompleteEditor = function (container, options) {
    /* omitted code */
    .kendoDropDownList({
        /* omitted code */
        change: function (e) {
            options.model.GameSeries = this.dataItem();
        }

      

+2


source







All Articles