我的第一个微服务系列(七):Contact.Api中使用MongoDB
一、MongoDB介绍
MongoDB是一个面向文档的数据库,它使用动态模式将数据存储在类似JSON的文档中。这意味着您可以存储记录而无需担心数据结构,例如存储值的字段数或字段类型。MongoDB文档类似于JSON对象。
如您所知,RDMS以表格格式存储数据,并使用结构化查询语言(SQL)来查询数据库。 RDBMS还具有基于需求的预定义数据库模式和一组规则来定义表中字段之间的关系。但MongoDB尽管存在表格,但仍将数据存储在文档中。您可以通过添加新字段或删除现有字段来更改记录的结构(在MongoDB中称为文档),MongoDB的这种能力可以帮助您轻松地表示层次关系,存储数组和其他更复杂的结构。MongoDB提供高性能,高可用性,易扩展性以及开箱即用的复制和自动分片功能。它与关系型数据库的概念对比如下表:
SQL概念 | MongoDB概念 | 解释说明 |
database | database | 数据库| |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
因为,MongoDB是一个NoSQL数据库,所以我们需要了解我们何时以及为什么需要在现实应用程序中使用这种类型的数据库。在正常情况下,当我们主要关注的是处理大量高性能数据时,MongoDB总是受开发人员或项目经理的青睐。如果我们想在一秒钟内插入数千条记录,那么MongoDB就是最好的选择。此外,在任何RDBMS系统中,水平扩展(添加新列)都不是那么容易的过程。但是对于MongoDB,它非常简单,因为它是一个架构较少的数据库。此外,这种类型的工作可以由应用程序自动直接处理。在MongoDB中执行任何类型的水平扩展都不需要任何类型的管理工作。MongoDB适用于以下类型的情况
- 电子商务类型的基于产品的应用程序
- 博客和内容管理系统
- 实时高速记录,缓存等
- 需要保持位置明智的地理空间数据
- 用于维护与社交和网络类型相关的数据,例如好友间互相关注、粉丝互相关注等
- 如果应用程序是松耦合机制 - 意味着设计可能在任何时间点发生变化。
二、使用MongoDB
Install-Package MongoDB.Driver
添加ContactContext
using Contact.Api.Models; using Microsoft.Extensions.Options; using MongoDB.Driver; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Contact.Api.Data { public class ContactContext { private IMongoDatabase _database; private IMongoCollection<ContactBook> _collection; private IOptions<AppSettings> _appSettings; public ContactContext(IOptions<AppSettings> settings) { _appSettings = settings; var client = new MongoClient(_appSettings.Value.MongoContactConnectionString); if(client != null) { _database = client.GetDatabase(_appSettings.Value.MongoContactDatabase); } } private void CheckAndCreateCollection(string collectionName) { var collectionList = _database.ListCollections().ToList(); var collectionNames = new List<string>(); collectionList.ForEach(b => collectionNames.Add(b["name"].AsString)); if (!collectionNames.Contains(collectionName)) { _database.CreateCollection(collectionName); } } /// <summary> /// 用户通讯录 /// </summary> public IMongoCollection<ContactBook> ContactBooks { get { CheckAndCreateCollection(nameof(ContactBooks)); return _database.GetCollection<ContactBook>(nameof(ContactBooks)); } } /// <summary> /// 好友申请请求记录 /// </summary> public IMongoCollection<ContactApplyRequest> ContactApplyRequests { get { CheckAndCreateCollection(nameof(ContactApplyRequests)); return _database.GetCollection<ContactApplyRequest>(nameof(ContactApplyRequests)); } } } }
"AppSettings": { "MongoContactConnectionString": "mongodb://127.0.0.1:27017", //mongodb://username:password@localhost/dbname "MongoContactDatabase": "Finbook_Contacts" },
services.AddSingleton<ContactContext>(); services.AddOptions(); services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
添加仓储
using Contact.Api.Dtos; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Contact.Api.Data { public interface IContactRepository { /// <summary> /// 更新联系人信息 /// </summary> /// <param name="userInfo"></param> /// <returns></returns> Task<bool> UpdateContactInfoAsync(UserIdentity userInfo, CancellationToken cancellationToken); /// <summary> /// 添加联系人信息 /// </summary> /// <param name="contactId"></param> /// <param name="contact"></param> /// <param name="cancellationToken"></param> /// <returns></returns> Task<bool> AddContactAsync(int contactId,UserIdentity contact,CancellationToken cancellationToken); /// <summary> /// 获取好友列表 /// </summary> /// <param name="userId"></param> /// <returns></returns> Task<List<Models.Contact>> GetContactAsync(int userId,CancellationToken cancellationToken); /// <summary> /// 更新好友标签 /// </summary> /// <param name="contactId"></param> /// <param name="userId"></param> /// <param name="tags"></param> /// <returns></returns> Task<bool> TagContactAsync(int userId,int contactId,List<string> tags,CancellationToken cancellationToken); } }
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Contact.Api.Models; namespace Contact.Api.Data { public interface IContactApplyRequestRepository { /// <summary> /// 添加申请好友的请求 /// </summary> /// <param name="request"></param> /// <returns></returns> Task<bool> AddRequestAsync(ContactApplyRequest request, CancellationToken cancellationToken); /// <summary> /// 通过好友请求 /// </summary> /// <param name="applierId"></param> /// <returns></returns> Task<bool> ApprovalAsync(int userId,int applierId, CancellationToken cancellationToken); /// <summary> /// 获取好友申请列表 /// </summary> /// <param name="userId"></param> /// <returns></returns> Task<List<ContactApplyRequest>> GetRequestListAsync(int userId, CancellationToken cancellationToken); } }
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Contact.Api.Dtos; using Contact.Api.Models; using MongoDB.Driver; namespace Contact.Api.Data { public class MongoContactRepository : IContactRepository { private readonly ContactContext _contactContext; public MongoContactRepository(ContactContext contactContext) { _contactContext = contactContext; } public async Task<bool> AddContactAsync(int userId,UserIdentity contact, CancellationToken cancellationToken) { if(_contactContext.ContactBooks.CountDocuments(c=>c.UserId == userId) == 0) { await _contactContext.ContactBooks.InsertOneAsync(new ContactBook { UserId=userId }); } var filter = Builders<ContactBook>.Filter.Eq(c => c.UserId, userId); var update = Builders<ContactBook>.Update.AddToSet(c => c.Contacts, new Models.Contact { UserId = contact.UserId, Avatar = contact.Avatar, Company = contact.Company, Name = contact.Name, Title = contact.Title, Tags = new List<string> { } }); var result = await _contactContext.ContactBooks.UpdateOneAsync(filter, update, null, cancellationToken); return result.MatchedCount == result.ModifiedCount && result.ModifiedCount == 1; } public async Task<List<Models.Contact>> GetContactAsync(int userId,CancellationToken cancellationToken) { var contactBook = (await _contactContext.ContactBooks.FindAsync(c => c.UserId == userId)).FirstOrDefault(); if(contactBook != null) { return contactBook.Contacts; } return new List<Models.Contact>(); } public async Task<bool> TagContactAsync(int userId,int contactId,List<string> tags,CancellationToken cancellationToken) { var filterDefinition = Builders<ContactBook>.Filter.And( Builders<ContactBook>.Filter.Eq(c => c.UserId, userId), Builders<ContactBook>.Filter.Eq("Contacts.UserId",contactId) ); var update = Builders<ContactBook>.Update .Set("Contacts.$[].Tags", tags); var result = await _contactContext.ContactBooks.UpdateOneAsync(filterDefinition, update, null, cancellationToken); return result.MatchedCount == result.ModifiedCount && result.ModifiedCount == 1; } public async Task<bool> UpdateContactInfoAsync(UserIdentity userInfo,CancellationToken cancellationToken) { var contactBook = (await _contactContext.ContactBooks.FindAsync(c => c.UserId == userInfo.UserId,null,cancellationToken)).FirstOrDefault(); if(contactBook == null) { return true; //return Task.FromResult(true); throw new Exception($"wrong user id for update contact info userId:{userInfo.UserId}"); } var contactIds = contactBook.Contacts.Select(c => c.UserId); var filterDefinition = Builders<ContactBook>.Filter.And( Builders<ContactBook>.Filter.In(c => c.UserId, contactIds), Builders<ContactBook>.Filter.ElemMatch(c => c.Contacts, contact => contact.UserId == userInfo.UserId) ); var update = Builders<ContactBook>.Update .Set("Contacts.$.Name", userInfo.Name) .Set("Contacts.$.Avatar", userInfo.Avatar) .Set("Contacts.$.Company", userInfo.Company) .Set("Contacts.$.Title", userInfo.Title); var updateResult = _contactContext.ContactBooks.UpdateMany(filterDefinition, update); return updateResult.MatchedCount == updateResult.ModifiedCount; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Contact.Api.Models; using MongoDB.Driver; namespace Contact.Api.Data { public class MongoContactApplyRequestRepository : IContactApplyRequestRepository { private readonly ContactContext _contactContext; public MongoContactApplyRequestRepository(ContactContext contactContext) { _contactContext = contactContext; } public async Task<bool> AddRequestAsync(ContactApplyRequest request, CancellationToken cancellationToken) { var filter = Builders<ContactApplyRequest>.Filter.Where(r => r.UserId == request.UserId && r.ApplierId == request.ApplierId); if((await _contactContext.ContactApplyRequests.CountDocumentsAsync(filter)) > 0) { var update = Builders<ContactApplyRequest>.Update.Set(r => r.ApplyTime, DateTime.Now); //var options = new UpdateOptions {s IsUpsert = true }; var result = await _contactContext.ContactApplyRequests.UpdateOneAsync(filter,update, null,cancellationToken); return result.MatchedCount == result.ModifiedCount && result.MatchedCount == 1; } await _contactContext.ContactApplyRequests.InsertOneAsync(request, null, cancellationToken); return true; } public async Task<bool> ApprovalAsync(int userId, int applierId, CancellationToken cancellationToken) { var filter = Builders<ContactApplyRequest>.Filter.Where(r => r.UserId == userId && r.ApplierId == applierId); var update = Builders<ContactApplyRequest>.Update .Set(r => r.Approvaled, 1) .Set(r => r.HandledTime, DateTime.Now); //.Set(r => r.ApplyTime, DateTime.Now); var result = await _contactContext.ContactApplyRequests.UpdateOneAsync(filter, update, null, cancellationToken); return result.MatchedCount == result.ModifiedCount && result.MatchedCount == 1; } public async Task<List<ContactApplyRequest>> GetRequestListAsync(int userId,CancellationToken cancellationToken) { return (await _contactContext.ContactApplyRequests.FindAsync(c => c.UserId == userId)).ToList(cancellationToken); } } }
如果我们调用User.Api的更新用户信息时,Contact.Api中联系人的对应信息也应该发生变化,这时候一般需要使用时间总线的概念,下篇接着讲。