ElasticsearchCRUD使用(三)【嵌套文档的MVC】
2017-05-11 21:54
507 查看
具有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字符串属性是使用
javascript和
jTable从视图创建的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.js和
jTable和
jQuery(带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,您可以创建复杂的搜索查询。相关文章推荐
- ElasticsearchCRUD使用(二)【简单的文档进行搜索的MVC应用程序】
- ElasticsearchCRUD使用(十三)【Elasticsearch谷歌地图搜索的MVC应用】
- Elasticsearch压缩索引——lucene倒排索引本质是列存储+使用嵌套文档可以大幅度提高压缩率
- ElasticsearchCRUD使用(五)【Elasticsearch中的子文档,父文档】
- ElasticsearchCRUD使用(六)【EF和Elasticsearch的MVC应用程序】
- 008-elasticsearch【二】Url方式索引CRUD、文档操作、批量操作
- 《Entity Framework 6 Recipes》中文翻译系列 (20) -----第四章 ASP.NET MVC中使用实体框架之在MVC中构建一个CRUD示例
- 在MVC中使用word进行文档协作
- Elasticsearch技术解析与实战(二)文档的CRUD操作
- ElasticsearchCRUD使用(十)【Elasticsearch类型与ElasticsearchCRUD的映射】
- 使用Sense操作ElasticSearch CRUD
- elasticsearch核心知识--5.集群健康检查(green yellow red),文档CRUD
- 使用Sense操作ElasticSearch CRUD
- ElasticsearchCRUD使用(十九)【索引热身】
- elasticsearch结合spring springmvc jest 使用做成WEB架构
- ElasticsearchCRUD使用(十四)【ElasticsearchCRUD搜索查询和过滤】
- 使用Sense操作ElasticSearch CRUD
- 使用Sense操作ElasticSearch CRUD
- ElasticsearchCRUD使用(十八)【进行MVC搜索Elasticsearch高亮】
- [ElasticSearch]使用 java API 进行CRUD操作