2017-04-08 71 views
0

我有以下文檔模式。使用MongoDB提供程序過濾多個參數

{ 
     "name":"Name", 
     "region":"New Jersey", 
     "address":"92 Something Rd", 
     "city":"Jersey City", 
     "state":"NJ", 
     "zipCode":"07302", 
     "country":"USA", 
     amenities":[ 
     "Sauna", 
     "Locker", 
     "Shop" 
     ], 
     "services":[ 
     "Car Rental", 
     "Transportation" 
     ] 
} 

我想用一個調用服務器以獲取所有匹配任何的過濾器參數,其中圖1-1意思"state" = "NJ" OR "city" = "Jersey City"文件,而且當包含的任何文件陣列孩子列表中的任意值,例如[ "Sauna", "Locker" ] ANY IN "amenities" 。它應該是所有可能的過濾器的OR級聯。

使用我用下面的方法,想出了一個MongoRepository類到目前爲止,但不返回所期望的結果的C#MongoDB的驅動程序。

public async Task<IEnumerable<T>> DocumentsMatchEqFieldValueAsync<T>(string collectionName, 
      IDictionary<string, string> fieldsValues = null, 
      IDictionary<string, IEnumerable<string>> fieldsWithEnumerableValues = null, 
      IEnumerable<ObjectId> ids = null) 
{ 
    var cursor = await GetEqAsyncCursor<T>(collectionName, fieldsValues, fieldsWithEnumerableValues, ids).ConfigureAwait(false); 
    return await cursor.ToListAsync().ConfigureAwait(false); 
} 

protected Task<IAsyncCursor<T>> GetEqAsyncCursor<T>(string collectionName, 
      IDictionary<string, string> fieldsValues = null, 
      IDictionary<string, IEnumerable<string>> fieldsWithEnumerableValues = null, 
      IEnumerable<ObjectId> ids = null) 
{ 
    var collection = GetCollection<T>(collectionName); 
    var builder = Builders<T>.Filter; 

    // Not sure if this is the correct way to initialize it because it seems adding an empty filter condition returning ALL document; 
    FilterDefinition<T> filter = new BsonDocument(); 

    if (fieldsValues != null && 
     fieldsValues.Any()) 
    { 
     filter = filter | fieldsValues 
        .Select(p => builder.Eq(p.Key, p.Value)) 
        .Aggregate((p1, p2) => p1 | p2); 
    } 

    if (fieldsWithEnumerableValues != null && 
     fieldsWithEnumerableValues.Any()) 
    { 
     filter = filter | fieldsWithEnumerableValues 
        .Select(p => builder.AnyEq(p.Key, p.Value)) 
        .Aggregate((p1, p2) => p1 | p2); 
    } 

    if (ids != null && 
     ids.Any()) 
    { 
     filter = filter | ids 
       .Select(p => builder.Eq("_id", p)) 
       .Aggregate((p1, p2) => p1 | p2); 
    } 
    return collection.FindAsync(filter); 
} 

我希望它是通用的,所以客戶端可以調用這樣的方法。

public async Task should_return_any_records_matching_all_possible_criteria() 
{ 
    // Arrange 
    IDocumentRepository documentRepository = new MongoRepository(_mongoConnectionString, _mongoDatabase); 

    // Act 
    var documents = await documentRepository.DocumentsMatchEqFieldValueAsync<BsonDocument>(Courses, 
       fieldsValues: new Dictionary<string, string> 
       { 
        { "state", "NJ" }, 
        { "city", "Jersey City" } 
       }, 
       fieldsWithEnumerableValues: new Dictionary<string, IEnumerable<string>> 
       { 
        { "services", new List<string> { "Car Rental", "Locker" } }, 
        { "amenities", new List<string> { "Sauna", "Shop" } } 
       }); 

    // Assert 
    documents.ShouldNotBeEmpty(); 
} 

我期待的文件有"state" = "NJ" OR "city" = "Jersey City" OR "services" CONTAINS ANY OF "Car Rental", "Locker" OR "amenities" CONTAINS ANY OF "Sauna", "Shop"

回答

1

我發佈了下面的方法,我最終使用後,一些研究人員希望做同樣的未來幫助。我發現如何使用正則表達式here進行查詢,編寫純粹的MongoDB查詢並將它們添加到篩選器集合here以及如何調試生成的查詢here

在使用Studio 3T客戶端獲得所有這些信息和一點實驗後,在該方法的下面找到。

protected Task<IAsyncCursor<T>> GetEqAsyncCursor<T>(string collectionName, 
      IDictionary<string, string> fieldEqValue = null, 
      IDictionary<string, string> fieldContainsValue = null, 
      IDictionary<string, IEnumerable<string>> fieldEqValues = null, 
      IDictionary<string, IEnumerable<string>> fieldElemMatchInValues = null, 
      IEnumerable<ObjectId> ids = null) 
{ 
    var collection = GetCollection<T>(collectionName); 
    var builder = Builders<T>.Filter; 

    IList<FilterDefinition<T>> filters = new List<FilterDefinition<T>>(); 

    if (fieldEqValue != null && 
     fieldEqValue.Any()) 
    { 
     filters.Add(fieldEqValue 
        .Select(p => builder.Eq(p.Key, p.Value)) 
        .Aggregate((p1, p2) => p1 | p2)); 
    } 

    if (fieldContainsValue != null && 
     fieldContainsValue.Any()) 
    { 
     filters.Add(fieldContainsValue 
        .Select(p => builder.Regex(p.Key, new BsonRegularExpression($".*{p.Value}.*", "i"))) 
        .Aggregate((p1, p2) => p1 | p2)); 
    } 

    if (fieldEqValues != null && 
     fieldEqValues.Any()) 
    { 
     foreach (var pair in fieldEqValues) 
     { 
      foreach (var value in pair.Value) 
      { 
       filters.Add(builder.Eq(pair.Key, value)); 
      } 
     } 
    } 

    if (fieldElemMatchInValues != null && 
     fieldElemMatchInValues.Any()) 
    { 
     var baseQuery = "{ \"%key%\": { $elemMatch: { $in: [%values%] } } }"; 
     foreach (var item in fieldElemMatchInValues) 
     { 
      var replaceKeyQuery = baseQuery.Replace("%key%", item.Key); 
      var bsonQuery = replaceKeyQuery.Replace("%values%", 
         item.Value 
          .Select(p => $"\"{p}\"") 
          .Aggregate((value1, value2) => $"{value1}, 
{value2}")); 
      var filter = BsonSerializer.Deserialize<BsonDocument>(bsonQuery); 
      filters.Add(filter); 
     } 
    } 

    if (ids != null && 
     ids.Any()) 
    { 
     filters.Add(ids 
       .Select(p => builder.Eq("_id", p)) 
       .Aggregate((p1, p2) => p1 | p2)); 
    } 

    var filterConcat = builder.Or(filters); 

    // Here's how you can debug the generated query 
    //var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer<T>(); 
    //var renderedFilter = filterConcat.Render(documentSerializer, BsonSerializer.SerializerRegistry).ToString(); 

    return collection.FindAsync(filterConcat); 
}