【问题标题】:Bitwise enum (flags) query using MongoDB's official C# driver使用 MongoDB 的官方 C# 驱动程序进行位枚举(标志)查询
【发布时间】:2013-07-01 23:06:43
【问题描述】:

当我尝试运行以下形式的 LINQ 查询时:

MongoCollection<MyEntity> collection;

collection.AsQueryable().Where(entity =>
    (entity.Flags & MyFlags.AFlag) != MyFlags.None);

我收到ArgumentException 和消息Unsupported where clause: ((Int32)((Int32)entity.Flags &amp; 4) != 0).

这是一个已知的错误/功能吗?

有什么解决办法吗?

documentation 看来,MongoDB 有按位更新,但没有按位查询。

作为对比,同样的查询在使用 ServiceStack 作为客户端的 Redis 上运行流畅。

我确实找到了这两个链接(link1link2),它们建议使用 JavaScript,但是,这会使服务层的实现非常依赖于 DB 技术。

【问题讨论】:

  • 也许你可以尝试使用 linq 来反对查询:collection.AsQueryable().ToList().Where(entity => (entity.Flags & MyFlags.AFlag) != MyFlags.None);
  • 这会将所有对象反序列化到内存中。我可以在其他过滤器之后使用它(假设它们减少了足够的记录)。
  • @Tim.Tang 这适用于我的查询,因为我有其他条件可以事先过滤。还注意到 GroupBy() 失败,需要移到 .ToList() 之后。将此作为答案发布,我会接受。

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


【解决方案1】:

从 MongoDB v 3.2 开始,您可以使用 bitsAllSetbitsAnySet,具体取决于您要搜索的内容。

所以,使用 C# MongoDB 驱动程序:

//Check single Flag as OP
collection.Find(Builders<MyEntity>.Filter.BitsAllSet(myEntity => myEntity.Flags, (long) MyFlags.AFlag));

//Check all multiple Flags
collection.Find(Builders<MyEntity>.Filter.BitsAllSet(myEntity => myEntity.Flags, (long) MyFlags.AFlag | MyFlags.BFlag));

//Check any multiple Flag
collection.Find(Builders<MyEntity>.Filter.BitsAnySet(myEntity => myEntity.Flags, (long) MyFlags.AFlag | MyFlags.BFlag));

注意:有一个open issue 可以在按位运算上使用索引。在 MongoDB 4.2 上,它们仍然不使用它们,所以如果您打算大量使用它们,请注意(我们曾经使用它们,之后我们不得不重构所有东西)。

【讨论】:

    【解决方案2】:

    我的解决方案有两个部分。 我为 Enum 标志制作了一个序列化程序,它将所有值存储在字符串列表中。 我为 Linq 做了一个扩展方法来“注入”我需要的 mongo 查询。

    public static IQueryable<TItem> HasFlags<TItem, TProperty>(
        this IQueryable<TItem> items,
        Expression<Func<TItem, TProperty>> itemPropertyExpression,
        params Enum[] enumFlags)
    {
        var enumFlagNames = enumFlags.Select(enumFlag => (BsonValue)enumFlag.ToString());
        return items.Where(item => Query.In(ExtendedObject.GetPropertyName(itemPropertyExpression), enumFlagNames).Inject());
    }
    

    这样,它既可读,我不需要将所有对象反序列化到内存中。

    P.S:GetPropertyName 方法只是获取属性名称的一种类型安全的方法:

    public static string GetPropertyName<TClass, TProperty>(
        Expression<Func<TClass, TProperty>> entityPropertyExpression)
    {
        return ((MemberExpression)entityPropertyExpression.Body).Member.Name;
    }
    

    【讨论】:

    • 请注意,如果您想处理点符号等,则需要更强大的 GetPropertyName
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-04
    • 2019-09-07
    • 1970-01-01
    • 2020-04-12
    • 1970-01-01
    • 2020-11-26
    相关资源
    最近更新 更多