MongoDB .NET驱动程序和文本搜索

时间:2016-12-01 16:03:23

标签: mongodb linq

我正在使用这个MongoDB驱动程序:https://mongodb.github.io/mongo-csharp-driver/ 我想使用文本索引进行搜索,我想这是在所有文本字段中创建的,如下所示:

{
    "_fts" : "text",
    "_ftsx" : 1
}

我正在使用linq查询来过滤数据,例如:

MongoClient client = new MongoClient(_mongoConnectionString);
IMongoDatabase mongoDatabase = client.GetDatabase(DatabaseName);
var aCollection = mongoDatabase.GetCollection<MyTypeSerializable>(CollectionName);

IMongoQueryable<MyTypeSerializable> queryable = aCollection.AsQueryable()
                .Where(e=> e.Field == 1);
var result = queryable.ToList();

如何使用此方法使用文本搜索?

4 个答案:

答案 0 :(得分:1)

查看C#MongoDB驱动程序中的PredicateTranslator,没有任何表达式转换为text查询。因此,您将无法使用linq查询获得text查询。

但是,您可以尝试使用Builder<>进行文字搜索:

MongoClient client = new MongoClient(_mongoConnectionString);
IMongoDatabase mongoDatabase = client.GetDatabase(DatabaseName);
var aCollection = mongoDatabase.GetCollection<MyTypeSerializable>(CollectionName);

var cursor = await aCollection.FindAsync(Builders<MyTypeSerializable>.Filter.Text("search"));

var results = await cursor.ToListAsync();

有关文字过滤器的详细信息,请访问https://docs.mongodb.com/manual/reference/operator/query/text/

答案 1 :(得分:0)

