郎榕 發表於 2023-10-7 08:00:00

ASP.NET 6启动时自动创建MongoDB索引

<p>大家好,我是Edison。</p>
<p>最近,在使用MongoDB时,碰到这样的一个需求:针对某个Collection手动在开发环境创建了索引,但在测试环境和生产环境不想再手动操作了,于是就想着通过代码的方式在ASP.NET 6应用启动时自动创建。</p>
<h1>背景知识</h1>
<p>索引本质上是树,最小的值在最左边的叶子上,最大的值在最右边的叶子上,使用索引可以提高查询速度(而不用全表扫描),也可以预防脏数据的插入(如唯一索引)。索引既支持普通字段,也支持内嵌文档中某个键和数组元素进行索引。</p>
<p><img src="https://img2023.cnblogs.com/blog/381412/202309/381412-20230927180329467-1750741942.png" alt="" loading="lazy" style="width: 75%; border: 1px solid rgba(221, 221, 221, 1); border-radius: 3px; box-shadow: 0 4px 8px rgba(3, 27, 78, 0.12); display: block; margin-left: auto; margin-right: auto"></p>
<p>在MongoDB中可以创建的索引类型:</p>
<ul class=" list-paddingleft-1">
<li>
<p>唯一索引 unique:保证数据的唯一不重复</p>
</li>
<li>
<p>稀疏索引 sparse</p>
</li>
<li>
<p>复合索引:用于提高查询速度</p>
</li>
<li>
<p>TTL 索引 : 设置文档的缓存时间,时间到了会自动删除掉</p>
</li>
<li>
<p>全文索引:便于大文本查询(如概要、文章等长文本)</p>
</li>
<li>
<p>二维平面索引:便于2d平面查询</p>
</li>
<li>
<p>地理空间索引:便于地理查询</p>
</li>
</ul>
<p>通过Mongo Shell管理索引:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建索引</span>
<span style="color: rgba(0, 0, 0, 1)">db.collection.createIndex(keys, options);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 查询索引</span>
<span style="color: rgba(0, 0, 0, 1)">db.collection.getIndexes(filter);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除索引</span>
db.collection.dropIndex(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">IndexName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 删除所有索引</span>
<span style="color: rgba(0, 0, 0, 1)">db.collection.dropIndexes()

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> explain 查看查询是否走索引
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "stage" : "COLLSCAN", 表示全集合扫描
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "stage" : "IXSCAN" ,基于索引的扫描</span>
db.collection.<span style="color: rgba(0, 0, 255, 1)">find</span>(query,options).explain(options)</pre>
</div>
<h1><strong>准备工作</strong></h1>
<p>假设我们有一个Entity定义如下:</p>
<div class="cnblogs_code">
<pre>
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyTaskEntity : IEntity
{
          
   
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> ObjectId Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> OrderNumber { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> List&lt;TransmissionEntity&gt; Transmissions { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MyTaskEntity()
    {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.Transmissions = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;TransmissionEntity&gt;<span style="color: rgba(0, 0, 0, 1)">();
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> MyTaskEntity(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> orderNumber)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.OrderNumber =<span style="color: rgba(0, 0, 0, 1)"> orderNumber;
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.Transmissions = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;TransmissionEntity&gt;<span style="color: rgba(0, 0, 0, 1)">();
    }
   
    ......
}</span></pre>
</div>
<p>这里,我们以之前分享的一篇文章《在ASP.NET 6中使用工作单元操作MongoDB》为基础,不熟悉的朋友可以先看看这篇文章。</p>
<p>下面,我们将使用基于上面提到的那篇文章中的 EDT.MongoProxy组件中 的内容 MongoDbConection,这是一个包裹MongoClient的单例对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MongoDbConnection : IMongoDbConnection
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> IMongoClient DatabaseClient { <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">; }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> DatabaseName { <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MongoDbConnection(MongoDatabaseConfigs configs, IConfiguration configuration)
    {
      DatabaseClient </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MongoClient(configs.GetMongoClientSettings(configuration));
      DatabaseName </span>=<span style="color: rgba(0, 0, 0, 1)"> configs.DatabaseName;
    }
}</span></pre>
</div>
<h1><strong>方式一:使用Builders.IndexKeys</strong></h1>
<p>这里创建一个静态类AppDbContext用于进行MongoDB索引创建,假设我们需要创建一个针对OrderNumber字段升序排列的唯一索引,那么创建的代码如下所示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> AppDbContext
{
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> Create indexes in MongoDB when startup
    </span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> NOTE: It'll skip creation when the indexes already exists.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Initialize(IApplicationBuilder app)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> dbInstance = app.ApplicationServices.GetService&lt;IMongoDbConnection&gt;<span style="color: rgba(0, 0, 0, 1)">();
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> logger = app.ApplicationServices.GetService&lt;ILogger&lt;MongoRepository&gt;&gt;<span style="color: rgba(0, 0, 0, 1)">();
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> db =<span style="color: rgba(0, 0, 0, 1)"> dbInstance.DatabaseClient.GetDatabase(dbInstance.DatabaseName);

      </span><span style="color: rgba(0, 0, 255, 1)">var</span> collection = db.GetCollection(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">MyTasks</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Index definitions</span>
      <span style="color: rgba(0, 0, 255, 1)">var</span> indexKeysDefine = Builders&lt;MyTaskEntity&gt;.IndexKeys.Ascending(indexKey =&gt;<span style="color: rgba(0, 0, 0, 1)"> indexKey.OrderNumber);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Create indexes by RunCommand</span>
      <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
      {
            </span><span style="color: rgba(0, 0, 255, 1)">await</span> collection.Indexes.CreateOneAsync(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CreateIndexModel(indexKeysDefine));
      }
      </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
      {
            logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{service}.{method} - throws an exception when creating indexes in database</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
               nameof(AppDbContext), nameof(Initialize));
      }
    }
}</span></pre>
</div>
<p>使用Builders.IndexKeys可以方便快速的声明索引,并且<strong>它只会在对应索引不存在的时候创建,已存在时则会跳过</strong>。</p>
<p>但是如果你想要给集合字段的某个字段声明索引,则不太容易实现。这个时候,你可以考虑方式二。</p>
<h1><strong>方式二:使用RunCommand</strong></h1>
<p>这里我们修改一下上面AppDbContext中Initialize方法,通过构造两个Mongo Shell命令的方式来创建索引。</p>
<p>与上面不同的是,这里我们还针对集合类型的几个常用查询字段创建了一个组合索引,代码如下所示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> AppDbContext
{
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
    <span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> Create indexes in MongoDB when startup
    </span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> NOTE: It'll skip creation when the indexes already exists.
    </span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Initialize(IApplicationBuilder app)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> dbInstance = app.ApplicationServices.GetService&lt;IMongoDbConnection&gt;<span style="color: rgba(0, 0, 0, 1)">();
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> logger = app.ApplicationServices.GetService&lt;ILogger&lt;MongoRepository&gt;&gt;<span style="color: rgba(0, 0, 0, 1)">();
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> db =<span style="color: rgba(0, 0, 0, 1)"> dbInstance.DatabaseClient.GetDatabase(dbInstance.DatabaseName);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Index definitions</span>
      <span style="color: rgba(0, 0, 255, 1)">var</span> indexCommand1 = <span style="color: rgba(128, 0, 0, 1)">@"</span><span style="color: rgba(128, 0, 0, 1)">{ createIndexes: 'MyTasks', indexes: [ { key: { 'OrderNumber': 1 }, name:'Idx_OrderNumber', unique: true } ] }</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> indexCommand2 = <span style="color: rgba(128, 0, 0, 1)">@"</span><span style="color: rgba(128, 0, 0, 1)">{ createIndexes: 'MyTasks', indexes: [ { key: { 'Transmissions.Type': 1, 'Transmissions.Status':1, 'Transmissions.Retries':1 }, name:'Idx_Transmission_TypeStatusRetries', unique: false } ] }</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Create indexes by RunCommand</span>
      <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
      {
            db.RunCommand</span>&lt;BsonDocument&gt;<span style="color: rgba(0, 0, 0, 1)">(BsonDocument.Parse(indexCommand1));
            db.RunCommand</span>&lt;BsonDocument&gt;<span style="color: rgba(0, 0, 0, 1)">(BsonDocument.Parse(indexCommand2));
      }
      </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
      {
            logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{service}.{method} - throws an exception when creating indexes in database</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
               nameof(AppDbContext), nameof(Initialize));
      }
    }
}</span></pre>
</div>
<h1><strong>在Program.cs中使用</strong></h1>
<p>这里我们仅仅需要在Program.cs中添加以下语句即可实现在ASP.NET 6应用启动时创建MongoDB索引啦:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">......
AppDbContext.Initialize(app);
......</span></pre>
</div>
<h1><strong>小结</strong></h1>
<p>本文我们了解了如何在ASP.NET 6应用启动时实现自动创建MongoDB的索引,相信会对你在ASP.NET 6中使用MongoDB有一定帮助!</p>
<h1><strong>参考资料</strong></h1>
<p>Kevin Smith,《Creating MongoDB indexes in ASP.NET Core 3.1》</p>
<p>TheCodeBuzz,《Create MongoDB indexes in C#.NET Part&nbsp;1》</p>
<p>TheCodeBuzz,《Create MongoDB indexes in C#.NET Part 2》</p>
<p>&nbsp;</p>
<p style="text-align: center"><img src="https://images.cnblogs.com/cnblogs_com/edisonchou/1647700/o_200902144330EdisonTalk-Footer.jpg" alt="" style="width: 65%; border: 1px solid rgba(221, 221, 221, 1); border-radius: 3px; box-shadow: 0 4px 8px rgba(3, 27, 78, 0.12)"></p>
<div id="Copyright">
<p>作者:周旭龙</p>
<p>出处:https://edisonchou.cnblogs.com</p>
<p>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。</p>
</div>

</div>
<div id="MySignature" role="contentinfo">
    <div align="center"><img border="0" src="http://service.t.sina.com.cn/widget/qmd/2068032061/d643d182/10.png"></div><br><br>
来源:https://www.cnblogs.com/edisontalk/p/auto-create-mongoindex-in-aspnet6.html
頁: [1]
查看完整版本: ASP.NET 6启动时自动创建MongoDB索引