【问题标题】:Filtering using AsQueryable in Mongodb C# - ExpandoObject在 Mongodb C# 中使用 AsQueryable 进行过滤 - ExpandoObject
【发布时间】:2020-05-17 12:01:32
【问题描述】:

我正在尝试使用 AsQueryable 和 LINQ 函数查询 mongodb 数据库。

我目前的数据库结构是我有一些集合,主要是在 C# 中定义的记录,如下所示:

public class Record  
    {
        [BsonElement("formName")]
        public string FormName { get; set; }

        [BsonElement("created")]
        public DateTime Created { get; set; }

        [BsonElement("createdBy")]
        public string CreatedBy { get; set; }

        [BsonId]
        [BsonIgnoreIfDefault]
        [BsonRepresentation(BsonType.ObjectId)]
        private string InternalId { get; set; }

        [BsonElement("recordId")]
        [Newtonsoft.Json.JsonProperty("recordId")]
        public string Id { get; set; }

        [BsonElement("organisationId")]
        public string OrganisationId { get; set; }

        // TODO: Consider making configurable
        private string appName = "MediLog";

        [BsonElement("appName")]
        public string AppName
        {
            get { return this.appName; }
            set { this.appName = value; }
        }

        [BsonElement("schemaVersion")]
        public int SchemaVersion { get; set; }

        [BsonElement("contents")]
        public ExpandoObject Contents { get; set; }

        [BsonElement("modified")]
        public DateTime Modified { get; set; }

        [BsonElement("modifiedBy")]
        public string ModifiedBy { get; set; }

        [BsonElement("majorVersion")]
        public int MajorVersion { get; private set; }

        [BsonElement("minorVersion")]
        public int MinorVersion { get; private set; }

        [BsonElement("version")]
        public string Version
        {
            get
            {
                return (MajorVersion + "." + MinorVersion);
            }

            set
            {
                MajorVersion = Convert.ToInt32(value?.Substring(0, value.IndexOf(".", StringComparison.OrdinalIgnoreCase)), CultureInfo.InvariantCulture);
                MinorVersion = Convert.ToInt32(value?.Substring(value.IndexOf(".", StringComparison.OrdinalIgnoreCase) + 1), CultureInfo.InvariantCulture);
            }
        }

    }

和你一样,数据主要存储在Contents,这是一个ExpandoObject,这就是我的问题所在。

我正在尝试做这样的事情:

 var collection =
                database.GetCollection<Record>("recordData").AsQueryable()
                    .Where(x =>  x.Contents.SingleOrDefault(z => z.Key == "dateOfBirth").Value.ToString().Length > 0)
                        .ToList();  

但我得到了这个例外:

System.InvalidOperationException H结果=0x80131509 Message={document}{contents}.SingleOrDefault(z => (z.Key == "dateOfBirth")).Value.ToString().Length 不受支持。

另外,当我尝试时:

  var collection1 = database.GetCollection<Record>("recordData").AsQueryable()
                .Where(x => (DateTime)(x.Contents.SingleOrDefault(z => z.Key == "dateOfBirth").Value)  > DateTime.Now)
                .ToList();

我得到了这个例外:

System.InvalidOperationException
  HResult=0x80131509
  Message=Convert({document}{contents}.SingleOrDefault(z => (z.Key == "dateOfBirth")).Value) is not supported.

另外,当我这样做时:

var collection =
database.GetCollection(“recordData”).AsQueryable()
.Where(x => (DateTime)x.Contents.First(z => z.Key == “dateOfBirth”).Value > DateTime.Now ).ToList();

我遇到了这个异常:

System.NotSupportedException: ‘The expression tree is not supported: {document}{contents}’

所以我的问题实际上是如何使用 AsQueryable 和 LINQ 对 ExpandoObject 类型的对象运行查询。

谢谢!

