ElasticsearchCRUD使用(三)【嵌套文档的MVC】

具有1对n个实体的模型

用于与Elasticsearch进行交互的模型具有1到n的关系。 SkillWithListOfDetails类具有SkillDetail对象的列表。 这些类将作为嵌套对象使用SkillDetail列表保存到Elasticsearch。 这个子对象可以像父对象SkillWithListOfDetails中的任何其他属性一样进行搜索。

public class SkillWithListOfDetails
{
  [Required]
  [Range(1, long.MaxValue)]
  public long Id { get; set; }

  [Required]
  public string Name { get; set; }

  [Required]
  public string Description { get; set; }
  public DateTimeOffset Created { get; set; }
  public DateTimeOffset Updated { get; set; }

  public List<SkillDetail> SkillDetails { get; set; }
}

SkillDetail用作子类。 父外键不需要Id,因为当存储到Elasticsearch时,该子被NESTED。

public class SkillDetail
{
  [Required]
  [Range(1, long.MaxValue)]
  public long Id { get; set; }

  [Required]
  public string SkillLevel { get; set; }

  [Required]
  public string Details { get; set; }
  public DateTimeOffset Created { get; set; }
  public DateTimeOffset Updated { get; set; }
}

控制器创建,使用ElasticsearchCRUD创建

创建Elasticsearch功能使用EleashseachCRUD实现。 要使用NuGet下载ElasticsearchCRUD:
这里写图片描述
这将使用默认的IElasticSearchMappingResolver来保存索引为多元状态,将类型设置为不带命名空间的类名称,并将所有属性保存为小写。

一个id是必需的,不是自动生成的。 ElasticseachCRUD不支持自动生成的ID。 通常,Elasticsearch不是主要的持久性,而是将已有ID的文档保存到搜索引擎中的实体。 如果需要创建Id,您可以自动生成一个新的随机Guid。

private const string ConnectionString = "http://localhost:9200/";
private readonly IElasticsearchMappingResolver _elasticsearchMappingResolver = new ElasticsearchMappingResolver();

public void AddUpdateEntity(SkillWithListOfDetails skillWithListOfDetails)
{
    using (var context = new ElasticsearchContext(ConnectionString, _elasticsearchMappingResolver))
    {
        context.AddUpdateDocument(skillWithListOfDetails, skillWithListOfDetails.Id);
        context.SaveChanges();
    }
}

然后,提供者可以在SearchController中使用。 action方法接受模型和包含SkillDetail实体的子列表的字符串。 此createSKillDetailsList字符串属性是使用javascriptjTable从视图创建的json字符串。

[HttpPost]
[Route("Index")]
public ActionResult Index(SkillWithListOfDetails model, string createSkillDetailsList)
{
    if (ModelState.IsValid)
    {
        model.Created = DateTime.UtcNow;
        model.Updated = DateTime.UtcNow;

        model.SkillDetails =
            JsonConvert.DeserializeObject(createSkillDetailsList, typeof(List<SkillDetail>)) as List<SkillDetail>;

        _searchProvider.AddUpdateDocument(model);
        return Redirect("Search/Index");
    }

    return View("Index", model);
}

创建视图是一个简单的MVC razor 局部视图。 该视图使用SkillWithListOfDetails模型,并向MVC控制器动作发送一个简单的表单。 输入按钮调用一个javascript函数,它从jTable创建表格中获取所有SkillDetail行,并将其添加到输入隐藏项。 然后它执行submti()

@model WebSearchWithElasticsearchNestedDocuments.Search.SkillWithListOfDetails
<div id="createForm">
    @using (Html.BeginForm("Index", "Search"))
    {
        @Html.ValidationSummary(true)

        <fieldset class="form">
            <legend>CREATE a new document in the search engine</legend>
            <table width="800">
                <tr>
                    <th></th>
                    <th></th>
                </tr>
                <tr>
                    <td>
                        @Html.Label("Id:")
                    </td>
                    <td>
                        @Html.EditorFor(model => model.Id)
                        @Html.ValidationMessageFor(model => model.Id)
                    </td>
                </tr>
                <tr>
                    <td>
                        @Html.Label("Name:")
                    </td>
                    <td>
                        @Html.EditorFor(model => model.Name)
                        @Html.ValidationMessageFor(model => model.Name)
                    </td>
                </tr>
                <tr>
                    <td>
                        @Html.Label("Description:")
                    </td>
                    <td>
                        @Html.EditorFor(model => model.Description)
                        @Html.ValidationMessageFor(model => model.Description)
                    </td>
                </tr>
                <tr>
                    <td colspan="2">
                        <div id="createtableskilldetails" />
                        <input id="createSkillDetailsList" name="createSkillDetailsList" type="hidden" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <br />
                        <input type="button" onclick="SumbitCreateForm()" value="Add Skill" style="width:200px" />
                    </td>
                    <td></td>
                </tr>
            </table>
        </fieldset>
    }

