2016-12-01 3 views
1

Я использую этот драйвер MongoDB: https://mongodb.github.io/mongo-csharp-driver/ , и я хотел бы найти с помощью текстового индекса, который (я думаю) создается на всех текстовых полях, как так:MongoDB .NET драйвер и текстовый поиск

{ 
    "_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(); 

Как использовать текстовый поиск, используя этот метод?

ответ

1

Глядя на PredicateTranslator в драйвере C# MongoDB есть не любое выражение, которое преобразуется в к text запроса. Таким образом, вы не сможете выполнить запрос text, используя запрос linq.

Однако вы можете попробовать просто делать поиск текста с 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/

+0

Выглядит законно, я проверю его и вернусь, спасибо. – user1713059

+0

Если это работает, вы можете принять ответ :-) –

+0

Не могли бы вы также рассказать мне, как объединить этот подход с фильтрами на основе linq? Могу ли я сделать курсор.ToEnumerable(). Где (e => e.Field == 1) 'и он будет фактически« материализован »после' .ToList() 'в конце? – user1713059

-1

Как насчет:

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

Вы уверены, что это использует текстовый индекс? – user1713059

+0

Это зависит от двух факторов: реализации провайдера Linq и процессора запросов на стороне mongodb. Чтобы убедиться, что вам нужно проследить эту цепочку. –

+0

Глядя, хотя исходный код выше не использует текстовый запрос в mongodb. –

0

Это possib le, чтобы изменить исходный код драйвера MongoDb. Позвольте мне объяснить это вам:

  1. Рассмотрите, что «PredicateTranslator» не преобразует выражение linq в запрос «$ text». Однако существует класс Text() метода FilterDefinitionBuilder, «PredicateTranslator» не знает, что свойство класса сущности имеет индекс текстового поиска.
  2. Вы должны пометить свойство класса сущности (условие в инструкции предиката) с атрибутом. Атрибут работает для свойства примечания имеет индекс полнотекстового поиска.
  3. Отныне «PredicateTranslator» класс знает, свойство имеет полный индекс поиска текста с помощью этого атрибута «PredicateTranslator»

Позвольте мне показать вам код:

  1. На проекте MongoDB.Bson создать атрибут, как показано ниже:

    [AttributeUsage (AttributeTargets.Property | AttributeTargets.Field)] класс BsonFullTextSearchAttribute общественности: Атрибут { }

  2. В вашем классе объект недвижимости месте "BsonFullTextSearchAttribute" Attribute, как показано ниже:

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

    • Добавить поле, которое держит в выражении типа класса объекта>, как показано ниже:

      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); 
      

      Б. MongoDB.Driver.Linq.Translators.PredicateTranslator.cs - Добавить поле: " private Тип 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); 
          }