Wednesday, February 17, 2010

Advanced PLINQO Future Queries, Part 2

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 2: Pipeline Loader Patter

There are many times when an application needs to process a series of independent actions, each requiring its own set of database objects. If using LINQ to SQL (prior to PLINQO), this would mean that you need to either hard code the loading of all the necessary data in one place, or allow each action to load its own data in succession; thus leaving you with the options of bad abstraction, or lack of optimization; neither of which are very desirable.

Using the PLINQO Future Queries in conjunction with some simple interfaces, you can create a pipeline system that allows your actions to share one single data loading session. As usual, I feel that code narrates this best: 

Use Case

[Test]
public void Test()
{
   var actions = new List<IPipelineLoader>
   {
       new CaseAction { CaseId = "A", Title = "First New Title" },
       new CaseAction { CaseId = "A", Title = "Second New Title" },
       new UserAction { UserId = 1, Title = "Senior Tester" },
       new UserAction { UserId = 2, Title = "Junior Tester" }
   };

   using (var db = new DemoDataContext())
   {
       actions.ForEach(a => a.Load(db));
       db.ExecuteFutureQueries();
       actions.ForEach(a => a.Process());
       db.SubmitChanges();
   }
}

Interface

public interface IPipelineLoader
{
   void Load(DemoDataContext db);
   void Process();
}

Implementation

public class CaseAction : IPipelineLoader
{
   private FutureValue<Case> _futureCase;

   public string CaseId { get; set; }
   public string Title { get; set; }

   public void Load(DemoDataContext db)
   {
       _futureCase = db.Case.ById(CaseId).FutureFirstOrDefault();
   }

   public void Process()
   {
       _futureCase.Value.Title = Title;
   }
}

public class UserAction : IPipelineLoader
{
   private FutureValue<User> _futureUser;

   public int UserId { get; set; }
   public string Title { get; set; }

   public void Load(DemoDataContext db)
   {
       _futureUser = db.User.ById(UserId).FutureFirstOrDefault();
   }

   public void Process()
   {
       _futureUser.Value.Title = Title;
   }
}

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

Real Time Web Analytics