NOTE: for version 1.4 or later, the query builder batch handling has been modified. This change is reflected in the documentation on this page.

Query Execution

Query execution is straightforward. To execute queries, ask the DataConfiguration class to build an IDataContext instance to use. For example:

var myConnection = "someConnectionString"; 
var context = DataConfiguration.BuildContext(myConnection);

// Build a simple query using the fluent interface, then complete the query:
var queryCommand = DataConfiguration.Default.ForEntity<Customer>()
    .BuildSelect()
    .Where(c => c.CustomerId == 123)
    .ToCommand();

// List of customers as a result (in this example the query is highly selective
// so you can opt to use "FirstOrDefault()"...
var results = context.Query<Customer>(queryCommand);


This will execute the query as built. This approach may seem like several steps to achieve a simple goal, but each step exposes a building block you can use independently based on your use case. For example, you can have conditions at run-time that vary how your query is built by passing around the IQueryBuilder instance as you see fit. The resulting QueryCommand instance can be used alone or become part of a larger query batch, to consolidate server round trips.

Great...but that seems like a lot of typing for simple use cases. You can also do as follows:

var context = DataConfiguration.CreateContext(myConnection);

var parameter = new SqlParameter("@id", 123);
var results = context.Query<Customer>("select [LastName], ... from [Customers] where [CustomerId] = @id", parameter);

The selected columns will be matched to property names by convention (case sensitive), unless a model is used to define an explicit mapping.

Arguably the "shortcut" method above isn't really any shorter when you consider code completion in modern IDE's, but it is simple and to the point. Also important to consider, the fluent approach (not stringy) allows for other IDE friendliness such as automated refactoring.

Batching

An instance of IDataContext provides an API to send queries to the underlying RDBMS. After exploring this API, you will realize this API executes one query per round trip. One of the key facets of Teaq is to enable batching of queries. When designing for batching, consider separation of read batches from write batches. Treating reading of data differently than writing data has been rationalized in many modern architectures and Teaq works sympathetically with this idea.

The following code example shows the build up of select statements using a batch, and then execution of the batch by reading the results:

using (var context = DataConfiguration.BuildBatchReaderContext(myConnection))
{
    // Build your queries to be batch aware (must do).
    // These queries can be used in isolation, but are now part of the batch:
    DataConfiguration.Default
        .ForEntity<Customer>()
        .BuildSelect(top: 1)
        .Where(c => c.CustomerId == 1)
        .AddToBatch(context.QueryBatch);

    DataConfiguration.Default
        .ForEntity<Address>()
        .BuildSelect()
        .Where(a => a.CustomerId == 1)
        .AddToBatch(context.QueryBatch);

    // System.Linq in use to filter the first...can also issue a Top via the fluent API:
    var customer = context.ReadEntitySet<CustomerWithAddresses>().First();
    if (context.NextReaderResult())
    {
        customer.Addresses = context.ReadEntitySet<Address>();
    }
}

In this case, the command is finalized using the "AddToBatch" method, and the batch being built is passed as an argument. Under the covers, the framework ensures each query gets unique parameter names and the batch of statements sent to the server falls within the implementation limit of SQL Server.

You will notice an optional argument, "canSplitBatch", which defaults to true. Unless you are doing some creative query batch creation, you won't care if nor when a batch of statements is split. The framework will do a best effort to ensure the maximum number of statements in a query batch is not exceeded.

NOTE: Instances of IBatchReaderContext and IBatchWritedContext contain state and are disposable. Instances of IDataContext are stateless, thread-safe, and are not disposable.



Last edited May 30, 2016 at 12:58 AM by bkjuice, version 43