可以修改MongoDb驱动程序源代码。让我向你解释一下:

  1. 考虑“PredicateTranslator”不会将linq表达式转换为“$ text”查询。但是有一个Text()方法“FilterDefinitionBuilder”类,“PredicateTranslator”不知道实体类属性有文本搜索索引。
  2. 您必须使用Attribute标记实体类属性(谓词语句中的条件)。该属性适用于remark属性具有全文搜索索引。
  3. 从现在开始,“PredicateTranslator”类知道该属性有一个带有此属性“PredicateTranslator”的全文搜索索引
  4. 让我给你看一些代码:

    1. 在MongoDB.Bson项目中创建一个Attribute,如下所示:

      [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class BsonFullTextSearchAttribute:Attribute { }

    2. 在您的实体类属性中放置“BsonFullTextSearchAttribute”属性,如下所示:

      public class History 
      {
          [MongoDB.Bson.Serialization.Attributes.BsonFullTextSearch]
          public string ObjectJSON { get; set; }
      }
      
    3. 在MongoDB.Driver.Linq.Translators.QueryableTranslator.cs

      • 在Expression&gt;中添加一个保持实体类类型的字段,如下所示:

        private Type _sourceObjectTypeInExpression;
        
      • 添加一个方法来获取实体类类型,如下所示:

        private void GetObjectType(Expression node)
        {
            if (node.Type != null && node.Type.GenericTypeArguments != null && node.Type.GenericTypeArguments.Length > 0)
            {
                this._sourceObjectTypeInExpression = node.Type.GenericTypeArguments[0]; 
            }
         }
        
      • 替换“public static QueryableTranslation Translate()”方法,如下所示:

        public static QueryableTranslation Translate(Expression node, IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions)
        {
        var translator = new QueryableTranslator(serializerRegistry, translationOptions);
        translator.GetObjectType(node);
        translator.Translate(node);
        
        var outputType = translator._outputSerializer.ValueType;
        var modelType = typeof(AggregateQueryableExecutionModel<>).MakeGenericType(outputType);
        var modelTypeInfo = modelType.GetTypeInfo();
        var outputSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(new[] { outputType });
        var constructorParameterTypes = new Type[] { typeof(IEnumerable<BsonDocument>), outputSerializerInterfaceType };
        var constructorInfo = modelTypeInfo.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)
            .Where(c => c.GetParameters().Select(p => p.ParameterType).SequenceEqual(constructorParameterTypes))
            .Single();
        var constructorParameters = new object[] { translator._stages, translator._outputSerializer };
        var model = (QueryableExecutionModel)constructorInfo.Invoke(constructorParameters);
        
        return new QueryableTranslation(model, translator._resultTransformer);
        }
        
      • 在TranslateWhere()方法中将“_sourceObjectTypeInExpression”字段传递给PredicateTranslator.Translate()静态方法

        var predicateValue = PredicateTranslator.Translate(node.Predicate, _serializerRegistry, this._sourceObjectTypeInExpression);
        

        B中。 MongoDB.Driver.Linq.Translators.PredicateTranslator.cs      - 添加一个字段:“private Type sourceObjectTypeInExpression = null;”

        - Replace constructor as shown below (there has to be only one constructor);
            private PredicateTranslator(Type _sourceObjectTypeInExpression)
            {
                this.sourceObjectTypeInExpression = _sourceObjectTypeInExpression;
            }
        
        - Replace function "public static BsonDocument Translate(Expression node, IBsonSerializerRegistry serializerRegistry)" as shown below;
            public static BsonDocument Translate(Expression node, IBsonSerializerRegistry serializerRegistry, Type sourceObjectTypeInExpression)
            {
                var translator = new PredicateTranslator(sourceObjectTypeInExpression);
                node = FieldExpressionFlattener.FlattenFields(node);
                return translator.Translate(node)
                    .Render(serializerRegistry.GetSerializer<BsonDocument>(), serializerRegistry);
            }
        
        - Add these lines for reflection cache:
            #region FullTextSearch
            private static readonly object mSysncFullTextSearchObjectCache = new object();
            private static ConcurrentDictionary<string, List<string>> _fullTextSearchObjectCache = null;
            private static ConcurrentDictionary<string, List<string>> FullTextSearchObjectCache
            {
                get
                {
                    if (_fullTextSearchObjectCache == null)
                    {
                        lock (mSysncFullTextSearchObjectCache)
                        {
                            try
                            {
                                if (_fullTextSearchObjectCache == null)
                                {
                                    _fullTextSearchObjectCache = new ConcurrentDictionary<string, List<string>>();
                                }
                            }
                            finally
                            {
                                Monitor.PulseAll(mSysncFullTextSearchObjectCache);
                            }
                        }
                    }
        
                    return _fullTextSearchObjectCache;
                }
            }
        
            private bool IsFullTextSearchProp(Type entityType, string propName)
            {
                bool retVal = false;
                string entityName = entityType.Name;
        
                this.SetObject2FullTextSearchObjectCache(entityType);
                if (FullTextSearchObjectCache.ContainsKey(entityName))
                {
                    List<string> x = FullTextSearchObjectCache[entityName];
                    retVal = x.Any(p => p == propName);
                }
        
                return retVal;
            }
        
            private void SetObject2FullTextSearchObjectCache(Type entityType)
            {
                string entityName = entityType.Name;
        
                if (!FullTextSearchObjectCache.ContainsKey(entityName))
                {
                    List<string> retVal = new List<string>();
        
                    PropertyInfo[] currentProperties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                    foreach (PropertyInfo tmp in currentProperties)
                    {
                        var attributes = tmp.GetCustomAttributes();
                        BsonFullTextSearchAttribute x = (BsonFullTextSearchAttribute)attributes.FirstOrDefault(a => typeof(BsonFullTextSearchAttribute) == a.GetType());
                        if (x != null)
                        {
                            retVal.Add(tmp.Name);
                        }
                    }
        
                    FieldInfo[] currentFields = entityType.GetFields(BindingFlags.Public | BindingFlags.Instance);
                    foreach (FieldInfo tmp in currentFields)
                    {
                        var attributes = tmp.GetCustomAttributes();
                        BsonFullTextSearchAttribute x = (BsonFullTextSearchAttribute)attributes.FirstOrDefault(a => typeof(BsonFullTextSearchAttribute) == a.GetType());
                        if (x != null)
                        {
                            retVal.Add(tmp.Name);
                        }
                    }
        
                    FullTextSearchObjectCache.AddOrUpdate(entityName, retVal, (k, v) => v);
                }
            }
            #endregion
        
        - Replace "switch (operatorType)" switch in "private FilterDefinition<BsonDocument> TranslateComparison(Expression variableExpression, ExpressionType operatorType, ConstantExpression constantExpression)" function as shown below;
            bool isFullTextSearchProp = this.IsFullTextSearchProp(this.sourceObjectTypeInExpression, fieldExpression.FieldName);
            switch (operatorType)
            {
                case ExpressionType.Equal:
                    if (!isFullTextSearchProp)
                    {
                        return __builder.Eq(fieldExpression.FieldName, serializedValue);
                    }
                    else
                    {
                        return __builder.Text(serializedValue.ToString());
                    }
                case ExpressionType.GreaterThan: return __builder.Gt(fieldExpression.FieldName, serializedValue);
                case ExpressionType.GreaterThanOrEqual: return __builder.Gte(fieldExpression.FieldName, serializedValue);
                case ExpressionType.LessThan: return __builder.Lt(fieldExpression.FieldName, serializedValue);
                case ExpressionType.LessThanOrEqual: return __builder.Lte(fieldExpression.FieldName, serializedValue);
                case ExpressionType.NotEqual:
                    if (!isFullTextSearchProp)
                    {
                        return __builder.Ne(fieldExpression.FieldName, serializedValue);
                    }
                    else
                    {
                        throw new ApplicationException(string.Format("Cannot use \"NotEqual\" on FullTextSearch property: \"{0}\"", fieldExpression.FieldName));
                    }
            }
        
        - Replace "switch (methodCallExpression.Method.Name)" switch in "private FilterDefinition<BsonDocument> TranslateStringQuery(MethodCallExpression methodCallExpression)" function as shown below;
            bool isFullTextSearchProp = this.IsFullTextSearchProp(this.sourceObjectTypeInExpression, tmpFieldExpression.FieldName);
            var pattern = Regex.Escape((string)constantExpression.Value);
            if (!isFullTextSearchProp)
            {
                switch (methodCallExpression.Method.Name)
                {
                    case "Contains": pattern = ".*" + pattern + ".*"; break;
                    case "EndsWith": pattern = ".*" + pattern; break;
                    case "StartsWith": pattern = pattern + ".*"; break; // query optimizer will use index for rooted regular expressions
                    default: return null;
                }
        
                var caseInsensitive = false;
                MethodCallExpression stringMethodCallExpression;
                while ((stringMethodCallExpression = stringExpression as MethodCallExpression) != null)
                {
                    var trimStart = false;
                    var trimEnd = false;
                    Expression trimCharsExpression = null;
                    switch (stringMethodCallExpression.Method.Name)
                    {
                        case "ToLower":
                        case "ToLowerInvariant":
                        case "ToUpper":
                        case "ToUpperInvariant":
                            caseInsensitive = true;
                            break;
                        case "Trim":
                            trimStart = true;
                            trimEnd = true;
                            trimCharsExpression = stringMethodCallExpression.Arguments.FirstOrDefault();
                            break;
                        case "TrimEnd":
                            trimEnd = true;
                            trimCharsExpression = stringMethodCallExpression.Arguments.First();
                            break;
                        case "TrimStart":
                            trimStart = true;
                            trimCharsExpression = stringMethodCallExpression.Arguments.First();
                            break;
                        default:
                            return null;
                    }
        
                    if (trimStart || trimEnd)
                    {
                        var trimCharsPattern = GetTrimCharsPattern(trimCharsExpression);
                        if (trimCharsPattern == null)
                        {
                            return null;
                        }
        
                        if (trimStart)
                        {
                            pattern = trimCharsPattern + pattern;
                        }
                        if (trimEnd)
                        {
                            pattern = pattern + trimCharsPattern;
                        }
                    }
        
                    stringExpression = stringMethodCallExpression.Object;
                }
        
                pattern = "^" + pattern + "$";
                if (pattern.StartsWith("^.*"))
                {
                    pattern = pattern.Substring(3);
                }
                if (pattern.EndsWith(".*$"))
                {
                    pattern = pattern.Substring(0, pattern.Length - 3);
                }
        
                var fieldExpression = GetFieldExpression(stringExpression);
                var options = caseInsensitive ? "is" : "s";
                return __builder.Regex(fieldExpression.FieldName, new BsonRegularExpression(pattern, options));
            }
            else
            {
                return __builder.Text(pattern);
            }
        

答案 2 :(得分:0)

在搜索解决方案时,我发现了FilterDefinition<T>.Inject()扩展方法。 因此,我们可以更深入地在IMongoQueryable<T>上创建一个扩展:

public static class MongoQueryableFullTextExtensions
{
    public static IMongoQueryable<T> WhereText<T>(this IMongoQueryable<T> query, string search)
    {
        var filter = Builders<T>.Filter.Text(search);
        return query.Where(_ => filter.Inject());
    }
}

并像这样使用它:

IMongoDatabase database = GetMyDatabase();

var results = database
    .GetCollection<Blog>("Blogs")
    .AsQueryable()
    .WhereText("stackoverflow")
    .Take(10)
    .ToArray();

希望这对某人有帮助:)

答案 3 :(得分:-1)

怎么样:

IMongoQueryable<MyTypeSerializable> queryable = aCollection
.AsQueryable()
.Where(e=> e.Field.Contains("term"));