您的位置:首页 > 其它

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,您可以创建复杂的搜索查询。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: