65.9K
CodeProject 正在变化。 阅读更多。
Home

MongoDB 4.0.2 官方 C# 驱动使用入门指南

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (43投票s)

2013年1月9日

CPOL

13分钟阅读

viewsIcon

211595

downloadIcon

3466

重点介绍 MongoDB 开源文档数据库和官方 C# 开源驱动的最新进展。

引言

本文旨在重点介绍 MongoDB 开源文档数据库和官方 C# 开源驱动的最新进展。本文已更新,以反映数据库版本 4.0.2 和 C# 驱动版本 2.7。

文档数据库概述。

文档数据库以一种称为“文档”的连续数据块存储与记录相关的信息。文档的结构通常遵循 JSON 格式,并包含一系列键值对。与关系数据库的模式不同,文档结构不引用空字段。这种灵活的安排可以轻松地添加和删除字段。更重要的是,在尝试组装数据时,无需在各种表中翻找;所有数据都在一个紧凑的块中。所有这些的缺点是文档数据库倾向于占用大量空间。但是,现在硬盘驱动器价格低廉,速度和存储成本之间的权衡已转向速度,这导致文档数据库的使用增加。欧洲核子研究中心的“大型强子对撞机”使用文档数据库,但这并不是它一直出现故障的原因。

MongoDB 的云托管服务器。

MongoDB 提供了免费的云部署 可用。沙盒数据库计划提供 512MB 存储空间,是试用该数据库的便捷方法。它没有时间限制,但有些命令 不受支持。最好先观看介绍性 视频,以便选择合适的选项。

MongoDB 的桌面安装。

可以从 此处 下载 MongoDB 二进制文件。Community Server 是免费的桌面应用程序。使用默认选项安装非常简单,但在默认目录以外的目录中安装则不那么容易。如何在非默认目录中成功更改默认目录已在示例应用程序中详细介绍。您可能需要更改防火墙设置,以允许“mongod”和“MongoDB Database Server”通过。查找任何安装错误的最佳位置是日志文件“C:\Program Files\MongoDB\Server\4.0\log\mongod.log”,弹出错误消息可能具有误导性。

数据库结构

存储数据字段的基本结构是 BsonElement。它是一个简单的 键值 对。键包含字段名,值包含其值。值本身可以是 BsonElement,因此它们可以像俄罗斯套娃一样嵌套。记录存储为文档。DocumentBsonElements 的集合。这是一个示例文档。

{
 _id : 50e5c04c0ea09d153c919473,
  Age : 43,
 Cars : {0:Humber,1: Riley},
 Forename : Rhys,
 Lastname : Richards

}

并非每条记录都需要包含每个字段。唯一必需的字段是 _id,并且以后可以添加字段,而无需更改现有记录。在此示例中,Cars 字段是一个数组。其 Value 字段包含一个嵌套的 Document。嵌套 Document 中的元素是键值对。键是数组索引号,值是汽车的名称。

C# 驱动程序。

C# .Net 驱动程序 以 nuget MongoDB.Driver 的形式提供。该驱动程序用于将您的代码连接到 Mongo 数据库。它可以在不需要特殊属性的情况下将数据类序列化到数据库。唯一需要的是一个唯一的 Id。这通常是 BSON.ObjectId 类型,一个由 MongoDB 自动分配的 12 字节带时间戳的值。您也可以使用 GUID,但需要将其映射到字符串。原因是 GUID 通常存储为二进制,而驱动程序的聚合框架在处理二进制数据时存在问题。黄瓜三明治也给我带来同样的麻烦。该驱动程序现在采用 基于任务的异步模式,以防止对服务器的调用阻塞用户界面线程。这意味着控制台应用程序需要提供用户界面类型的线程,以便异步方法可以返回到调用它们所在的同一个线程。默认行为是使用线程池线程。驱动程序的文档 快速入门 部分和 参考 部分中有大量有关如何使用驱动程序的示例。

连接到数据库。

桌面服务器默认部署的连接字符串是简单的 mongodb://。这种部署仅适用于测试 MongoDB 的功能,因为它不安全。以下是访问名为 test 的数据库的代码。

const string connectionString = "mongodb://";

// Create a MongoClient object by using the connection string
var client = new MongoClient(connectionString);

