using DataSync.Model;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using Pipelines.Sockets.Unofficial.Arenas;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
namespace DataSync
{
/// <summary>
/// MongoDB操作上下文类
/// </summary>
/// <typeparam name="TDatabase">数据库中集合操作类</typeparam>
public abstract partial class MongoDBContext<TDatabase> where TDatabase : MongoDBContext<TDatabase>, new()
{
public partial class Collection<TDocument> where TDocument : new()
{
private readonly MongoDBContext<TDatabase> _dbContext;
private readonly string _likelyCollectionName;
private IMongoCollection<TDocument> mongoCollection;
private List<SortDefinition<TDocument>> sortDefinitions;
private List<UpdateDefinition<TDocument>> updateDefinitions;
private string[] keywords;
public Collection(MongoDBContext<TDatabase> dbContext, string likelyCollectionName)
{
this._dbContext = dbContext;
this._likelyCollectionName = likelyCollectionName;
this.Builder();
}
public IMongoCollection<TDocument> MongoCollection
{
get
{
mongoCollection = this._dbContext.MongoDatabase.GetCollection<TDocument>(CollectionName);
return mongoCollection;
}
}
public string CollectionName
{
get
{
return _dbContext.DetermineCollectionName<TDocument>(_likelyCollectionName);
}
}
public Collection<TDocument> Builder()
{
sortDefinitions = new List<SortDefinition<TDocument>>();
updateDefinitions = new List<UpdateDefinition<TDocument>>();
return this;
}
public Collection<TDocument> BuilderUpdate()
{
updateDefinitions = new List<UpdateDefinition<TDocument>>();
return this;
}
public Collection<TDocument> BuilderSort()
{
sortDefinitions = new List<SortDefinition<TDocument>>();
return this;
}
public Collection<TDocument> Set<TField>(Expression<Func<TDocument, TField>> field, TField value)
{
updateDefinitions.Add(Builders<TDocument>.Update.Set(field, value));
return this;
}
#region 排序
public Collection<TDocument> SortBy(Expression<Func<TDocument, dynamic>> field, bool ascending = true)
{
if (ascending)
sortDefinitions.Add(Builders<TDocument>.Sort.Ascending(field));
else
sortDefinitions.Add(Builders<TDocument>.Sort.Descending(field));
return this;
}
public Collection<TDocument> SortBy(Expression<Func<TDocument, dynamic>> field)
{
sortDefinitions.Add(Builders<TDocument>.Sort.Ascending(field));
return this;
}
public Collection<TDocument> SortDescending(Expression<Func<TDocument, dynamic>> field)
{
sortDefinitions.Add(Builders<TDocument>.Sort.Descending(field));
return this;
}
#endregion
public Collection<TDocument> Search(string[] searchKeywords)
{
this.keywords = searchKeywords;
return this;
}
public virtual void Insert(TDocument t)
{
MongoCollection.InsertOne(t);
}
public virtual long UpdateOne(Expression<Func<TDocument, bool>> filter)
{
if (updateDefinitions == null || updateDefinitions.Count < 1)
return 0;
var update = Builders<TDocument>.Update.Combine(updateDefinitions);
var result = MongoCollection.UpdateOne(filter, update);
return result.ModifiedCount;
}
public virtual long UpdateMany(Expression<Func<TDocument, bool>> filter)
{
if (updateDefinitions == null || updateDefinitions.Count < 1)
return 0;
var update = Builders<TDocument>.Update.Combine(updateDefinitions);
var result = MongoCollection.UpdateMany(filter, update);
return result.ModifiedCount;
}
public virtual long DeleteOne(Expression<Func<TDocument, bool>> filter)
{
var result = MongoCollection.DeleteOne(filter);
return result.DeletedCount;
}
public virtual long DeleteMany(Expression<Func<TDocument, bool>> filter)
{
var result = MongoCollection.DeleteMany(filter);
return result.DeletedCount;
}
public virtual TDocument Get(object objectId)
{
if (objectId == null)
return default;
if (!ObjectId.TryParse(objectId.ToString(), out ObjectId id))
return default;
BsonDocument filter = new BsonDocument("_id", id);
var result = MongoCollection.Find(filter).FirstOrDefault();
return result;
}
public virtual long Count(Expression<Func<TDocument, bool>> filter)
{
long count = MongoCollection.CountDocuments(filter);
return count;
}
public virtual TDocument First(Expression<Func<TDocument, bool>> filter)
{
var findFluent = MongoCollection.Find(filter);
if (sortDefinitions != null && sortDefinitions.Count > 0)
{
var sort = Builders<TDocument>.Sort.Combine(sortDefinitions.ToArray());
findFluent.Sort(sort);
}
var result = findFluent.FirstOrDefault();
return result;
}
public virtual TDocument First()
{
var findFluent = MongoCollection.Find(x => true);
if (sortDefinitions != null && sortDefinitions.Count > 0)
{
var sort = Builders<TDocument>.Sort.Combine(sortDefinitions.ToArray());
findFluent.Sort(sort);
}
var result = findFluent.FirstOrDefault();
return result;
}
public virtual List<TDocument> List(Expression<Func<TDocument, bool>> filter, int page = 1, int limit = 10)
{
page = Math.Max(1, page);
limit = Math.Max(limit, 10);
limit = Math.Min(limit, 1000);
int skip = (page - 1) * limit;
var filterBuilder = Builders<TDocument>.Filter;
List<FilterDefinition<TDocument>> filters = new List<FilterDefinition<TDocument>>
{
filterBuilder.Where(filter)
};
if (keywords != null && keywords.Count() > 0)
{
filters.Add(filterBuilder.Text(string.Join(" ", keywords)));
}
var finallyFilter = filterBuilder.And(filters);
var findFluent = MongoCollection.Find(finallyFilter).Skip(skip).Limit(limit);
if (sortDefinitions != null && sortDefinitions.Count > 0)
{
var sort = Builders<TDocument>.Sort.Combine(sortDefinitions.ToArray());
findFluent.Sort(sort);
}
var result = findFluent.ToList();
return result;
}
public virtual PagedResult<TDocument> Paged(Expression<Func<TDocument, bool>> filter, int page, int limit)
{
List<TDocument> documents = List(filter, page, limit);
var total = MongoCollection.Find(filter).CountDocuments();
return new PagedResult<TDocument>(true, "", documents, total);
}
public virtual MongoDBContextCursor ToCursor(Expression<Func<TDocument, bool>> filter)
{
return new MongoDBContextCursor(this.MongoCollection, filter, this.sortDefinitions);
}
public class MongoDBContextCursor
{
private readonly IMongoCollection<TDocument> _mongoCollection;
private readonly Expression<Func<TDocument, bool>> _cursorFilter;
private readonly List<SortDefinition<TDocument>> _sortDefinitions;
private int _cursorSkip = 0;
private int _cursorPage = 0;
private int _cursorLimit = 100;
public MongoDBContextCursor(IMongoCollection<TDocument> mongoCollection, Expression<Func<TDocument, bool>> filter, List<SortDefinition<TDocument>> sortDefinitions)
{
_mongoCollection = mongoCollection;
_cursorFilter = filter;
_sortDefinitions = sortDefinitions;
}
public List<TDocument> Current
{
get
{
var findFluent = _mongoCollection.Find(_cursorFilter).Skip(_cursorSkip).Limit(_cursorLimit);
if (_sortDefinitions != null && _sortDefinitions.Count > 0)
{
var sort = Builders<TDocument>.Sort.Combine(_sortDefinitions.ToArray());
findFluent.Sort(sort);
}
_cursorPage++;
var result = findFluent.ToList();
return result;
}
}
public bool MoveNext()
{
_cursorSkip = _cursorPage * _cursorLimit;
var findFluent = _mongoCollection.Find(_cursorFilter).Skip(_cursorSkip).Limit(_cursorLimit);
if (_sortDefinitions != null && _sortDefinitions.Count > 0)
{
var sort = Builders<TDocument>.Sort.Combine(_sortDefinitions.ToArray());
findFluent.Sort(sort);
}
var count = findFluent.CountDocuments();
return count > 0;
}
public void Reset()
{
_cursorPage = 0;
_cursorLimit = 100;
}
}
public virtual KeyValuePair<string, object> GetElementKey(Expression expressionBody, object keyType)
{
MemberInfo member;
if (expressionBody is UnaryExpression)
{
member = ((MemberExpression)((UnaryExpression)expressionBody).Operand).Member;
}
else if (expressionBody is MemberExpression)
{
member = ((MemberExpression)expressionBody).Member;
}
else if (expressionBody is ParameterExpression)
{
member = ((ParameterExpression)expressionBody).Type;
}
else
{
throw new Exception("需要完善该方法");
}
string keyName;
if (member.IsDefined(typeof(BsonElementAttribute)))
{
keyName = member.GetCustomAttribute<BsonElementAttribute>().ElementName;
}
else
{
keyName = member.Name;
}
KeyValuePair<string, object> keyValues = new KeyValuePair<string, object>(keyName, keyType);
return keyValues;
}
public virtual bool HasIndexKey(params KeyValuePair<string, object>[] keyValues)
{
var indexs = MongoCollection.Indexes.List().ToList();
string bsonName;
List<string> keyNames = keyValues.Select(x => x.Key + "_" + x.Value.ToString()).ToList();
string keyName = string.Join("_", keyNames);
foreach (var index in indexs)
{
bsonName = index.GetValue("name").AsString;
if (bsonName == keyName)
return true;
}
return false;
}
public virtual void CreateTextIndex(Expression<Func<TDocument, object>> field)
{
var keyValues = GetElementKey(field.Body, "text");
bool created = HasIndexKey(keyValues);
if (created)
return;
var keys = Builders<TDocument>.IndexKeys.Text(field);
MongoCollection.Indexes.CreateOne(new CreateIndexModel<TDocument>(keys));
}
public virtual void CreateHashedIndex(Expression<Func<TDocument, object>> field)
{
var keyValues = GetElementKey(field.Body, "hashed");
bool created = HasIndexKey(keyValues);
if (created)
return;
var keys = Builders<TDocument>.IndexKeys.Hashed(field);
MongoCollection.Indexes.CreateOne(new CreateIndexModel<TDocument>(keys));
}
public virtual void CreateUniqueIndex(params Expression<Func<TDocument, object>>[] fields)
{
var builder = Builders<TDocument>.IndexKeys;
List<IndexKeysDefinition<TDocument>> keys = new List<IndexKeysDefinition<TDocument>>();
List<KeyValuePair<string, object>> pairs = new List<KeyValuePair<string, object>>();
KeyValuePair<string, object> keyValues;
foreach (var field in fields)
{
keyValues = GetElementKey(field.Body, 1);
pairs.Add(keyValues);
keys.Add(builder.Ascending(field));
}
if (keys.Count < 1)
return;
if (HasIndexKey(pairs.ToArray()))
return;
MongoCollection.Indexes.CreateOne(new CreateIndexModel<TDocument>(builder.Combine(keys), new CreateIndexOptions
{
Unique = true,
Background = true,
}));
}
public virtual void CreateUniqueIndexByDescending(Expression<Func<TDocument, object>> field)
{
var keyValues = GetElementKey(field.Body, -1);
bool created = HasIndexKey(keyValues);
if (created)
return;
var keys = Builders<TDocument>.IndexKeys.Descending(field);
MongoCollection.Indexes.CreateOne(new CreateIndexModel<TDocument>(keys, new CreateIndexOptions
{
Unique = true,
Background = true,
}));
}
public virtual void CreateExpireAfterIndex(Expression<Func<TDocument, object>> field, TimeSpan timeSpan)
{
var keyValues = GetElementKey(field.Body, 1);
bool created = HasIndexKey(keyValues);
if (created)
return;
var keys = Builders<TDocument>.IndexKeys.Ascending(field);
MongoCollection.Indexes.CreateOne(new CreateIndexModel<TDocument>(keys, new CreateIndexOptions
{
Background = true,
ExpireAfter = timeSpan
}));
}
}
private static readonly ConcurrentDictionary<Type, string> collectionNameMap = new ConcurrentDictionary<Type, string>();
//确定集合名
private string DetermineCollectionName<T>(string likelyCollectionName)
{
if (!collectionNameMap.TryGetValue(typeof(T), out string name))
{
name = likelyCollectionName;
if (!HasCollection(name))
{
if (!typeof(T).IsDefined(typeof(CollectionNameAttribute)))
new Exception("类" + typeof(T).Name + "缺失" + nameof(CollectionNameAttribute) + "特性修饰,该特性参数name作为在数据库中对应的集合名");
var attr = typeof(T).GetCustomAttribute<CollectionNameAttribute>();
name = attr.Name;
}
collectionNameMap[typeof(T)] = name;
}
return name;
}
private bool HasCollection(string name)
{
return this.MongoDatabase.ListCollectionNames().ToList().Contains(name);
}
public MongoClient MongoClient { get; private set; }
public IMongoDatabase MongoDatabase { get; private set; }
internal static Action<TDatabase> collectionConstructor;
public static TDatabase Init(MongoDBOptions mongoDBOptions)
{
TDatabase db = new TDatabase();
db.InitDatabase(mongoDBOptions);
return db;
}
internal void InitDatabase(MongoDBOptions mongoDBOptions)
{
//MongoClientSettings settings = MongoClientSettings.FromConnectionString(mongoDBOptions.ConnectionString);
this.MongoClient = new MongoClient(mongoDBOptions.ConnectionString);
this.MongoDatabase = this.MongoClient.GetDatabase(mongoDBOptions.DatabaseName);
if (collectionConstructor == null)
collectionConstructor = CreateCollectionConstructorForCollection();
collectionConstructor(this as TDatabase);
}
#region 为集合创建构造函数
internal virtual Action<TDatabase> CreateCollectionConstructorForCollection()
{
return CreateCollectionConstructor(typeof(Collection<>));
}
protected Action<TDatabase> CreateCollectionConstructor(Type collectionType)
{
return CreateCollectionConstructor(new[] { collectionType });
}
protected Action<TDatabase> CreateCollectionConstructor(params Type[] collectionTypes)
{
var dm = new DynamicMethod("ConstructInstances", null, new[] { typeof(TDatabase) }, true);
var il = dm.GetILGenerator();
var setters = GetType().GetProperties()
.Where(p => p.PropertyType.IsGenericType && collectionTypes.Contains(p.PropertyType.GetGenericTypeDefinition()))
.Select(p => Tuple.Create(
p.GetSetMethod(true),
p.PropertyType.GetConstructor(new[] { typeof(TDatabase), typeof(string) }),
p.Name,
p.DeclaringType
));
foreach (var setter in setters)
{
// [db]
il.Emit(OpCodes.Ldarg_0);
// [db, likelyname]
il.Emit(OpCodes.Ldstr, setter.Item3);
// [collection]
il.Emit(OpCodes.Newobj, setter.Item2);
var collection = il.DeclareLocal(setter.Item2.DeclaringType);
// []
il.Emit(OpCodes.Stloc, collection);
// [db]
il.Emit(OpCodes.Ldarg_0);
// [db cast to container]
il.Emit(OpCodes.Castclass, setter.Item4);
// [db cast to container, collection]
il.Emit(OpCodes.Ldloc, collection);
// []
il.Emit(OpCodes.Callvirt, setter.Item1);
}
il.Emit(OpCodes.Ret);
return (Action<TDatabase>)dm.CreateDelegate(typeof(Action<TDatabase>));
}
#endregion
}
}
配置选项
public class MongoDbOptions
{
public string DatabaseName { get; set; }
public string ConnectionString { get; set; }
}
使用
要操作集合对应的实体类映射
public class TestContext : MongoDBContext<TestContext>
{
public Collection<Person> Person{ get; set; }
public Collection<Video> Video { get; set; }
public Collection<Music> Music{ get; set; }
}
使用MongoDBFactory.DemoContext.Person调用增删改查方法
public class MongoDBFactory
{
public static DemoContext =>
MongoDBContext<TestContext>.Init(new MongoDbOptions
{
ConnectionString = "mongodb://admin:10086@192.168.101.168:27017",
DatabaseName = "test",
});
}