12. SODA Evaluations


In the SODA API chapter we already mentioned Evaluations as a means of providing user-defined custom constraints and as a means to run any arbitrary code in a SODA query. Let's have a closer look.


    12.1. Evaluation API


    The evaluation API consists of two interfaces, Evaluation and Candidate . Evaluation implementations are implemented by the user and injected into a query. During a query, they will be called from db4o with a candidate instance in order to decide whether to include it into the current (sub-)result.


    The Evaluation interface contains a single method only:

    public void evaluate(Candidate candidate);


    This will be called by db4o to check whether the object encapsulated by this candidate should be included into the current candidate set.


    The Candidate interface provides three methods:

    public Object getObject();
    public void include(boolean flag);
    public ObjectContainer objectContainer();


    An Evaluation implementation may call getObject() to retrieve the actual object instance to be evaluated, it may call include() to instruct db4o whether or not to include this object in the current candidate set, and finally it may access the current database directly by calling objectContainer().



    12.2. Example


    For a simple example, let's go back to our Pilot/Car implementation from the Collections chapter. Back then, we kept a history of SensorReadout instances in a List member inside the car. Now imagine that we wanted to retrieve all cars that have assembled an even number of history entries. A quite contrived and seemingly trivial example, however, it gets us into trouble: Collections are transparent to the query API, it just 'looks through' them at their respective members.


    So how can we get this done? Let's implement an Evaluation that expects the objects passed in to be instances of type Car and checks their history size.

    using Db4objects.Db4o.Query;
    using Db4objects.Db4o.Tutorial.F1.Chapter3;
    namespace Db4objects.Db4o.Tutorial.F1.Chapter6
    {    
        public class EvenHistoryEvaluation : IEvaluation
        {
            public void Evaluate(ICandidate candidate)
            {
                Car car=(Car)candidate.GetObject();
                candidate.Include(car.History.Count % 2 == 0);
            }
        }
    }


    To test it, let's add two cars with history sizes of one, respectively two:

    // storeCars
    Pilot pilot1 = new Pilot("Michael Schumacher", 100);
    Car car1 = new Car("Ferrari");
    car1.Pilot = pilot1;
    car1.Snapshot();
    db.Store(car1);
    Pilot pilot2 = new Pilot("Rubens Barrichello", 99);
    Car car2 = new Car("BMW");
    car2.Pilot = pilot2;
    car2.Snapshot();
    car2.Snapshot();
    db.Store(car2);


    and run our evaluation against them:

    // queryWithEvaluation
    IQuery query = db.Query();
    query.Constrain(typeof (Car));
    query.Constrain(new EvenHistoryEvaluation());
    IObjectSet result = query.Execute();
    Util.ListResult(result);



    12.3. Drawbacks


    While evaluations offer you another degree of freedom for assembling queries, they come at a certain cost: As you may already have noticed from the example, evaluations work on the fully instantiated objects, while 'normal' queries peek into the database file directly. So there's a certain performance penalty for the object instantiation, which is wasted if the object is not included into the candidate set.


    Another restriction is that, while 'normal' queries can bypass encapsulation and access candidates' private members directly, evaluations are bound to use their external API, just as in the language itself.




    12.4. Conclusion


    With the introduction of evaluations we finally completed our query toolbox. Evaluations provide a simple way of assemble arbitrary custom query building blocks, however, they come at a price.


    12.5. Full source


    using System.IO;
    using Db4objects.Db4o.Query;
    using Db4objects.Db4o.Tutorial.F1.Chapter3;
    namespace Db4objects.Db4o.Tutorial.F1.Chapter6
    {
        public class EvaluationExample : Util
        {
            public static void Main(string[] args)
            {
                File.Delete(Util.YapFileName);
                IObjectContainer db = Db4oFactory.OpenFile(Util.YapFileName);
                try
                {
                    StoreCars(db);
                    QueryWithEvaluation(db);
                }
                finally
                {
                    db.Close();
                }
            }
            public static void StoreCars(IObjectContainer db)
            {
                Pilot pilot1 = new Pilot("Michael Schumacher", 100);
                Car car1 = new Car("Ferrari");
                car1.Pilot = pilot1;
                car1.Snapshot();
                db.Store(car1);
                Pilot pilot2 = new Pilot("Rubens Barrichello", 99);
                Car car2 = new Car("BMW");
                car2.Pilot = pilot2;
                car2.Snapshot();
                car2.Snapshot();
                db.Store(car2);
            }
            public static void QueryWithEvaluation(IObjectContainer db)
            {
                IQuery query = db.Query();
                query.Constrain(typeof (Car));
                query.Constrain(new EvenHistoryEvaluation());
                IObjectSet result = query.Execute();
                Util.ListResult(result);
            }
        }
    }




    www.db4o.com