為什么要在.net 項目中使用NoSQL?其實這是個歷史遺留問題. 最近一年用express+mongodb架構做了不少工程, 不亦樂乎, 但當項目上線時, 悲哀的發(fā)現(xiàn)這些腳本語言的一大特點就是難以部署. 這個話題可以展開講: 腳本/弱類型語言不適合做大工程.
所以對其中的一個工程, 就有了重寫的念頭. 但是數(shù)據(jù)已經(jīng)在mongo中, 遷移到關系型數(shù)據(jù)庫想想就頭疼. 最終決定還是mongodb吧.
看了很多各種教程, 都是helloworld, 大型項目不可能不分層, 吐槽一下MongoDB的官網(wǎng)文檔真是很屎. 沒有多少例子.今天下午, 琢磨了一下.net core和mongodb的操作實踐, 算是有點心得,記下來吧.
0. 環(huán)境
- .net core v2.2
- mongodb v4.0
- visual studio community 2017
- mongodb.driver v2.7.2
1. 起項目
- 新建.net core api 工程 , nuget 搜索
Mongodb
, 下載安裝MongoDB.Driver
, 注意其中已經(jīng)包含了MongoDB.Core, 不需要重復下載:
2. 規(guī)劃
- POCO模型類:
Domain
文件夾, 所有模型類都繼承Entity
接口 - 領域類:
Repository
文件夾, 包含數(shù)據(jù)上下文定義接口IContext
和抽象操作接口IRepository
不愿意弄太多文件夾, 這兩個就夠了
3. 開碼
- 寫一個空的Entity接口,方便模型類繼承:
public interface Entity
{
}
- 寫一個User POCO類測試, 繼承Entity:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
using System.Collections.Generic;
[BsonIgnoreExtraElements]
/// <summary>
/// User POCO
/// </summary>
public class User:Entity
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("username")]
public string Username { get; set; }
[BsonElement("password")]
public string Password { get; set; }
[BsonElement("realName")]
public string RealName { get; set; }
}
上面的一些標簽有必要說一下:BsonIgnoreExtraElements
是忽略mongodb內(nèi)部自動產(chǎn)生的一些字段, 打開mongodb就知道:比如:
類似這樣的, 你的POCO類中沒有這個字段就會報錯.
BsonId
告訴說這個字段是主鍵, 默認是ObjectId
類型.BsonRepresentation(BsonType.ObjectId)
這個標簽很有用, 在后面Update, findbyid的時候, 由于前臺傳過來的是字符串, 沒法和數(shù)據(jù)庫中ObjectId
類型的主鍵進行比較. 加上這個就可以解決這個問題.BsonElement("username")
是在數(shù)據(jù)庫中的字段名.
- Repository文件夾下建立
IContext
接口:
using BioVR.Backend.Domain;
using MongoDB.Driver;
public interface IContext<T> where T:Entity
{
IMongoCollection<T> Entities { get;}
}
這個接口主要是規(guī)定所有的Contex實現(xiàn)類中都要有IMongoCollection
類型字段, 俗話說得好,接口即規(guī)范.
- UserContext實現(xiàn)類
public class UserContext : IContext<User>
{
// IOC
private readonly IMongoDatabase _db;
public UserContext(IOptions<Settings> options)
{
var client = new MongoClient(options.Value.ConnectionString);
_db = client.GetDatabase(options.Value.Database);
}
public IMongoCollection<User> Entities => _db.GetCollection<User>("users");
}
這里, 我們使用.net 的依賴注入(構造函數(shù)方式)從Settings里拿到了數(shù)據(jù)庫連接字符串, 并連接到了數(shù)據(jù)庫, 如是, 我們必須注冊settings:
打開項目目錄下的appsettings.json
, 添加數(shù)據(jù)庫連接地址
{
"MongoDB": {
"ConnectionString": "mongodb://localhost:27017",
"Database": "yourdbname"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
然后在Startup.cs中加入Configuration
:
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.Configure<Settings>(
options =>
{
options.ConnectionString = Configuration.GetSection("MongoDb:ConnectionString").Value;
options.Database = Configuration.GetSection("MongoDb:Database").Value;
});
}
這樣上面的UserContext
就能拿到數(shù)據(jù)了
- UserRepository類:
public class UserRepository : IRepository<User>
{
private readonly IContext<User> _context;
/// <summary>
/// IOC
/// </summary>
/// <param name="context"></param>
public UserRepository(IContext<User> context)
{
_context = context;
}
public async Task<string> Create(User t)
{
await _context.Entities.InsertOneAsync(t);
return t.Id;
}
public async Task<bool> Delete(string id)
{
var deleteResult = await _context.Entities.DeleteOneAsync(x => x.Id == id);
return deleteResult.DeletedCount != 0;
}
public async Task<List<User>> GetList(int skip = 0, int count = 0)
{
try
{
var result = await _context.Entities.Find(x => true)
.Skip(skip)
.Limit(count)
.ToListAsync();
return result;
}
catch (Exception ex)
{
throw;
}
}
public async Task<List<User>> GetListByField(string fieldName, string fieldValue)
{
var filter = Builders<User>.Filter.Eq(fieldName, fieldValue);
var result = await _context.Entities.Find(filter).ToListAsync();
return result;
}
public async Task<User> GetOneById(string id)
{
return await _context.Entities.Find(x => x.Id.ToString() == id).FirstOrDefaultAsync();
}
public async Task<bool> Update(User t)
{
var filter = Builders<User>.Filter.Eq(x=>x.Id,t.Id);
var replaceOneResult = await _context.Entities.ReplaceOneAsync(doc => doc.Id == t.Id,t);
return replaceOneResult.ModifiedCount != 0;
}
}
這里寫CURD的各個方法, 注意,除了lambda表達式,我們還可以使用Linq語法做查詢.
- UserController:
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly IRepository<User> _userRepository;
public UsersController(IRepository<User> userRepository)
{
_userRepository = userRepository;
}
[HttpGet]
public async Task<List<User>> List()
{
return await _userRepository.GetList();
}
[HttpPost]
public async Task<string> Add([FromBody] User user)
{
var id = await _userRepository.Create(user);
return id;
}
[HttpDelete("{id}")]
public async Task<bool> Delete(string id)
{
return await _userRepository.Delete(id);
}
}
注意UserController和UserRepository類中都有依賴注入,我們必須注冊這些服務, 在Startup文件中加入:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.Configure<Settings>(
options =>
{
options.ConnectionString = Configuration.GetSection("MongoDb:ConnectionString").Value;
options.Database = Configuration.GetSection("MongoDb:Database").Value;
});
InjectRepositories(services);
}
private void InjectRepositories(IServiceCollection services)
{
services.AddTransient<IContext<User>, UserContext>();
services.AddTransient<IRepository<User>, UserRepository>();
}
單獨寫一個InjectRepositories
方法, 因為可能將來需要注冊的服務會很多.
這樣就可以了.
本文沒什么特別之處, 就是覺得這樣的架構比較適合項目開發(fā), 自己記錄一下, 也供大家參考,拋磚引玉.