如何搜索旅游好去处(MongoDb LINQ & .NET Core)






4.74/5 (11投票s)
演示文章,介绍如何使用 LINQ 查询 MongoDB,导入数据并定义索引
我们将使用 .NET Core 和 MongoDb 构建一个简单的 WebApi,以查询全球各地景点的详细信息。我们将使用 MongoDb LINQ 进行搜索,并运行不同的场景。
有关如何构建和测试完整的 .NET CORE WebApi 和 MongoDB 的简要介绍,请参阅我之前的文章:将 MongoDB .NET 驱动程序与 .NET Core WebAPI 结合使用。
您也可以从 GitHub 下载项目和 数据集:github.com/fpetru/WebApiQueryMongoDb。
在本文中,我将使用两个数据集
- Wikivoyage 提供了全球最受游客欢迎的博物馆、景点、餐厅和酒店的更多详细信息。原始数据集可从 以下网址 访问。
- 第二个数据集来自 GeoNames,这是一个涵盖所有国家的地理数据库。出于演示目的,我仅选择了人口超过 5000 居民的城市。
使用这些数据集,可以更容易地运行一些示例查询,检索到一致的数据量。
涵盖的主题
- MongoDb – 安装和安全设置
- MongoDB – 使用 mongoimport 工具
- 创建一个完整的 ASP.NET WebApi 项目,使用 MongoDB C# Driver v.2 进行异步连接
- 运行 LINQ 查询
安装说明
以下是所有需要安装的东西
- Visual Studio Community 2017,包含 .NET Core 选项
- MongoDB 和 Robomongo
MongoDB 配置
安装 MongoDB 后,您需要配置访问权限以及数据存储位置。
为此,请在本地创建一个名为 mongod.cfg 的文件。此文件将包含 MongoDB 服务器数据文件夹的路径以及 MongoDB 日志文件的路径,最初不包含任何身份验证(最后两行被注释掉)。请 更新这些本地路径,以反映您的本地设置。
systemLog:
destination: file
path: "C:\\tools\\mongodb\\db\\log\\mongo.log"
logAppend: true
storage:
dbPath: "C:\\tools\\mongodb\\db\\data"
#Once the admin user is created, remove the comments, and let the authorization be enabled
#security:
# authorization: enabled
在 命令提示符 中运行以下命令。这将启动 MongoDB 服务器,指向已创建的配置文件(如果服务器安装在自定义文件夹中,请先更新命令)。
"C:\Program Files\MongoDB\Server\3.4\bin\mongod.exe" --config C:\Dev\Data.Config\mongod.cfg
服务器启动后(您可以在日志文件中看到详细信息),在 命令提示符 中运行 mongo.exe。下一步是向数据库添加 管理员 用户。使用完整路径运行 MongoDB(例如:“C:\Program Files\MongoDB\Server\3.4\bin\mongo.exe”)并将以下代码复制粘贴到控制台中。
use admin
db.createUser(
{
user: "admin",
pwd: "abc123!",
roles: [ { role: "root", db: "admin" } ]
}
);
exit;
停止服务器,取消注释 mongod.cfg 文件中的最后两行,然后重新启动 MongoDb
服务器。
MongoImport – 使用大型数据集初始化数据库
我们将从 Wikivoyage 开始。该数据集最初可在(链接)找到。为了便于导入,我已经对其进行了少量转换(更改为制表符分隔文件,并进行了最基本的数据清理)。该文件可在 Github 上找到(链接)。
第二个数据集 GeoNames 可在同一个 Github 文件夹中找到(链接)。
运行脚本 import.bat(位于数据集的同一文件夹中),将导入数据,同时创建一个名为 TravelDb
的新数据库以及相关的索引。脚本包含在此处,但最好只运行脚本文件。
mongoimport --db TravelDb ^
--collection WikiVoyage ^
--type tsv ^
--fieldFile enwikivoyage-fields.txt^
--file enwikivoyage-20150901-listings.result.tsv^
--columnsHaveTypes^
--username admin ^
--password abc123! ^
--authenticationDatabase admin ^
--numInsertionWorkers 4
mongoimport --db TravelDb ^
--collection Cities ^
--type tsv ^
--fieldFile cities5000-fields.txt^
--file cities5000.txt ^
--columnsHaveTypes^
--username admin ^
--password abc123! ^
--authenticationDatabase admin ^
--numInsertionWorkers 4
字段文件 指定字段名称及其关联类型。使用 columnsHaveTypes
选项,我们可以导入所需类型的数据(例如 int
、double
、string
等)。
结果应该如下所示
MongoDB – LINQ 支持
此处包含的 .NET Core 解决方案遵循我之前文章的结构 – 将 MongoDB .NET 驱动程序与 .NET Core WebAPI 结合使用。在那里,我已经逐步介绍了如何从零开始创建一个 WebApi 解决方案,连接到 MongoDB 并实现 REST API 的所有基本操作。
相比之下,这里的 Web 控制器将只实现一个操作(GET
)– 主要侧重于运行不同的查询。
[NoCache]
[HttpGet]
public Task<IEnumerable<TravelItem>> Get()
{
return GetTravelItemsInternal();
}
private async Task<IEnumerable<TravelItem>> GetTravelItemsInternal()
{
return await _travelItemRepository.GetTravelItems();
}
在后台,查询使用 LINQ 语法运行,并返回前 500 条记录。
public async Task<IEnumerable<TravelItem>> GetTravelItems()
{
try
{
return await _context.TravelItems.Take(500).ToListAsync();
}
catch (Exception ex)
{
// log or manage the exception
throw ex;
}
}
查询在服务器端渲染,我们只接收有限的数据集。这是可能的,因为我们有 MongoDB C# Driver 原生提供的 IQueryable
类型 接口
。
...
using MongoDB.Driver.Linq;
...
public IMongoQueryable<TravelItem> TravelItems
{
get
{
return _database.GetCollection<TravelItem>
("WikiVoyage").AsQueryable<TravelItem>();
}
}
如何在特定城市查找活动
假设我们想查找一个城市中有趣的活动。我们可以显示城市中的所有项目,按活动类型排序,或者只选择一个特定的活动(例如,购买、做、吃、喝等)。
public async Task<IEnumerable<TravelItem>> GetTravelItems(string cityName, string action)
{
try
{
if (action != null)
return await _context.TravelItems
.Where(p => p.City == cityName && p.Action == action).ToListAsync();
return await _context.TravelItems.Where(p => p.City == cityName)
.OrderBy(p => p.Action)
.ToListAsync();
}
catch (Exception ex)
{
// log or manage the exception
throw ex;
}
}
此方法将由 GET
函数调用。假设我们想搜索巴黎有趣的活动(https://:61612/api/travelquery/Paris?doAction=do),我们将得到有趣的结果,其中之一是以下内容。
运行更快的查询
提高查询速度的一种方法是应用索引。在 City
和 Action
集合中搜索后,建议为这两个字段添加一个简单索引。
使用 mongo shell 执行 JavaScript 文件,将在 City
上添加索引,然后是 Action
。
db = db.getSiblingDB('TravelDb');
db.WikiVoyage.createIndex( { City: 1, Action: 1 } );
检索速度将从平均 0.150 毫秒提高到约 0.001 毫秒。
分组项目
如果我们只想查看标题怎么办?特定城市有哪些类型的活动,而不深入了解细节?
一个按 City
和 Action
字段分组的示例查询将是。
await _context.TravelItems
.GroupBy(grp => new { grp.City, grp.Action })
.Select(g => new { g.Key.City, g.Key.Action }).ToListAsync();
继续
我将创建此文章的第二部分,添加分页支持以及较新 MongoDB 版本带来的聚合增强功能,同时也会考虑第二个数据集。也许您已经知道了这些,也许您学到了一些新东西。您希望看到更多内容吗?
更新
如果您有兴趣,请也查看我关于 MongoDB 分页的文章(MongoDB 分页 – 如何真正避免性能低下?)。