【问题标题】:Db4o query: find all objects with ID = {anything in array}Db4o 查询:查找 ID = {数组中的任何内容}的所有对象
【发布时间】:2010-12-13 22:19:22
【问题描述】:

我的数据库中存储了 30,000 个 SimpleObject:

class SimpleObject
{
    public int Id { get; set; }
} 

我想在 DB4O 上运行一个查询,查找所有具有任何指定 ID 的 SimpleObjects:

public IEnumerable<SimpleObject> GetMatches(int[] matchingIds)
{
     // OH NOOOOOOES! This activates all 30,000 SimpleObjects. TOO SLOW!
     var query = from SimpleObject simple in db
                 join id in matchingIds on simple.Id equals id
                 select simple;
     return query.ToArray();
}  

如何编写此查询以使 DB4O 不会激活所有 30,000 个对象?

【问题讨论】:

标签: c# linq db4o


【解决方案1】:

您可以“构建”一个动态 linq 查询。例如,API 可能如下所示:

第一个参数:一个表达式,它告诉您搜索哪个属性 其他参数:id 或您要搜索的任何参数。

 var result1 = db.ObjectByID((SimpleObject t) => t.Id, 42, 77);
 var result2 = db.ObjectByID((SimpleObject t) => t.Id, myIDList);
 var result3 = db.ObjectByID((OtherObject t) => t.Name, "gamlerhart","db4o");

实现构建一个动态查询,如下所示:

var result = from SimpleObject t 
  where t.Id = 42 || t.Id==77 ... t.Id == N
  select t

由于所有内容都与 OR 相结合,因此可以直接在索引上进行评估。它不需要激活。示例-实现:

public static class ContainerExtensions{

public static IDb4oLinqQuery<TObjectType> ObjectByID<TObjectType, TIdType>(this IObjectContainer db,
Expression<Func<TObjectType, TIdType>> idPath,
params TIdType[] ids)
{
  if(0==ids.Length)
  {
       return db.Cast<TObjectType>().Where(o=>false);
  }
  var orCondition = BuildOrChain(ids, idPath);
  var whereClause = Expression.Lambda(orCondition, idPath.Parameters.ToArray());
  return db.Cast<TObjectType>().Where((Expression<Func<TObjectType, bool>>) whereClause);
}

private static BinaryExpression BuildOrChain<TIdType, TObjectType>(TIdType[] ids,     Expression<Func<TObjectType, TIdType>> idPath)
{
  var body = idPath.Body;
  var currentExpression = Expression.Equal(body, Expression.Constant(ids.First()));
  foreach (var id in ids.Skip(1))
  {
    currentExpression = Expression.OrElse(currentExpression, Expression.Equal(body,     Expression.Constant(id)));
  }
return currentExpression;
    }

}

【讨论】:

    【解决方案2】:

    如果您尝试使用 LINQ 运行此查询,它将在未优化的情况下运行(这意味着 db4o 将检索所有 SimpleObject 类型的对象并将其余的委托给 LINQ to objects)

    最好的方法是运行 n 个查询(因为 id 字段已编入索引,所以每个查询都应该运行得很快)并按照“Mark Hall”的建议聚合结果。

    您甚至可以为此使用 LINQ(类似)

    IList<SimpleObject> objs = new List<SimpleObject>();
    foreach(var tbf in ids)
    {
         var result = from SimpleObject o in db()
                   where o.Id = tbf
                      select o;
    
         if (result.Count == 1)
         {
            objs.Add(result[0]);
         }
    }
    

    最好的

    【讨论】:

      【解决方案3】:

      我不是这方面的专家,在 DB4O 论坛上发帖可能会很好,但我想我有一个解决方案。它涉及不使用 LINQ 和使用 SODA。

      这就是我所做的。我创建了一个快速项目,根据您的帖子定义使用 30000 SimpleObject 填充数据库。然后我写了一个查询来从数据库中获取所有的 SimpleObjects:

      var simpleObjects = db.Query<SimpleObject>(typeof(SimpleObject));
      

      当我用秒表包裹它时,运行大约需要 740 毫秒。然后,我使用您的代码搜索 0 到 2999 之间的 100 个随机数。响应为 772 毫秒,因此基于该数字,我假设它将所有对象从数据库中拉出。我不确定如何验证这一点,但后来我想我用性能证明了这一点。

      然后我往下走。据我了解,DB4O 团队的 LINQ 提供者只是将其转换为 SODA。因此,我想我会编写一个 SODA 查询来测试,我发现对属性使用 SODA 对性能不利,因为它需要 19902 毫秒才能执行。代码如下:

      private SimpleObject[] GetSimpleObjectUsingSodaAgainstAProperty(int[] matchingIds, IObjectContainer db)
      {
          SimpleObject[] returnValue = new SimpleObject[matchingIds.Length];
      
          for (int counter = 0; counter < matchingIds.Length; counter++)
          {
              var query = db.Query();
              query.Constrain(typeof(SimpleObject));
              query.Descend("Id").Constrain(matchingIds[counter]);
              IObjectSet queryResult = query.Execute();
              if (queryResult.Count == 1)
                  returnValue[counter] = (SimpleObject)queryResult[0];
          }
      
          return returnValue;
      }
      

      考虑到为什么会如此糟糕,我决定不使用自动实现的属性并自己定义它,因为属性实际上是方法而不是值:

      public class SimpleObject
      {
          private int _id;
      
          public int Id { 
              get
              { return _id; }
              set
              { _id = value; }
          }
      }
      

      然后我重写了查询以使用 _id 私有字段而不是属性。在大约 91 毫秒时,性能要好得多。这是代码:

      private SimpleObject[] GetSimpleObjectUsingSodaAgainstAField(int[] matchingIds, IObjectContainer db)
      {
          SimpleObject[] returnValue = new SimpleObject[matchingIds.Length];
      
          for (int counter = 0; counter < matchingIds.Length; counter++)
          {
              var query = db.Query();
              query.Constrain(typeof(SimpleObject));
              query.Descend("_id").Constrain(matchingIds[counter]);
              IObjectSet queryResult = query.Execute();
              if (queryResult.Count == 1)
                  returnValue[counter] = (SimpleObject)queryResult[0];
          }
      
          return returnValue;
      }
      

      为了确保这不是侥幸,我运行了几次测试并收到了类似的结果。然后我又添加了 60,000 条记录,总共 90,000 条,这就是性能差异:

      GetAll: 2450 ms
      GetWithOriginalCode: 2694 ms
      GetWithSODAandProperty: 75373 ms
      GetWithSODAandField: 77 ms
      

      希望对您有所帮助。我知道它并不能真正解释原因,但这可能有助于解决方法。此外,SODA 字段查询的代码也不难包装成更通用的代码。

      【讨论】:

        【解决方案4】:

        我在 db4o LINQ 方面做得不多。但是您可以使用 DiagnosticToConsole(或 ToTrace)并将其添加到 IConfiguration.Diagnostic().AddListener。这将显示查询是否已优化。

        你没有提供很多细节,但是 SimpleObject 上的 Id 属性是否被索引?

        打开诊断后,您可以尝试这样的查询...

        from SimpleObject simple in db
        where matchingIds.Contains(simple.Id)
        select simple
        

        看看这是否会给你一个不同的查询计划。

        【讨论】:

        • Id 属性已编入索引,是的。我会尝试打开诊断。感谢您的帮助。
        猜你喜欢
        • 1970-01-01
        • 2014-06-03
        • 1970-01-01
        • 1970-01-01
        • 2017-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-14
        相关资源
        最近更新 更多