</div>

SearchCreate是一个MVC PartialView。 这在Index View中使用。 Index View包含所有的JavaScript实现。 这应该在单独的js文件中实现并捆绑。 JavaScript代码使用3个js库,moment.jsjTablejQuery(带UI)。

@model WebSearchWithElasticsearchNestedDocuments.Search.SkillWithListOfDetails

<fieldset class="form">
    <legend>SEARCH for a document in the search engine</legend>
    <table width="500">
        <tr>
            <th></th>
        </tr>
        <tr>
            <td>
                <label for="autocomplete">Search: </label>
            </td>
        </tr>
        <tr>
            <td>
                <input id="autocomplete" type="text" style="width:500px" />
            </td>
        </tr>
    </table>
</fieldset>

@section scripts
{
    <link href="~/Scripts/jtable/themes/jqueryui/jtable_jqueryui.min.css" rel="stylesheet" />
    <script src="~/Scripts/jtable/jquery.jtable.min.js"></script>
    <script src="~/Scripts/moment.min.js"></script>
    <script type="text/javascript">

        $(document).ready(function () {

            var updateResults = [];
            $("input#autocomplete").autocomplete({
                source: function(request, response) {
                    $.ajax({
                        url: "http://localhost:50227/Search/search",
                        dataType: "json",
                        data: {
                            term: request.term,
                        },
                        success: function(data) {
                            var itemArray = new Array();
                            for (i = 0; i < data.length; i++) {
                                itemArray[i] = { label: data[i].Name, value: data[i].Name, data: data[i] }
                            }

                            console.log(itemArray);
                            response(itemArray);
                        },
                        error: function(data, type) {
                            console.log(type);
                        }
                    });
                },
                select: function(event, ui) {
                    $("#spanupdateId").text(ui.item.data.Id);
                    $("#spanupdateCreated").text(moment(ui.item.data.Created).format('DD/MM/YYYY HH:mm:ss'));
                    $("#spanupdateUpdated").text(moment(ui.item.data.Updated).format('DD/MM/YYYY HH:mm:ss'));
                    $("#updateName").text(ui.item.data.Name);
                    $("#updateDescription").text(ui.item.data.Description);
                    $("#updateName").val(ui.item.data.Name);
                    $("#updateDescription").val(ui.item.data.Description);

                    if (ui.item.data.SkillDetails) {
                        updateResults = ui.item.data.SkillDetails;
                    }

                    $('#updatetableskilldetails').jtable('load');

                    $("#updateId").val(ui.item.data.Id);
                    $("#updateCreated").val(ui.item.data.Created);
                    $("#updateUpdated").val(ui.item.data.Updated);

                    $("#spandeleteId").text(ui.item.data.Id);
                    $("#deleteId").val(ui.item.data.Id);
                    $("#deleteName").text(ui.item.data.Name);

                    console.log(ui.item);
                }
            });

            $('#updatetableskilldetails').jtable({
                title: 'Skill Details',
                paging: false,
                pageSize: 5,
                sorting: true,
                multiSorting: true,
                defaultSorting: 'Name asc',
                actions: {
                    listAction: function (postData) {
                        console.log("Loading from custom function...");
                        return {
                            "Result": "OK",
                            "Records": updateResults 
                        };
                    },
                    deleteAction: function (postData) {
                        console.log("delete action called for:" + JSON.stringify(postData));
                        return {
                            "Result": "OK"
                        };
                    },
                    createAction: function (postData) {
                        var data = getQueryParams(postData);
                        return {
                            "Result": "OK",
                            "Record": { "Id": data["Id"], "SkillLevel": data["SkillLevel"], "Details": data["Details"], "Created": data["Created"], "Updated": moment() }
                        }
                    },
                    updateAction: function (postData) {
                        return {
                            "Result": "OK",
                        };
                    }
                },
                fields: {
                    Id: {
                        key: true,
                        create: true,
                        edit: true,
                        list: true
                    },
                    SkillLevel: {
                        title: 'SkillLevel',
                        width: '20%'
                    },
                    Details: {
                        title: 'Details',
                        width: '30%'
                    },
                    Created: {
                        title: 'Created',
                        edit: false,
                        create: false,
                        width: '20%',
                        display: function (data) { return moment(data.record.Created).format('DD/MM/YYYY HH:mm:ss'); }
                    },
                    Updated: {
                        title: 'Updated',
                        edit: false,
                        create: false,
                        width: '20%',
                        display: function (data) { return moment(data.record.Updated).format('DD/MM/YYYY HH:mm:ss'); }
                    }
                }
            });

            $('#createtableskilldetails').jtable({
                title: 'Skill Details',
                paging: false,
                pageSize: 5,
                sorting: true,
                multiSorting: true,
                defaultSorting: 'Name asc',
                actions: {
                    deleteAction: function (postData) {
                        console.log("delete action called for:" + JSON.stringify(postData));
                        return {
                            "Result": "OK"
                        };
                    },
                    createAction: function(postData) {
                        var data = getQueryParams(postData);
                        return {
                            "Result": "OK",
                            "Record": { "Id": data["Id"], "SkillLevel": data["SkillLevel"], "Details": data["Details"], "Created": moment(), "Updated": moment() }
                        }
                    },
                    updateAction: function(postData) {
                        return {
                            "Result": "OK",
                        };
                    }
                },
                fields: {
                    Id: {
                        key: true,
                        create: true,
                        edit: true,
                        list: true
                    },
                    SkillLevel: {
                        title: 'SkillLevel',
                        width: '20%'
                    },
                    Details: {
                        title: 'Details',
                        width: '30%'
                    },
                    Created: {
                        title: 'Created',
                        edit: false,
                        create: false,
                        width: '20%',
                        display: function(data) { return moment(data.record.Created).format('DD/MM/YYYY HH:mm:ss'); }
                    },
                    Updated: {
                        title: 'Updated',
                        edit: false,
                        create: false,
                        width: '20%',
                        display: function(data) { return moment(data.record.Updated).format('DD/MM/YYYY HH:mm:ss'); }
                    }
                }
            });      
        }); // End of document ready

        function getQueryParams(qs) {
            qs = qs.split("+").join(" ");

            var params = {},
                tokens,
                re = /[?&]?([^=]+)=([^&]*)/g;

            while (tokens = re.exec(qs)) {
                params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
            }

            return params;
        }

        function getAllRowsOfjTableUpdateSkillDetailsList() {
            var $rows = $('#updatetableskilldetails').find('.jtable-data-row');
            var headers = ["Id", "SkillLevel", "Details", "Created", "Updated"];

            var data = [];
            $.each($rows, function() {
                var rowData = {};
                for (var j = 0; j < 5; j++) {
                    console.log(headers[j] + ":" +  this.cells[j].innerHTML);

                    rowData[headers[j]] = this.cells[j].innerHTML;
                }
                data.push(rowData);
            });

            $("#updateSkillDetailsList").val(JSON.stringify(data));
        }

        function getAllRowsOfjTableCreateSkillDetailsList() {
            var $rows = $('#createtableskilldetails').find('.jtable-data-row');
            var headers = ["Id", "SkillLevel", "Details", "Created", "Updated"];

            var data = [];
            $.each($rows, function () {
                var rowData = {};
                for (var j = 0; j < 5; j++) {
                    console.log(headers[j] + ":" + this.cells[j].innerHTML);

                    rowData[headers[j]] = this.cells[j].innerHTML;
                }
                data.push(rowData);
            });

            $("#createSkillDetailsList").val(JSON.stringify(data));
        }

        function SumbitUpdateForm() {
            getAllRowsOfjTableUpdateSkillDetailsList();
            $("#updateForm form").submit();
        }

        function SumbitCreateForm() {
            getAllRowsOfjTableCreateSkillDetailsList();
            $("#createForm form").submit();
        }

    </script>
}

