Mongodb .NET Driver 2.0 升級指南
我們舊系統是基于Mongodb 1.10的驅動,為了一些更方便的功能(為了宇宙的和平),我們決定將驅動升級到2.0。先來看下2.0 驅動有哪些新特性。
1、Async
該版本的驅動為了以比較自然的方式支持異步特性進行了重寫,大部分api 返回的是Task或者Task<T>,于是我們這里可以愉快的使用async 和await 。這個年代,沒有async的API 都不好意思出來打招呼。當然,該版本的驅動還提供了兼容老版本的Sync API 。不過,所有新的應用這里都建議使用新的API。
2、新的 API
2.1 使用了面向接口的風格,更有利于測試
IMongoClient, IMongoDatabase, IMongoCollection
2.2 Find API 支持所有的表達式樹包括project
var names = await db.GetCollection<Person>("people")
.Find(x => x.FirstName == "Jack")
.SortBy(x => x.Age)
.Project(x => x.FirstName + " " + x.LastName)
.ToListAsync();2.3 Aggregation 支持大部分表達式樹
var totalAgeByLastName = await db.GetCollection<Person>("people")
.Aggregate()
.Match(x => x.FirstName == "Jack")
.GroupBy(x => x.LastName, g => new { _id = g.Key, TotalAge = g.Sum(x => x.Age)})
.ToListAsync();2.4 支持dynamic
var person = new ExpandoObject();
person.FirstName = "Jane";
person.Age = 12;
person.PetNames = new List<dynamic> { "Sherlock", "Watson" }
await db.GetCollection<dynamic>("people").InsertOneAsync(person);這里我們系統中使用到了dynamic 的屬性字段,如果用老版本的驅動就得手動實現BsonDocument的序列化和反序列化。新版本內置了ExpandoObjectSerializer 。
public abstract class BaseItem
{
public ObjectId _id { get; set; }
public string AppNo { get; set; }
public string Ip { get; set; }
public DateTime CreateTime { get; set; }
[BsonSerializer(typeof(ExpandoObjectSerializer))]
public ExpandoObject Content { get; set; }
}</pre><br />
如果不加該配置, 序列化的時候可能會拋出 stackoverflow 的Exception。
2.5 一些額外的實驗性功能
2.5.1 全局性的日志監聽功能
var settings = new MongoClientSettings
{
ClusterConfigurator = cb =>
{
var textWriter = TextWriter.Synchronized(new StreamWriter("mylogfile.txt"));
cb.AddListener(new LogListener(textWriter));
}
};
這個是居家旅游必備的好東西。
2.5.2 性能計數器
var settings = new MongoClientSettings
{
ClusterConfigurator = cb =>
{
cb.UsePeformanceCounters("MyApplicationName");
}
};
這個用處不是太大。
3、兼容性
3.1 驅動和服務器的兼容性
3.2 驅動的.NET 語言兼容性
4、舊版本升級指南
2.0版本和1.x 版本還是有不少差別的,雖然在編碼的過程中盡量做到了兼容,但是如果要做驅動版本升級,還是有不少需要注意的點。
4.1 系統需求
不再支持.NET 3.5 和 .NET 4.0。
4.2 BSON
改進了BSON 序列化的基礎,所有實現了自定義序列化器的代碼會受影響。這是因為我們必須要支持dynamic類型的序列化和提升內存管理的性能,所以這里做了點小的修改。
4.3 Driver
MongoConnectionStringBuilder 類被移除。請使用mongodb 連接串或者MongoUrlBuilder代替。
MongoServer 標記為deprecated。所以所有調用了MongoClient.GetServer() 的代碼將會獲得一個warning。
獲得所有Documents
// old API
var list = collection.FindAll().ToList();
// new API
var list = await collection.Find(new BsonDocument()).ToListAsync();</pre>
同理獲得一個Document 也得new 一個BsonDocument
// old API
var document = collection.FindOne();
// new API
var document = await collection.Find(new BsonDocument()).FirstOrDefaultAsync();</pre>
還有Counting
// old API
var count = collection.Count();
// new API
var count = await collection.CountAsync(new BsonDocument());</pre>
4.4 Dao 改造
我們這里有部分老的代碼無法直接改成async 的模式, 因而有了如下一個混合版本的dao。老的代碼少做修改,新的代碼基本走到async 的模式。
4.4.1 老版本的dao
public class Dao<TEntity> where TEntity:class
{
#region Properties
protected MongoCollection<TEntity> Collection { get; private set; }
#endregion
#region Constructors
public Dao()
{
var db = DataBaseManager.Database;
Collection = db.GetCollection<TEntity>(typeof(TEntity).Name.ToLower());
}
#endregion
#region Public Methods
public void Add(TEntity entity)
{
this.Collection.Insert(entity);
}
public void AddBatch(IEnumerable<TEntity> entities)
{
this.Collection.InsertBatch(entities);
}
public void Update(TEntity entity)
{
this.Collection.Save(entity);
}
public void Update(IMongoQuery query, IMongoUpdate update)
{
this.Collection.Update(query, update);
}
public void Delete(string id)
{
ObjectId objId;
if (ObjectId.TryParse(id, out objId))
{
throw new Exception("objectid格式不正確!");
}
Collection.Remove(Query.EQ("_id", objId));
}
public void Delete(IMongoQuery query)
{
Collection.Remove(query);
}
public TEntity FindOne(string id)
{
ObjectId objId;
if (ObjectId.TryParse(id, out objId))
{
throw new Exception("objectid格式不正確!");
}
return Collection.Find(Query.EQ("_id", objId)).FirstOrDefault();
}
public IEnumerable<TEntity> Find(IMongoQuery query)
{
var results = Collection.Find(query);
return results;
}
....................................
}</pre>
4.4.2 新老版本混合使用的中間方案
保留老的Collection 和新的NewCollection。
public class Dao<TEntity> : IDisposable where TEntity : BaseEntity
{
protected IMongoCollection NewCollection { get; private set; }
protected MongoCollection<TEntity> Collection { get; private set; }
......
}</pre>
初始化連接也得有兩份,這里使用pragma warning disable 618禁止warning.
private static IMongoDatabase GetDatabase(string key)
{
var connectString = ConfigurationManager.ConnectionStrings[key].ConnectionString;
var mongoUrl = new MongoUrl(connectString);
var client = new MongoClient(connectString);
return client.GetDatabase(mongoUrl.DatabaseName);
}
internal static MongoDatabase GetLegacyDatabase(string key)
{
var connectString = ConfigurationManager.ConnectionStrings[key].ConnectionString;
var mongoUrl = new MongoUrl(connectString);
var client = new MongoClient(connectString);
pragma warning disable 618
var server = client.GetServer();
pragma warning restore 618
return server.GetDatabase(mongoUrl.DatabaseName);
}</pre><br />
同時暴露異步和同步的api
public async Task InsertOneAsync(TEntity entity)
{
await NewCollection.InsertOneAsync(entity);
}
public async Task InsertManyAsync(IEnumerable<TEntity> entities)
{
await NewCollection.InsertManyAsync(entities);
}
public async Task ReplaceOneAsync(TEntity entity)
{
await NewCollection.ReplaceOneAsync(a => a.Id == entity.Id, entity);
}
............................
public long Add(TEntity entity)
{
return this.Collection.Insert(entity).DocumentsAffected;
}
public IQueryable<TEntity> GetQuery()
{
return this.Collection.AsQueryable();
}
public IEnumerable<WriteConcernResult> AddBatch(IEnumerable<TEntity> entities)
{
return this.Collection.InsertBatch(entities);
}</pre><br />
4.4.3 不需要手動管理數據庫連接對象
IMongoDatabase IMongoClient等都沒有Close方法,而且沒有繼承IDispose接口,這意味著你不需要顯示的關閉數據庫操作的連接。這個和以前ado的連接使用方式不同。可以直接使用單例模式,不必頻繁的去實例化該對象,也不用代碼顯式的去打開和關閉數據庫連接,驅動內部已經幫我們做好了連接池管理。
參考: