我正在使用这个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();
如何使用此方法使用文本搜索?
答案 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驱动程序源代码。让我向你解释一下:
让我给你看一些代码:
在MongoDB.Bson项目中创建一个Attribute,如下所示:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class BsonFullTextSearchAttribute:Attribute { }
在您的实体类属性中放置“BsonFullTextSearchAttribute”属性,如下所示:
public class History
{
[MongoDB.Bson.Serialization.Attributes.BsonFullTextSearch]
public string ObjectJSON { get; set; }
}
在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"));