@Html.Partial("SearchUpdate")

@Html.Partial("SearchDelete")

@Html.Partial("SearchCreate")

现在可以创建具有嵌套对象数组的新的Elastissearch文档。视图看起来像这样:
这里写图片描述

Elasticsearch 索引和映射

当您检查Elasticsearch搜索引擎中的映射时,您将找到具有嵌套数组子项的新文档。
http://localhost:9200//_mapping

{
  "skillwithlistofdetailss": {
    "mappings": {
      "skillwithlistofdetails": {
        "properties": {
          "created": { "type": "date", "format": "dateOptionalTime" },
          "description": { "type": "string" },
          "id": { "type": "long" },
          "name": { "type": "string" },
          "skilldetails": { "properties": { "created": { "type": "date", "format": "dateOptionalTime" }, "details": { "type": "string" }, "id": { "type": "long" }, "skilllevel": { "type": "string" }, "updated": { "type": "date", "format": "dateOptionalTime" } } },
          "updated": { "type": "date", "format": "dateOptionalTime" } }
      }
    }
  }
}

使用查询字符串搜索进行搜索

搜索是使用包含查询字符串搜索的搜索类构建的。 此查询使用可用于自动完成的通配符。 这可以通过使用不同的查询类型进行优化。

该term在每个结尾处被分成具有*通配符的不同搜索词。 搜索也搜索嵌套数组。

private static readonly Uri Node = new Uri(ConnectionString);

