AdventureWorks2012用于填充搜索引擎的数据。 可以在这里下载。
以下是使用的传输方法(Entity子对象作为子文档保存在stateprovinces
索引中):
public void SaveToElasticsearchStateProvince()
{
IElasticsearchMappingResolver elasticsearchMappingResolver = new ElasticsearchMappingResolver();
using ( var elasticsearchContext = new ElasticsearchContext("http://localhost:9200/", new ElasticsearchSerializerConfiguration(elasticsearchMappingResolver,true,true)))
{
elasticsearchContext.TraceProvider = new ConsoleTraceProvider();
using (var databaseEfModel = new SQLDataModel())
{
int pointer = 0;
const int interval = 20;
bool firstRun = true;
int length = databaseEfModel.StateProvince.Count();
while (pointer < length)
{
var collection = databaseEfModel.StateProvince.OrderBy(t => t.StateProvinceID).Skip(pointer).Take(interval).ToList();
foreach (var item in collection)
{
var ee = item.CountryRegion.Name;
elasticsearchContext.AddUpdateDocument(item, item.StateProvinceID);
}
if (firstRun)
{
elasticsearchContext.SaveChangesAndInitMappingsForChildDocuments();
firstRun = false;
}
else
{
elasticsearchContext.SaveChanges();
}
pointer = pointer + interval;
}
}
}
}
注意:当您使用代码首先从Entity Framework中的现有数据库导入数据时,ElasticsearchCRUD要求将[key]
属性添加到子对象主键。 这些用于为每个文档定义文档_id
。 一个文件只能有一个_i
d!
ElasticSearchProvider
类实现了Elasticsearch的访问层。 存储层使用ElasticsearchCRUD
访问Elasticsearch。 需要为子/父文档配置ElasticsearchSerializerConfiguration
类。 为嵌套文档设置默认配置,因此需要更改。 我在默认构造函数中已经构造了所有内容,这也可以与使用构建注入的IoC
一起使用。 _context
与该类具有相同的生命周期,需要处理。
private const string ConnectionString = "http://localhost:9200/";
private readonly IElasticsearchMappingResolver _elasticsearchMappingResolver;
private readonly ElasticsearchContext _context;
public ElasticSearchProvider()
{
_elasticsearchMappingResolver = new ElasticsearchMappingResolver();
_elasticsearchMappingResolver.AddElasticsearchMappingForEntityType(
typeof(Address),
new ElasticsearchMappingAddress()
);
_context = new ElasticsearchContext(
ConnectionString,
new ElasticsearchSerializerConfiguration(
_elasticsearchMappingResolver,
true,
true
)
);
}
定义Address
类型的映射是因为这是父索引'stateprovinces'
中的子文档。 由于这个原因,默认映射需要被覆盖。 ElasticSearchMapping
类可以被继承,只需要实现一个方法:GetIndexForType
。 现在Address
类型将被正确映射。
using System;
using ElasticsearchCRUD;
namespace WebSearchWithElasticsearchChildDocuments.Search
{
public class ElasticsearchMappingAddress : ElasticsearchMapping
{
// This address type is a child type form stateprovince in the stateprovinces index
public override string GetIndexForType(Type type)
{
return "stateprovinces";
}
}
}
这个类然后在构造函数中添加到上面的Resolver中。
_elasticsearchMappingResolver.AddElasticsearchMappingForEntityType(
typeof(Address),
new ElasticsearchMappingAddress()
);
现在,搜索提供可以实现CRUD功能
public IEnumerable QueryString(string term)
{
return _context.Search(BuildQueryStringSearch(term)).PayloadResult.Hits.HitsResult.Select(t =>t.Source).ToList();
}
private Search BuildQueryStringSearch(string term)
{
var names = "";
if (term != null)
{
names = term.Replace("+", " OR *");
}
var search = new Search
{
Query = new Query(new QueryStringQuery(names + "*"))
};
return search;
}
public void AddUpdateDocument(Address address)
{
// if the parent has changed, the child needs to be deleted and created again. This in not required in this example
_context.AddUpdateDocument(address, address.AddressID, address.StateProvinceID);
_context.SaveChanges();
}
public void UpdateAddresses(long stateProvinceId, List addresses)
{
foreach (var item in addresses)
{
_context.AddUpdateDocument(item, item.AddressID, item.StateProvinceID);
}
_context.SaveChanges();
}
public void DeleteAddress(long addressId)
{
_context.DeleteDocument(addressId);
_context.SaveChanges();
}
public List GetAllStateProvinces()
{
var result = from element in _context.Search("").PayloadResult.Hits.HitsResult.Select(t => t.Source)
select new SelectListItem
{
Text = string.Format("StateProvince: {0}, CountryRegionCode {1}",
element.StateProvinceCode, element.CountryRegionCode),
Value = element.StateProvinceID.ToString()
};
return result.ToList();
}
public PagingTableResult GetAllAddressesForStateProvince(string stateprovinceid, int jtStartIndex, int jtPageSize, string jtSorting)
{
var result = new PagingTableResult();
var data = _context.Search(
BuildSearchForChildDocumentsWithIdAndParentType(
stateprovinceid,
"stateprovince",
jtStartIndex,
jtPageSize,
jtSorting)
);
result.Items = data.PayloadResult.ToList();
result.TotalCount = data.TotalHits;
return result;
}
// {
// "from": 0, "size": 10,
// "query": {
// "term": { "_parent": "parentdocument#7" }
// },
// "sort": { "city" : { "order": "desc" } }"
// }
private Search BuildSearchForChildDocumentsWithIdAndParentType(object parentId, string parentType, int jtStartIndex, int jtPageSize, string jtSorting)
{
var search = new Search
{
From = jtStartIndex,
Size = jtPageSize,
Query = new Query(new TermQuery("_parent", parentType + "#" + parentId))
};
var sorts = jtSorting.Split(' ');
if (sorts.Length == 2)
{
var order = OrderEnum.asc;
if (sorts[1].ToLower() == "desc")
{
order = OrderEnum.desc;
}
search.Sort = CreateSortQuery(sorts[0].ToLower(), order);
}
return search;
}
public SortHolder CreateSortQuery(string sort, OrderEnum order)
{
return new SortHolder(
new List
{
new SortStandard(sort)
{
Order = order
}
}
);
}
_context.Search
方法接受在Elasticsearch Search API中直接使用的任何Json字符串。 Elasticsearch提供了如何将这个Json查询放在一起的良好文档。 因为这个例子中的查询非常简单,所以我用StringBuilder添加了它们。 如果需要更复杂的查询,也许您应该使用NEST进行搜索功能。 ElasticsearchCRUD的重点是进行CRUD操作,轻松实现简单,嵌套或父/子文档的数据传输。
现在实现后端,需要为视图实现搜索控制器。 jTable
表格使用ajax请求直接访问控制器。 jTable
要求数据是所需的格式。 该表格执行分页并发送stateprovince
文档的parentId
(用于Elasticsearch中的地址子文档路由)
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using WebSearchWithElasticsearchChildDocuments.Search;
namespace WebSearchWithElasticsearchChildDocuments.Controllers
{
[RoutePrefix("Search")]
public class SearchController : Controller
{
readonly ISearchProvider _searchProvider = new ElasticSearchProvider();
[HttpGet]
public ActionResult Index()
{
return View();
}
[Route("Search")]
public JsonResult Search(string term)
{
return Json(_searchProvider.QueryString(term), "AddressListForStateProvince", JsonRequestBehavior.AllowGet);
}
[Route("GetAddressForStateProvince")]
public JsonResult GetAddressForStateProvince(string stateprovinceid, int jtStartIndex = 0, int jtPageSize = 0, string jtSorting = null)
{
try
{
var data = _searchProvider.GetAllAddressesForStateProvince(stateprovinceid, jtStartIndex, jtPageSize, jtSorting);
return Json(new { Result = "OK", Records = data.Items, TotalRecordCount = data.TotalCount });
}
catch (Exception ex)
{
return Json(new { Result = "ERROR", Message = ex.Message });
}
}
[Route("CreateAddressForStateProvince")]
public JsonResult CreateAddressForStateProvince(Address address)
{
try
{
_searchProvider.AddUpdateDocument(address);
return Json(new { Result = "OK", Records = address });
}
catch (Exception ex)
{
return Json(new { Result = "ERROR", Message = ex.Message });
}
}
[HttpPost]
[Route("DeleteAddress")]
public ActionResult DeleteAddress(long addressId, long selectedstateprovinceid)
{
_searchProvider.DeleteAddress(addressId, selectedstateprovinceid);
return Json(new { Result = "OK"});
}
}
}
Razor视图如下:
@model WebSearchWithElasticsearchChildDocuments.Models.SearchModel
<br/>
<fieldset class="form">
<legend>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>
<div id="addressResultsForStateProvince" />
<input name="selectedstateprovinceid" id="selectedstateprovinceid" type="hidden" value="" />
@section scripts
{
<link href="http://localhost:49907/Content/themes/flat/jquery-ui-1.10.3.min.css" rel="stylesheet" />
<link href="~/Scripts/jtable/themes/jqueryui/jtable_jqueryui.min.css" rel="stylesheet" />
<script type="text/javascript">
$(document).ready(function() {
var updateResults = [];
$("input#autocomplete").autocomplete({
source: function(request, response) {
$.ajax({
url: "http://localhost:49907/Search/search",
dataType: "json",
data: {
term: request.term,
},
success: function(data) {
var itemArray = new Array();
for (i = 0; i < data.length; i++) {
var labelData = data[i].Name + ", " + data[i].StateProvinceCode + ", " + data[i].CountryRegionCode;
itemArray[i] = { label: labelData, value: labelData, data: data[i] }
}
console.log(itemArray);
response(itemArray);
},
error: function(data, type) {
console.log(type);
}
});
},
select: function(event, ui) {
$("#selectedstateprovinceid").val(ui.item.data.StateProvinceID);
$('#addressResultsForStateProvince').jtable('load', {selectedstateprovinceid : ui.item.data.StateProvinceID});
console.log(ui.item);
}
});
$('#addressResultsForStateProvince').jtable({
title: 'Address list of selected StateProvince',
paging: true,
pageSize: 10,
sorting: true,
multiSorting: true,
defaultSorting: 'City asc',
actions: {
deleteAction: function (postData, jtParams) {
return $.Deferred(function ($dfd) {
$.ajax({
url: 'http://localhost:49907/Search/DeleteAddress?addressId=' + postData.AddressID + "&selectedstateprovinceid=" + $('#selectedstateprovinceid').val(),
type: 'POST',
dataType: 'json',
data: postData,
success: function (data) {
$dfd.resolve(data);
},
error: function () {
$dfd.reject();
}
});
});
},
listAction: function (postData, jtParams) {
return $.Deferred(function ($dfd) {
console.log(jtParams);
$.ajax({
url: 'http://localhost:49907/Search/GetAddressForStateProvince?stateprovinceid=' + $('#selectedstateprovinceid').val(),
type: 'POST',
dataType: 'json',
data: jtParams,
success: function (data) {
$dfd.resolve(data);
},
error: function () {
$dfd.reject();
}
});
});
},
createAction: function (postData) {
return $.Deferred(function ($dfd) {
$.ajax({
url: 'http://localhost:49907/Search/CreateAddressForStateProvince?stateprovinceid=' + $('#selectedstateprovinceid').val(),
type: 'POST',
dataType: 'json',
data: postData,
success: function (data) {
$dfd.resolve(data);
},
error: function () {
$dfd.reject();
}
});
});
},
updateAction: function(postData) {
return $.Deferred(function ($dfd) {
$.ajax({
url: 'http://localhost:49907/Search/CreateAddressForStateProvince?stateprovinceid=' + $('#selectedstateprovinceid').val(),
type: 'POST',
dataType: 'json',
data: postData,
success: function (data) {
$dfd.resolve(data);
},
error: function () {
$dfd.reject();
}
});
});
}
},
fields: {
AddressID: {
key: true,
create: true,
edit: true,
list: true
},
AddressLine1: {
title: 'AddressLine1',
width: '20%'
},
AddressLine2: {
title: 'AddressLine2',
create: true,
edit: true,
width: '20%'
},
City: {
title: 'City',
create: true,
edit: true,
width: '15%'
},
StateProvinceID: {
title: 'StateProvinceID',
create: false,
edit: false,
width: '10%'
},
PostalCode: {
title: 'PostalCode',
create: true,
edit: true,
width: '10%'
},
ModifiedDate: {
title: 'ModifiedDate',
edit: false,
create: false,
width: '15%',
display: function (data) { return moment(data.record.ModifiedDate).format('DD/MM/YYYY HH:mm:ss'); }
}
}
});
});
script>
}
当autocomplete 选择一个StateProvince
项时,它将加载具有子address 项的jTable
。 这些项目可以被编辑,删除或新的添加到父StateProvince
。 所需的javascript库和css文件都包含在MVC包中。 (jQuery-UI,moment.js和jTable)
这是它的外观: