2009-11-06 15 views
3

J'ai emmagasinés 30.000 SimpleObjects dans ma base de données:requête db4o: trouver tous les objets avec ID = {} quoi que ce soit dans le tableau

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

Je veux exécuter une requête sur db4o qui trouve tous les SimpleObjects avec l'un des ID spécifié:

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(); 
} 

Comment écrire cette requête pour que DB4O n'active pas tous les 30 000 objets?

+0

J'ai remarqué que vous avez posté sur les forums db4o: http://developer.db4o.com/forums/57635/ShowThread.aspx. J'ai ajouté un commentaire sur la méthode qu'ils ont recommandée. –

Répondre

2

Si vous essayez d'exécuter cette requête en utilisant LINQ il courra unoptimized (cela signifie que db4o allez récupérer tous les objets de type SimpleObject et déléguer le reste à LINQ aux objets)

La meilleure approche serait Pour exécuter n requêtes (puisque le champ id est indexé, chaque requête devrait s'exécuter rapidement) et agréger les résultats comme suggéré par "Mark Hall".

Vous pouvez même utiliser LINQ pour cela (quelque chose comme)

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]); 
    } 
} 

Meilleur

2

Je n'ai pas beaucoup fait avec db4o LINQ. Mais vous pouvez utiliser DiagnosticToConsole (ou ToTrace) et l'ajouter à IConfiguration.Diagnostic(). AddListener. Cela vous montrera si la requête est optimisée.

Vous ne donnez pas beaucoup de détails, mais la propriété Id est-elle indexée sur SimpleObject?

Une fois le diagnostic sont activés, vous pouvez essayer la requête comme si ...

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

Voir si cela vous donne un plan de requête différent.

+0

La propriété Id est indexée, oui. Je vais essayer d'activer les diagnostics. Merci pour l'aide. –

3

Je ne suis pas un expert sur ce sujet, et il pourrait être bon de publier sur les forums DB4O à ce sujet, mais je pense que j'ai une solution. Cela implique de ne pas utiliser LINQ et d'utiliser SODA.

C'est ce que j'ai fait. J'ai créé un projet rapide qui remplit la base de données avec 30000 SimpleObject en fonction de la définition de votre message. J'ai alors écrit une requête pour saisir toutes les SimpleObjects de la base de données:

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

Quand je passai un StopWatch autour d'elle, cette course prend environ 740 millisecondes. J'ai ensuite utilisé votre code pour rechercher 100 nombres aléatoires compris entre 0 et 2999. La réponse était de 772 ms, donc en supposant que ce nombre tire tous les objets hors de la base de données. Je ne suis pas sûr de savoir comment vérifier cela, mais plus tard, je pense que je l'ai prouvé avec performance.

Je suis ensuite allé plus bas. D'après ce que je comprends, le fournisseur LINQ de l'équipe DB4O fait juste une traduction en SODA. Par conséquent j'ai pensé que j'écrirais une requête de SODA pour examiner, et ce que j'ai trouvé était qu'utiliser SODA contre une propriété est mauvais pour la représentation parce qu'il a fallu 19902 ms pour s'exécuter. Voici le code:

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; 
} 

penser Alors pourquoi cela serait si mauvais, j'ai décidé de ne pas utiliser une propriété implémentée automatiquement et définir mon auto parce que les propriétés sont en fait des méthodes et non des valeurs:

public class SimpleObject 
{ 
    private int _id; 

    public int Id { 
     get 
     { return _id; } 
     set 
     { _id = value; } 
    } 
} 
J'ai ensuite réécrit la requête pour utiliser le champ privé _id à la place de la propriété

La performance était bien meilleure à environ 91 ms. Voici ce code:

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; 
} 

Juste pour vous assurer qu'il est était pas un hasard, j'ai couru la course de test à plusieurs reprises et avons reçu des résultats similaires. J'ai ensuite ajouté un autre 60.000 dossiers pour un total de 90 000, et ce sont les différences de performance:

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

espoir qui aide. Je sais que cela n'explique pas vraiment pourquoi, mais cela pourrait aider avec le comment. De même, le code de la requête de zone SODA ne serait pas difficile à emballer pour être plus générique.

2

Vous pourriez 'construire' une requête LINQ dynamique. Par exemple l'API pourrait ressembler à ceci:

Premier paramètre: une expression qui indique sur quelle propriété vous recherchez Les autres paramètres: les id ou quoi que vous cherchiez.

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"); 

La mise en œuvre crée une requête dynamique comme ceci:

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

Puisque tout est combiné avec l'OR peut être évalué directement sur les indices. Il n'a pas besoin d'activation. Exemples d'implémentations:

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; 
    } 

}