public IEnumerable<SkillWithListOfDetails> QueryString(string term)
{
    var names = "";
    if (term != null)
    {
        names = term.Replace("+", " OR *");
    }

    var search = new ElasticsearchCRUD.Model.SearchModel.Search
    {
        From= 0,
        Size = 10,
        Query = new Query(new QueryStringQuery(names + "*"))
    };
    IEnumerable<SkillWithListOfDetails> results;
    using (var context = new ElasticsearchContext(ConnectionString, _elasticSearchMappingResolver))
    {
        results = context.Search<SkillWithListOfDetails>(search).PayloadResult.Hits.HitsResult.Select(t => t.Source);
    }
    return results;
}

然后在SearchController中使用搜索。 这将使用直接从autocomplete 控件使用的Json数组返回集合。

$[Route("Search")]
public JsonResult Search(string term)
{
    return Json(_searchProvider.QueryString(term), "SkillWithListOfDetails", JsonRequestBehavior.AllowGet);
}

查看jTable的autocomplete
autocomplete 控件使用此Json结果,并允许用户选择单个文档。 当选择一个文档时,它将显示在更新控件中。

<input id="autocomplete" type="text" style="width:500px" />

$("input#autocomplete").autocomplete({
source: function(request, response) {
    $.ajax({
        url: "http://localhost:50227/Search/search",
        dataType: "json",
        data: {
            term: request.term,
        },
        success: function(data) {
            var itemArray = new Array();
            for (i = 0; i < data.length; i++) {
                itemArray[i] = { label: data[i].Name, value: data[i].Name, data: data[i] }
            }

            console.log(itemArray);
            response(itemArray);
        },
        error: function(data, type) {
            console.log(type);
        }
    });
},
select: function(event, ui) {
    $("#spanupdateId").text(ui.item.data.Id);
    $("#spanupdateCreated").text(moment(ui.item.data.Created).format('DD/MM/YYYY HH:mm:ss'));
    $("#spanupdateUpdated").text(moment(ui.item.data.Updated).format('DD/MM/YYYY HH:mm:ss'));
    $("#updateName").text(ui.item.data.Name);
    $("#updateDescription").text(ui.item.data.Description);
    $("#updateName").val(ui.item.data.Name);
    $("#updateDescription").val(ui.item.data.Description);

    if (ui.item.data.SkillDetails) {
        updateResults = ui.item.data.SkillDetails;
    }

    $('#updatetableskilldetails').jtable('load');

    $("#updateId").val(ui.item.data.Id);
    $("#updateCreated").val(ui.item.data.Created);
    $("#updateUpdated").val(ui.item.data.Updated);

    $("#spandeleteId").text(ui.item.data.Id);
    $("#deleteId").val(ui.item.data.Id);
    $("#deleteName").text(ui.item.data.Name);

    console.log(ui.item);
}
});

更新控件显示父对象和子对象。类Skilldetails的子列表显示在jTable JavaScript组件中。
这里写图片描述

moment.js的DateTime

moment.js库用于以可读格式显示Json DateTime项。 然后在jTable和输入表单中使用这些项。

这个包可以使用NuGet(Moment.js)下载。 使用方法如下:

moment(ui.item.data.Created).format('DD/MM/YYYY HH:mm:ss')

使用ElasticsearchCRUD进行更新

更新方法从视图接收数据,并更新所有更新的时间戳。 类SkillDetail的子列表被添加到实体,然后在Elasticsearch中更新。

public void UpdateSkill(long updateId, string updateName, string updateDescription, List<SkillDetail> updateSkillDetailsList)
{
    using (var context = new ElasticsearchContext(ConnectionString, _elasticsearchMappingResolver))
    {
        var skill = context.GetDocument<SkillWithListOfDetails>(updateId);
        skill.Updated = DateTime.UtcNow;
        skill.Name = updateName;
        skill.Description = updateDescription;
        skill.SkillDetails = updateSkillDetailsList;

        foreach (var item in skill.SkillDetails)
        {
            item.Updated = DateTime.UtcNow;
        }

        context.AddUpdateDocument(skill, skill.Id);
        context.SaveChanges();
    }
}

delete方法使用_id字段删除文档。

ElasticsearchCRUD进行删除

public void DeleteSkill(long deleteId)
{
    using (var context = new ElasticsearchContext(ConnectionString, _elasticsearchMappingResolver))
    {
        context.DeleteDocument<SkillWithListOfDetails>(deleteId);
        context.SaveChanges();
    }
}

结论

使用ElasticsearchCRUD,可以轻松添加,更新,删除1到n个关系的文档。 子元素嵌套在父文档中。 支持集合或对象数组以及简单类型集合/数组。 使用Elasticsearch与ElasticsearchCRUD,您可以创建复杂的搜索查询。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值