// Use the client to access the 'test' database
IMongoDatabase database = client.GetDatabase("test");

如果数据库尚不存在,将创建一个新数据库。

访问集合。

结构相似的文档被组织为数据库中的命名数据集合。驱动程序有一个 Collection 对象,它充当数据库集合的代理。以下代码显示了如何访问类型为 ClubMember 的名为“entities”的集合。

//Builds new Collection if 'entities' is not found
IMongoCollection<ClubMember> collection = database.GetCollection<ClubMember>("entities");

然后可以将 IMongoCollection 对象传递给 async 方法,以执行 CRUD 操作。

索引。

MongoDB 索引使用 B 树 数据结构。所有查询只使用一个索引,查询优化器会选择最适合任务的索引。以下代码构建一个索引,以 Lastname 属性为第一排序键,然后是 Forename(按 A-Z 排序),最后是 Age 属性(从最老到最年轻)。

 
//Build an index if it is not already built
 IndexKeysDefinition<ClubMember> keys =
  Builders<ClubMember>.IndexKeys.Ascending("Lastname").Ascending("Forename").Descending("Age");
 //Add an optional name- useful for admin
 var options = new CreateIndexOptions { Name = "MyIndex"};
 var indexModel = new CreateIndexModel<ClubMember>(keys,options);
 await collection.Indexes.CreateOneAsync(indexModel);

此索引对于按 Lastname 或 Lastname、Forename 或 Lastname、Forename、Age 进行搜索非常有用。它对于按 Forename 或 Age 或两者任意组合进行排序没有用处。默认行为是在保存数据时更新索引,这有助于防止并发问题。

使用 Linq 查询数据。

这可以通过在编写 Linq 语句之前引用集合的 AsQueryable 方法来完成。所有常用方法都可用。这里有几个例子。

 public async Task EnumerateClubMembersAsync(IMongoCollection<ClubMember> collection)
    {
       Console.WriteLine("Starting EnumerateClubMembersAsync");
       List<ClubMember> membershipList =
         await collection.AsQueryable()
               .OrderBy(p => p.Lastname)
               .ThenBy(p => p.Forename)
               .ToListAsync();
       Console.WriteLine("Finished EnumerateClubMembersAsync");
       Console.WriteLine("List of ClubMembers in collection ...");
       foreach (ClubMember clubMember in membershipList)
       {
         ConsoleHelper.PrintClubMemberToConsole(clubMember);
       }
    }

 public async Task OrderedFindSelectingAnonymousTypeAsync(IMongoCollection&th;ClubMember> collection)
     {
         Console.WriteLine("Starting  OrderedFindSelectingAnonymousTypeAsync");
         var names =
             await
                 collection.AsQueryable()
                    .Where(p => p.Lastname.StartsWith("R") && p.Forename.EndsWith("an"))
                    .OrderBy(p => p.Lastname)
                    .ThenBy(p => p.Forename)
                    .Select(p => new { p.Forename, p.Lastname })
                    .ToListAsync();
         Console.WriteLine("Finished  OrderedFindSelectingAnonymousTypeAsync");
         Console.WriteLine("Members with Lastname starting with 'R' and Forename ending with 'an'");
         foreach (var name in names)
         {
             Console.WriteLine(name.Lastname + " " + name.Forename);
         }
     }

在这些示例中,将 List<T> 从查询返回,以便连续输出结果,但最好尽可能使用 ForEachAsync(Action<T>) 方法枚举查询结果。这样可以避免将整个查询结果保留在内存中。类似这样。

 public async Task FindUsingForEachAsync(IMongoCollection<ClubMember> collection)
        {
            Console.WriteLine("Starting FindUsingForEachAsync");
            var builder = Builders<ClubMember>.Filter;
            var filter =
                builder.Or(
                    Builders<ClubMember>.Filter.Eq("Lastname", "Rees"),
                    Builders<ClubMember>.Filter.Eq("Lastname", "Jones"));
            await
                collection.Find(filter)
                //the async read of each item is awaited in sequence
                //the 'action' delegate runs on a threadpool thread
                 .ForEachAsync(c =>DoSomeAction(c));
            Console.WriteLine(" Finished FindUsingForEachAsync");
          
        }

        private void DoSomeAction(ClubMember c)
        {
           //It's best only to use thread safe methods here
           //as we are not running on the main thread

        }

