实现MongoDBContext与EFContext相同的调用风格

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",
            });

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值