【问题讨论】:

    标签: c# mongodb linq mongodb-.net-driver


    【解决方案1】:

    无论您试图传递给.Where() 方法的内容都是一个表达式树,它需要被翻译成MongoDB 查询语言。 C# 编译器将接受 ExpandoObject 的方法,如 SingleOrDefault(扩展方法),但是当此类表达式树需要转换为 Mongo 查询时,这将在运行时失败。

    当您使用 MongoDB 时,ExpandoObject 没有什么特别之处。它必须以某种方式翻译成 BSON 文档(MongoDB 格式),例如下面的代码:

    dynamic o = new ExpandoObject();
    o.dateOfBith = new DateTime(2000, 1, 1);
    r.Contents = o;
    col.InsertOne(r);
    

    插入ExpandoObjectBsonDocumentDictionary&lt;string, T&gt; 相同,所以它就变成了:

    { "_id" : ObjectId(",,,"), "contents" : { "dateOfBith" : ISODate("2000-01-01T07:00:00Z") } }
    

    在您的数据库中。

    知道您可以使用the dot notation 构建查询。还有一个 StringFieldDefinition 类可以使用,因为您不能以强类型的方式从 ExpandoObject 构建表达式树:

    var fieldDef = new StringFieldDefinition<Record, DateTime>("contents.dateOfBith");
    var filter = Builders<Record>.Filter.Gt(fieldDef, new DateTime(2000, 1, 1));
    
    var res = col.Find(filter).ToList();
    

    【讨论】:

      【解决方案2】:

      在我看来,这些表达式转换在 LINQ 中表现不佳。如果您向下一级将字段定义为字符串,它会播放得更好。比你可以Inject你的Where子句保留IQueryable如果你喜欢>

      更新(您可以有多个条件,以及不同类型的空值检查)

      string connectionString = "mongodb://localhost:27017";
      var client = new MongoClient(connectionString);
      var db = client.GetDatabase("test");
      var records = db.GetCollection<Record>("recordData");
      //dynamic content = new ExpandoObject();
      //content.dateOfBirth = DateTime.Today;
      //records.InsertOne(new Record
      //{
      //    AppName = "my app", Created = DateTime.Today, CreatedBy = "some user", FormName = "form name",
      //    OrganisationId = "organization id", SchemaVersion = 1, Version = "1.1", Contents = content
      //});
      // first option for multiple conditions:
      var filter =
          Builders<Record>.Filter.Lt("contents.dateOfBirth", DateTime.Now) &
          Builders<Record>.Filter.Gt("contents.dateOfBirth", DateTime.Now.AddDays(-10)) &
          // checking if the field is there. Whether the value is null or not.
          Builders<Record>.Filter.Exists("contents.dateOfBirth") &
          // checking if the field is there, and it's not null
          Builders<Record>.Filter.Ne("contents.dateOfBirth", BsonNull.Value);
      // second option for multiple conditions
      var dateOfBirths = records.AsQueryable().Where(_ => filter.Inject()).Where(x => x.AppName == "my app").ToList();
      

      从那里,您可以继续您的 IQueryable 语法。

      更新 2

      如果是.Where(x =&gt; x.AppName.Contains("app")),你会得到

      "pipeline" : [
              {
                  "$match" : {
                      "contents.dateOfBirth" : {
                          "$exists" : true,
                          "$gt" : ISODate("2020-05-15T12:48:14.483+02:00"),
                          "$lt" : ISODate("2020-05-25T12:48:14.480+02:00"),
                          "$ne" : null
                      }
                  }
              },
              {
                  "$match" : {
                      "appName" : /app/g
                  }
              }
          ]
      

      从分析数据库。因此&lt;string&gt;.Contains(&lt;string&gt;)IQueryable 中实现为不在内存中的正则表达式过滤器。

      【讨论】:

      • 迄今为止最好的答案!我也可以检查null吗?另外,有没有办法将多个条件组合在一起?
      • @AmirNo-Family 看起来可以。请记住,mongo 是动态的,因此某些内容的定义为“null”可能意味着该列是否存在,或者该字段的值是 BsonNull.Value
      • 非常感谢您的回答!它几乎回答了我的问题,并且我知道我必须遵循一些规则,例如 Gt、Lt、Ne 等。最后一件事:有没有办法检查字符串字段(例如 firstName)是否包含搜索短语(不区分大小写)?谢谢!
      • 您需要在字段上使用文本索引,而不是可以使用(此答案)[stackoverflow.com/a/41357957/1859959]&amp; 链中使用。 Filter.Text...。这很方便,因为您可以指定语言、文本、变音符号的敏感性
      • @AmirNo-Family 分析Contains 并非易事。 shell throws Error: SyntaxError: invalid regular expression flag s NoSQLBooster 工具帮助查看结果。查看更新 2。
      猜你喜欢
      • 2016-05-15
      • 2015-07-18
      • 2011-08-25
      • 2022-01-23
      • 2020-05-12
      • 1970-01-01
      • 2014-06-02
      • 1970-01-01
      • 2016-03-15
      相关资源
      最近更新 更多