使用过滤器查询数据。

驱动程序使用过滤器,或者更确切地说,过滤器定义来过滤数据。通过使用 Builders<T> 辅助类实例化 FilterDefinition 生成器来构造过滤器。然后,通过在将适当的参数传递给生成器之前调用其过滤器构造方法之一来构造所需的 FilterDefinition。这里有一些例子。

 public async Task FindUsingFilterDefinitionBuilder1Async(IMongoCollection<ClubMember> collection)
        {
            Console.WriteLine("Starting FindUsingFilterDefinitionBuilder1Async");
            DateTime cutOffDate = DateTime.Now.AddYears(-5);
            var builder = Builders<ClubMember>.Filter;
            //A greater than filter. Selects where the MembershipDate is greater than the cutOffDate
            var filterDefinition = builder.Gt("MembershipDate", cutOffDate.ToUniversalTime());
            //DateTime is stored in BsonElement as a UTC value so need to convert
            List<ClubMember> membersList =
                await
                    collection.Find(filterDefinition)
                        .SortBy(c => c.Lastname)
                        .ThenBy(c => c.Forename)
                        .ToListAsync();
            Console.WriteLine("Finished FindUsingFilterDefinitionBuilder1Async");
            Console.WriteLine("\r\nMembers who have joined in the last 5 years ...");
            foreach (ClubMember clubMember in membersList)
            {
                ConsoleHelper.PrintClubMemberToConsole(clubMember);
            }
        }

        public async Task FindUsingFilterDefinitionBuilder2Async(IMongoCollection<ClubMember> collection)
        {
            Console.WriteLine("Starting FindUsingFilterDefinitionBuilder2Async");
            var builder = Builders<ClubMember>.Filter;
            //An 'Or' filter. Selects where Lastname ==Rees Or Lastname==Jones
            var filter =
                builder.Or(
                    Builders<ClubMember>.Filter.Eq("Lastname", "Rees"),
                    Builders<ClubMember>.Filter.Eq("Lastname", "Jones"));
            IEnumerable<ClubMember> jonesReesList =
            await
                collection.Find(filter)
                    .SortBy(c => c.Lastname)
                    .ThenBy(c => c.Forename)
                    .ThenByDescending(c => c.Age)
                    .ToListAsync();
            Console.WriteLine("Finished FindUsingFilterDefinitionBuilder2Async");
            Console.WriteLine("Members named Jones or Rees ...");
            foreach (ClubMember clubMember in jonesReesList)
            {
                ConsoleHelper.PrintClubMemberToConsole(clubMember);
            }
            Console.WriteLine("...........");
        }

使用聚合框架查询数据。

聚合框架用于从数据库中的各种文档收集和整理数据。通过将集合沿着管道传递来实现聚合,在管道中连续执行各种管道操作以生成结果。这就像一个即食鸡的生产线——最后的产品量较少,但更适合特定用途。通过将描述各种管道操作的文档数组传递给集合的 Aggregate 方法来执行聚合。

聚合示例。

在此示例中,有一个文档数据库集合,由一辆老爷车俱乐部的成员组成。每个文档是以下 ClubMember 类的序列化版本。

public class ClubMember
{
    #region Public Properties
    public int Age { get; set; }
    public List  <string> Cars { get; set; }
    public string Forename { get; set; }
    public ObjectId Id { get; set; }
    public string Lastname { get; set; }
    public DateTime MembershipDate { get; set; }
    #endregion
}

ClubMember 类有一个名为 Cars 的数组,其中包含会员拥有的老爷车的名称。聚合的目的是为集合中每种类型的汽车生成一个有序的、唯一的车主列表,这些车主是在过去五年内加入的。

步骤 1:匹配操作。

匹配操作仅选择在过去五年内加入的成员。这是代码。

var utcTime5yearsago = DateTime.Now.AddYears(-5).ToUniversalTime();

var matchMembershipDateOperation = new BsonDocument
{
 { "$match", new BsonDocument {  { "MembershipDate", 
     new BsonDocument { { "$gte",utcTime5yearsago  } } } } }
};

