Wednesday, February 10, 2010

Advanced PLINQO Future Queries, Part 1

The immediate uses of PLINQO Future Queries are readily apparent: Do you have multiple queries that need to be executed back to back? Add future to the back of them and save yourself a few database trips. What might not be quite as easy to spot, are those places where using a more advanced design pattern can really enhance your application's performance. Here is an example of one of those patters.

Part 1: Entity Loaders

When retrieving entities from the database you can not always use DataLoadOptions to populate the complete object model in a single trip to the database. Having to make multiple trips to populate one type of entity can be costly, and this can be compounded by the need to populate a series of different entities at the same time.

One solution is to create a loader class for your entities that use future queries to get their data, as this is a very reliable means with which to ensure that no extra unnecessary database calls are execute. Also, by creating an interface for these loader classes, you can use a generic piece of code to load any number of entities of any number of data types.

Use Cases

[Test]
public static Case Test(int id)
{
    var loader = new CaseTagLoader(id);
    loader.Load();
    return loader.Entity;
}

[Test]
public static List<Case> Test(int[] ids)
{
    var loaders = new List<CaseTagLoader>();
    foreach (var id in ids)
        loaders.Add(new CaseTagLoader(id));

    using (var db = new BlogDataContext())
    {
        loaders.ForEach(l => l.Load(db));
        db.ExecuteFutureQueries();
    }

    return loaders.Select(l => l.Entity).ToList();
}

Interface

public interface ILoader<T>
{
    void Load();
    void Load(BlogDataContext db);
    T Entity { get; }
    bool IsLoaded { get; }
}

Implementation

public class CaseTagLoader : ILoader<Case>
{
    private readonly int _caseId;
    private FutureValue<Case> _futureCase;
    private IEnumerable<CaseTag> _caseTags;
    private IEnumerable<Tag> _tags;
    private Case _case;

    public Case Entity
    {
        get
        {
            if (!IsLoaded)
                return null;

            if (_case == null)
                CreateCase();

            return _case;
        }
    }

    public bool IsLoaded { get; private set; }

    public CaseTagLoader(int caseId)
    {
        _caseId = caseId;
        IsLoaded = false;
    }

    public void Load()
    {
        if (IsLoaded)
            return;

        using (var db = new BlogDataContext())
        {
            db.ObjectTrackingEnabled = false;
            Load(db);
            db.ExecuteFutureQueries();
        }
    }

    public void Load(BlogDataContext db)
    {
        if (IsLoaded)
            return;

        _futureCase = db.Case.ById(_caseId).FutureFirstOrDefault();
        var caseTagQuery = db.CaseTag.ByCaseId(_caseId);
        _caseTags = caseTagQuery.Future();
        _tags = db.Tag
            .Join(caseTagQuery, t => t.Id, ct => ct.TagId, (t, ct) => t)
            .Future();

        IsLoaded = true;
    }

    private void CreateCase()
    {
        _case = _futureCase.Value;
        _case.CaseTags.AddRange(_caseTags);

        foreach (var caseTag in _caseTags)
            caseTag.Tag = _tags.First(t => t.Id == caseTag.TagId);
    }
}

No comments:

Post a Comment

Real Time Web Analytics