我目前正在将Web API v2与OData v3链接到Kendo Grid。我在获取网格以将模型正确序列化到 PatchEntityAsync method上的 AsyncEntitySetController<TEntity, TKey> class时遇到问题。传递给Delta<TEntity>方法的PatchEntityAsyncnull,这显然是不正确的。

首先,实体框架模型。我有一个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并在$expand属性上发布GameSeries时,我得到以下期望/正确的信息:
{
    "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)端点向Kendo 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。

但是,我认为部分问题来自于我如何定义自定义编辑器,特别是Kendo要求的data属性:
$('<input data-text-field="Description" data-value-field="GameSeriesId" data-bind="value:GameSeriesId"/>')

我觉得我应该使用点表示法来引用GameSeries实例上的Game属性,但是我不确定如何使用。

另外,我相信这里的绑定(bind)会导致create命令失败。应该有一些方法可以设置数据绑定(bind)属性,以允许新创建以及编辑现有属性。

但是,当我让编辑器弹出一个现有实例时,它会正确地执行所有包含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>实例为null。

我试过的

我注意到扩展后的GameSeriesId属性上的GameSeries属性没有被字符串化。我已将值更改为"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;
}

更新

要将编辑器与网格同步,您需要绑定(bind)更改事件并在网格中手动更改模型的属性。
gameSeriesIdAutoCompleteEditor = function (container, options) {
    /* omitted code */
    .kendoDropDownList({
        /* omitted code */
        change: function (e) {
            options.model.GameSeries = this.dataItem();
        }

10-07 14:10