正如您所见,代码中的花括号比牙医的托架还多,但至少 IntelliSense 在编写时会提供帮助。$gte 关键字表示大于或等于查询。

步骤 2:解构操作。

解构操作会修改包含指定数组的文档。对于数组中的每个元素,都会创建一个与原始文档相同的文档。然后将数组字段的值更改为等于单个元素的值。因此,具有以下结构的文档

_id:700,Lastname: “Evans”, Cars[“MG”,”Austin”,Humber”]

变成 3 个文档

_id:700,Lastname: “Evans”, Cars:“MG”
_id:700,Lastname: “Evans”, Cars:“Austin”
_id:700,Lastname: “Evans”, Cars:“Humber”

如果存在两个或多个相同元素,例如 Evans 有两辆 MG,那么将产生重复的文档。解构数组使其成员可以被其他聚合操作访问。

var unwindCarsOperation = new BsonDocument { { "$unwind", "$Cars" } };

步骤 3:分组操作。

定义一个操作以按汽车类型对文档进行分组。每个连续的操作都不会作用于原始文档,而是作用于前一个操作生成的文档。可用的字段仅限于前一个管道操作的结果。您不能回去获取原始文档中的字段。美元符号 $ 有两种用法。首先,表示关键字;其次,区分字段名和字段值。例如,Age 是一个字段名,$Age 是 Age 字段的值。

var groupByCarTypeOperation = new BsonDocument
{
    {
        //Sort the documents into groups
        "$group",
        new BsonDocument
        {
            //Make the unique identifier for the group a BSON element consisting
            // of a field named Car.
            // Set its value to that of the Cars field
            // The Cars field is nolonger an array because it has now been unwound
            { "_id", new BsonDocument { { "Car", "$Cars" } } },
            {
                //Add a field named Owners
                "Owners",
                new BsonDocument
                {
                    {
                        //add a value to the Owners field if it does not
                        //already contain an  identical value.
                        //This makes the field Value an array
                        "$addToSet",
                        //The value to add is a BsonDocument with an identical structure to
                        // a serialized ClubMember class.
                        new BsonDocument
                        {
                            { "_id", "$_id" },
                            { "Lastname", "$Lastname" },
                            { "Forename", "$Forename" },
                            { "Age", "$Age" },
                            {"MembershipDate","$MembershipDate"}
                        }
                    }
               }
           }
        }
    }
};

步骤 4:投影操作。

前一个操作生成的 _id 字段是一个 BsonElement,包含字段名及其值。最好删除字段名,只使用值。以下投影操作执行此操作。

var projectMakeOfCarOperation = new BsonDocument
{
    {
        "$project", new BsonDocument
        {
            // drop the _id field. A 0 as used here means drop
            { "_id", 0 },
            //Add a new field. Make its Value equal to the value of the _id's Car field
            { "MakeOfCar", "$_id.Car" },
            //Keep the Owners field. A 1 as used here means keep
            { "Owners", 1 }
        }
     }
};

步骤 5:排序操作。

定义一个操作以按汽车类型对文档进行排序。

  var sortCarsOperation = new BsonDocument { { "$sort", new BsonDocument { { "MakeOfCar", 1 } } } };

数字 1 表示执行升序排序。0 用于表示降序排序。

步骤 6:运行聚合并输出结果。

 var pipeline = new[]
     {
      matchMembershipDateOperation, 
      unwindCarsOperation, 
      groupByCarTypeOperation, 
      projectMakeOfCarOperation,
      sortCarsOperation
      };
  var carStatsList = await collection.Aggregate<CarStat>(pipeline).ToListAsync();
  Console.WriteLine("Finished AggregateOwnersByCarManufacturerAsync");

结果作为 List<CarStat> 返回。

public class CarStat
{
#region Public Properties

public string MakeOfCar { get; set; }

public BsonDocument[] Owners { get; set; }

#endregion
}

