3

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

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 个对象?

4

4 回答 4

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 字段查询的代码也不难包装成更通用的代码。

于 2009-11-12T17:08:21.993 回答
2

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

您没有提供很多详细信息,但是 SimpleObject 上的 Id 属性是否已编入索引?

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

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

看看这是否为您提供了不同的查询计划。

于 2009-11-06T23:39:43.887 回答
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]);
     }
}

最好的

于 2009-11-12T20:04:42.603 回答
2

您可以“构建”一个动态 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;
    }

}
于 2009-11-12T22:24:12.147 回答