从剑道网格执行Web API补丁操作时,无法正确序列化导航属性



我目前正在使用Web API v2与OData v3连接到剑道网格。我有问题,使网格正确序列化模型到AsyncEntitySetController<TEntity, TKey>类上的PatchEntityAsync方法。传递给PatchEntityAsync方法的Delta<TEntity>null,这显然是不正确的。

首先,实体框架模型。我有一个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; }
}

然后是Game的定义,每个Game实例都有一个对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; }
}

当使用JSON查询Game,并在GameSeries属性上发出$expand时,我得到以下内容,这是预期的/正确的:

{
    "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...
  ]
}

我通过OData Web API (Microsoft.AspNet.WebApi。OData 5.2.0)端点到剑道UI网格。下面是网格的配置:

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

这将正确渲染网格,我将得到GameSeries.Description显示而不是GameSeries的数字ID。

然而,我认为部分问题来自于我如何定义自定义编辑器,特别是剑道需要的data属性:

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

我得到的感觉我应该使用点符号来引用Game实例上的GameSeries属性,但我不确定如何。

另外,我认为这里的绑定导致了create命令失败。应该有一些方法来设置数据绑定属性,以允许新的创建,以及编辑现有的。

但是,当我让编辑器弹出一个现有实例时,它会正确地使用填充了所有GameSeries实例的下拉列表。

我可以进行修改,当我这样做时,我通过Fiddler注意到主体被传递,尽管我注意到一些差异:

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

在这种情况下,GameSeriesId属性通过更改正确填充(我想要 4),但扩展的GameSeries属性有一个"GameSeriesId"为1。

调用时,传入的Delta<Game>实例为空。

我试过了:

我注意到扩展的GameSeries属性上的GameSeriesId属性没有字符串化。我已经将值更改为"1"",Delta<Game>实例仍然为空。

我已经复制了对OData点的调用,不包括扩展的GameSeries属性,所以有效负载看起来像这样:

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

填充Delta<Game>。我不确定是否从有效载荷中删除扩展的GameSeries属性是正确的方法,或者是否应该在服务器端处理,或者在Kendo网格中处理。

由于外键id已成功更改,您可以在更新时排除导航属性GameSeries

当只使用外键id更新关系时,

EF工作得很好。

所以让OData指向包括GameSeries,但在进行更新时排除它。您可以使用parameterMap来拦截更新操作。

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

要使编辑器与网格同步,您需要绑定更改事件并手动更改网格中模型的属性。

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

最新更新