结果的枚举方式如下

  
Console.WriteLine("\r\nMembers grouped by Car Marque");
            foreach (CarStat stat in carStatsList)
            {
                Console.WriteLine("\n\rCar Marque : {0}\n\r", stat.MakeOfCar);
                IEnumerable<ClubMember> clubMembers =
                    stat.Owners.ToArray()
                     //deserialize the BsonDocument[] to an IEnumerable<ClubMember>
                     .Select(d => BsonSerializer.Deserialize<ClubMember>(d))
                     .OrderBy(c => c.Lastname)
                     .ThenBy(c => c.Forename)
                     .ThenBy(c => c.Age)
                     .Select(c => c);
                foreach (ClubMember clubMember in clubMembers)
                {

                    ConsoleHelper.PrintClubMemberToConsole(clubMember);
                }
              
            }

示例应用程序有一个聚合示例,该示例对数据集执行各种计算,例如 Count、Min、Max 和 Total。但这种类型的聚合,在投影不多的情况下,最好使用 Linq 来完成。

 List<FamilyStat> familyStats =
                await
                    collection.Aggregate()
                        .Group(
                            x => x.Lastname,
                            g =>
                                new FamilyStat
                                {
                                    FamilyName = g.Key,
                                    TotalAge = g.Sum(x => x.Age),
                                    MinAge = (g.Min(x => x.Age)),
                                    MaxAge = (g.Max(x => x.Age)),
                                    Count = g.Count()
                                })
                        .SortBy(x => x.FamilyName)
                        .ToListAsync();

使用 Map Reduce 查询数据。

MapReduce 是一种重型方法,用于批量处理大量数据。在此示例中,将对具有相同 Lastname 的每个人的年龄进行总计。需要定义两个函数。map 方法为每个文档输出一个键/值对。reduce 方法按键对数据进行分组,并对值执行某种数学函数。

  //the map method outputs a key/value pair for each document
  //in this case, the key is the Lastname property and the value is the Age property value
  var map = new BsonJavaScript(@"function() 
                 {
                //Associate each LastName property with the Age value
                emit(this.Lastname,this.Age);
                 }");

Reduce 方法为每个 Lastname 返回 Lastname 作为键,以及具有相同 Lastname 的每个人的年龄总和作为值。

 var reduce = new BsonJavaScript(@"function(lastName,ages) 
                            {
                             return Array.sum(ages);
                            }");

一个批次的输出可以与另一个批次的输出合并,并通过 Reducer 重新馈送。在此示例中,一个批次文档的结果输出到服务器上的一个名为 ResultsCollection 的集合中。如果集合已包含数据,则会对新批次数据和集合中的数据进行规约,每个 Lastname 的总年龄将更新并保存在集合中。

 var options = new MapReduceOptions<ClubMember, BsonDocument>
          {
              OutputOptions = MapReduceOutputOptions.Reduce("ResultsCollection")
          };

  var resultAsBsonDocumentList = await collection.MapReduce(map, reduce, options).ToListAsync();
           Console.WriteLine("The total age for every member of each family  is ....");
          var reduction =
              resultAsBsonDocumentList.Select(
                  doc => new { family = doc["_id"].AsString, age = (int)doc["value"].AsDouble });
          foreach (var anon in reduction)
          {
              Console.WriteLine("{0} Family Total Age {1}", anon.family, anon.age);
          }

GridFS。

GridFS 是一种存储和检索大于 BsonDocument 大小限制(16MB)的文件的手段。GridFS 不将文件存储在单个文档中,而是将文件分成块,并将每个块存储为单独的文档。GridFS 使用两个集合来存储文件。一个集合存储文件块,另一个是 GridFSFileInfo 类型的集合,该集合保存有关文件存储方式的信息,并且可以包含其他元数据。块大小约为 255k。这里的想法是,较小的数据块可以更有效地存储,并在处理时比大文件消耗更少的内存。通常不建议将二进制数据存储在主文档中,因为它占用了更有意义的数据最适合的空间。由于引入了异步流,将数据上传到 GridFS 现在比以前更复杂。

   public async Task UploadDemoAsync(IMongoCollection<ClubMember> collection)
        {
            Console.WriteLine("Starting GridFSDemo");
            IMongoDatabase database = collection.Database;
            const string filePath = @"C:\temp\mars996.png";
            //the name of the uploaded GridFS file
            const string fileName = @"mars996";
            //add some metadata to the GridFSFileInfo object
            // to facilitate searching
            var photoMetadata = new BsonDocument
            {
                { "Category", "Astronomy" },
                { "SubGroup", "Planet" },
                { "ImageWidth", 640 },
                { "ImageHeight", 480 }
            };
            var uploadOptions = new GridFSUploadOptions { Metadata = photoMetadata };
            try
            {
                await UploadFileAsync(database, filePath, fileName,uploadOptions);
            }
            catch (Exception e)
            {
                Console.WriteLine("***GridFS Error " + e.Message);
            }
        }
   public async Task UploadFileAsync(
            IMongoDatabase database,
            string filePath,
            string fileName,
            GridFSUploadOptions uploadOptions=null)
        {
            var gridFsBucket = new GridFSBucket(database);
            using (FileStream sourceStream = File.Open(filePath, FileMode.Open))
            {
                using (
                    GridFSUploadStream destinationStream =
                        await gridFsBucket.OpenUploadStreamAsync(fileName, uploadOptions))
                {
                    await sourceStream.CopyToAsync(destinationStream);
                    await destinationStream.CloseAsync();
                }
            }
        }

可以基于 GridFSFileInfo 元数据创建索引。

 public async Task DemoCreateIndexAsync(IMongoDatabase database, string indexName)
        {
            IMongoCollection<GridFSFileInfo> filesCollection = database.GetCollection<GridFSFileInfo>("fs.files");
            IndexKeysDefinition<GridFSFileInfo> keys =
                Builders<GridFSFileInfo>.IndexKeys.Ascending("Metadata.Category").Ascending("Metadata.SubGroup");
            //Add an optional name- useful for admin
            var options = new CreateIndexOptions { Name = indexName };
            var indexModel = new CreateIndexModel<GridFSFileInfo>(keys, options);
            await filesCollection.Indexes.CreateOneAsync(indexModel);
        }

该索引可用于查找存储文件的子集。然后可以从数据库下载文件。

 public async Task DemoDownloadFilesAsync()
{
 IMongoCollection<GridFSFileInfo> filesCollection = database.GetCollection<GridFSFileInfo>("fs.files");
            List<GridFSFileInfo> fileInfos = await DemoFindFilesAsync(filesCollection);
            foreach (GridFSFileInfo gridFsFileInfo in fileInfos)
            {
                Console.WriteLine("Found file {0} Length {1}", gridFsFileInfo.Filename, gridFsFileInfo.Length);
              try
              {
                await DemoDownloadFileAsync(database, filePath, fileName);
              }
              catch (Exception e)
              {
                Console.WriteLine("***GridFS Error " + e.Message);
              }
           }
          
}

 public async Task<List<GridFSFileInfo>> DemoFindFilesAsync(IMongoCollection<GridFSFileInfo> filesCollection)
        {
            //Search using the PhotoIndex
            FilterDefinitionBuilder<GridFSFileInfo> builder = Builders<GridFSFileInfo>.Filter;
            FilterDefinition<GridFSFileInfo> filter = builder.Eq("metadata.Category", "Astronomy")
                                                      & builder.Eq("metadata.SubGroup", "Planet");
            return await filesCollection.Find(filter).ToListAsync();
        }
 public async Task DemoDownloadFileAsync(IMongoDatabase database, string filePath, string fileName)
        {
            var gridFsBucket = new GridFSBucket(database);
            using (
                GridFSDownloadStream<ObjectId> sourceStream = await gridFsBucket.OpenDownloadStreamByNameAsync(fileName)
                )
            {
                using (FileStream destinationStream = File.Open(filePath, FileMode.Create))
                {
                    await sourceStream.CopyToAsync(destinationStream);
                }
            }
        }

MongoDB 副本集。

副本集是 MongoDB 实例的集群,这些实例相互复制,因此它们存储相同的数据。一个服务器是主服务器,接收客户端的所有写入。其他是次要成员,并异步从主服务器复制。巧妙之处在于,当主服务器出现故障时,一个次要成员会接管并成为新的主服务器。这完全对用户透明地发生,并确保服务连续性。副本集还有其他优势,例如易于备份数据,以及具有大量读取请求的数据库可以通过从次要服务器读取来减少主服务器的负载。您不能依赖任何一个实例为主服务器,因为主服务器由副本集的成员在运行时确定。

将副本集安装为 Windows 服务。

此示例安装一个副本集,该副本集由一个主实例和两个次要实例组成。实例将命名为 MongDB0、MongoDB1、MongoDB2。它们将使用 IP 地址 localhost,并分别监听端口 27017、27018 和 27019。副本集名称为 myReplSet。应允许端口通过 Windows 防火墙进行输入和输出,因此您可能需要添加新的入站和出站规则。要允许访问端口,请在命令窗口中输入 wf.msc。在弹出窗口中选择“入站规则”。在“操作”列中选择“新建规则”,然后选择“端口”并输入详细信息。为出站规则重复此过程。在实际部署中,这三个服务器将在网络内的不同计算机上,但在本例中,它们都在同一台计算机上。

步骤 1:内务处理任务。

需要添加三个名为 MongoDB0、MongoDB1、MongoDB2 的新数据文件夹,并且必须删除可能已在运行的任何 MongoDB 实例。在本例中,要删除的服务名为 MongoDB。以管理员模式打开命令提示符并复制粘贴以下内容:

cd "C:\Program Files\MongoDB\Server\4.0\bin"
mongod.exe --serviceName MongoDB  --remove
md "C:\Program Files\MongoDB\Server\4.0\data\MongoDB0"
md "C:\Program Files\MongoDB\Server\4.0\data\MongoDB1"
md "C:\Program Files\MongoDB\Server\4.0\data\MongoDB2"

步骤 2:安装三个新的服务实例。

最好的方法是为每个实例拥有三个配置文件。这些文件的格式非常相似。这是 MongoDB0 的配置文件 repSetDb0.cfg。

# repSetDb0.cfg
storage:  
    dbPath: "C:/Program Files/MongoDB/Server/4.0/data/MongoDB0"
systemLog:  
    destination: file
    path: "C:/Program Files/MongoDB/Server/4.0/log/MongoDB0.log"
    logAppend: true
    timeStampFormat: iso8601-utc
replication:  
    replSetName: "myReplSet"
net:  
    port: 27017

配置文件包含在示例代码包中,但基本上,您需要为每个实例更改端口、dbpath 和 logpath。将配置文件存储在 mongoDB\Server\4.0\bin 目录中,并从 mongoDB\Server\4.0\bin 目录的命令提示符输入以下命令。

mongod.exe --config "C:\Program Files\MongoDB\Server\4.0\bin\repSetDb0.cfg" --serviceName MongoDB0 --serviceDisplayName MongoDB0 --install
mongod.exe --config "C:\Program Files\MongoDB\Server\4.0\bin\repSetDb1.cfg" --serviceName MongoDB1 --serviceDisplayName MongoDB1 --install
mongod.exe --config "C:\Program Files\MongoDB\Server\4.0\bin\repSetDb2.cfg" --serviceName MongoDB2 --serviceDisplayName MongoDB2 --install

检查日志文件以确认一切正常,然后输入以下命令来启动服务。

net start MongoDB0 
net start MongoDB1
net start MongoDB2
services.msc

 

在服务管理窗口中,右键单击这三个实例中的每个实例,然后将“启动类型”更改为“自动(延迟启动)”。

步骤 3:配置副本集。

要配置副本集,您需要使用 Mongo shell。确保您位于 MongoDB\Server\4.0\bin 目录中,然后复制粘贴以下内容:

mongo MongoDB0
use admin
config = { _id : "myReplSet",members : [ {_id : 0, host :"localhost:27017"}, 
  {_id : 1, host : "localhost:27018"}, {_id : 2, host :"localhost:27019"}]}
rs.initiate(config)
exit


安装完成后,您可以通过在 mongo shell 中输入 rs.status() 来查看副本集的状态。要使用 C# 驱动程序连接到副本集,请使用此连接字符串。

const string connectionString = 
  "mongodb:///?replicaSet=myReplSet&readPreference=primary";

结论

MongoDB 的内容远不止本文所介绍的,但希望这里的信息足以让您开始探索这个开源软件的功能。此处给出的数据库配置不安全,仅适用于测试目的。MongoDB 有许多安全功能,如果您有兴趣实施它们,请参阅本文的续篇,使用加密和身份验证保护 MongoDB

 

© . All rights reserved.