ServiceStack.OrmLite

Follow @ServiceStack or join the Google+ Community for updates, or StackOverflow or the Customer Forums for support.

Fast, Simple, Typed ORM for .NET

OrmLite's goal is to provide a convenient, DRY, config-free, RDBMS-agnostic typed wrapper that retains a high affinity with SQL, exposing intuitive APIs that generate predictable SQL and maps cleanly to (DTO-friendly) disconnected POCO's. This approach makes easier to reason-about your data access making it obvious what SQL is getting executed at what time, whilst mitigating unexpected behavior, implicit N+1 queries and leaky data access prevalent in Heavy ORMs.

OrmLite was designed with a focus on the core objectives:

  • Provide a set of light-weight C# extension methods around .NET's impl-agnostic System.Data.* interfaces
  • Map a POCO class 1:1 to an RDBMS table, cleanly by conventions, without any attributes required.
  • Create/Drop DB Table schemas using nothing but POCO class definitions (IOTW a true code-first ORM)
  • Simplicity - typed, wrist friendly API for common data access patterns.
  • High performance - with support for indexes, text blobs, etc.
    • Amongst the fastest Micro ORMs for .NET.
  • Expressive power and flexibility - with access to IDbCommand and raw SQL
  • Cross platform - supports multiple dbs (currently: Sql Server, Sqlite, MySql, PostgreSQL, Firebird) running on both .NET and Mono platforms.

In OrmLite: 1 Class = 1 Table. There should be no surprising or hidden behaviour, the Typed API that produces the Querydoesn't impact how results get intuitively mapped to the returned POCO's which could be different to the POCO used to create the query, e.g. containing only a subset of the fields you want populated.

Any non-scalar properties (i.e. complex types) are text blobbed by default in a schema-less text field using any of the available pluggable text serializers. Support for POCO-friendly references is also available to provide a convenient API to persist related models. Effectively this allows you to create a table from any POCO type and it should persist as expected in a DB Table with columns for each of the classes 1st level public properties.

Download

Download on NuGet

8 flavours of OrmLite is on NuGet:

  • ServiceStack.OrmLite.SqlServer
  • ServiceStack.OrmLite.Sqlite
  • ServiceStack.OrmLite.PostgreSQL
  • ServiceStack.OrmLite.MySql
  • ServiceStack.OrmLite.MySqlConnector

These packages contain both .NET Framework v4.5 and .NET Standard 2.0 versions and supports both .NET Framework and .NET Core projects.

The .Core packages contains only .NET Standard 2.0 versions which can be used in ASP.NET Core Apps running on the .NET Framework:

  • ServiceStack.OrmLite.SqlServer.Core
  • ServiceStack.OrmLite.PostgreSQL.Core
  • ServiceStack.OrmLite.MySql.Core
  • ServiceStack.OrmLite.Sqlite.Core

Unofficial Releases maintained by ServiceStack Community:

  • ServiceStack.OrmLite.Oracle
  • ServiceStack.OrmLite.Firebird
  • ServiceStack.OrmLite.VistaDb

Latest v4+ on NuGet is a commercial release with free quotas.

Getting Started with OrmLite and AWS RDS

OrmLite has great support AWS's managed RDS Databases, follow these getting started guides to help getting up and running quickly:

  • PostgreSQL
  • Aurora
  • MySQL
  • MariaDB
  • SQL Server

Docs and Downloads for older v3 BSD releases

Copying

Since September 2013, ServiceStack source code is available under GNU Affero General Public License/FOSS License Exception, see license.txt in the source. Alternative commercial licensing is also available.

Contributing

Contributors need to approve the Contributor License Agreement before submitting pull-requests, see the Contributing wikifor more details.


Usage

First Install the NuGet package of the RDBMS you want to use, e.g:

PM> Install-Package ServiceStack.OrmLite.SqlServer

Each RDBMS includes a specialized dialect provider that encapsulated the differences in each RDBMS to support OrmLite features. The available Dialect Providers for each RDBMS is listed below:

SqlServerDialect.Provider      // SQL Server Version 2012+
SqliteDialect.Provider         // Sqlite
PostgreSqlDialect.Provider     // PostgreSQL 
MySqlDialect.Provider          // MySql
OracleDialect.Provider         // Oracle
FirebirdDialect.Provider       // Firebird
VistaDbDialect.Provider        // Vista DB

SQL Server Versions

There are a number of different SQL Server dialects to take advantage of features available in each version. For any version before SQL Server 2008 please use SqlServer2008Dialect.Provider, for any other version please use the best matching version:

SqlServer2008Dialect.Provider  // SQL Server <= 2008
SqlServer2012Dialect.Provider  // SQL Server 2012
SqlServer2014Dialect.Provider  // SQL Server 2014
SqlServer2016Dialect.Provider  // SQL Server 2016
SqlServer2017Dialect.Provider  // SQL Server 2017+

Configure OrmLiteConnectionFactory

To configure OrmLite you need the DB Connection string along the Dialect Provider of the RDBMS you're connecting to, e.g:

var dbFactory = new OrmLiteConnectionFactory(
    connectionString,  
    SqlServerDialect.Provider);

If you're using an IOC you can register OrmLiteConnectionFactory as a singleton, e.g:

container.Register(c => 
    new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider)); //InMemory Sqlite DB

You can then use the dbFactory to open ADO.NET DB Connections to your database. If connecting to an empty database you can use OrmLite's Create Table API's to create any tables you need based solely on the Schema definition of your POCO and populate it with any initial seed data you need, e.g:

using (var db = dbFactory.Open())
{
    if (db.CreateTableIfNotExists())
    {
        db.Insert(new Poco { Id = 1, Name = "Seed Data"});
    }

    var result = db.SingleById(1);
    result.PrintDump(); //= {Id: 1, Name:Seed Data}
}

OrmLite Interactive Tour

The best way to learn about OrmLite is to take the OrmLite Interactive Tour which lets you try out and explore different OrmLite features immediately from the comfort of your own browser without needing to install anything:

ServiceStack.OrmLite_第1张图片

Type Converters

You can customize, enhance or replace how OrmLite handles specific .NET Types with the new OrmLite Type Converters.

There's also support for SQL Server-specific SqlGeographySqlGeometry and SqlHierarchyId Types, See docs on SQL Server Types for instructions on how to enable them.

Async API Overview

A quick overview of Async API's can be seen in the class diagram below:

ServiceStack.OrmLite_第2张图片

Essentially most of OrmLite public API's now have async equivalents of the same name and an additional conventional *Async suffix. The Async API's also take an optional CancellationToken making converting sync code trivial, where you just need to add the Async suffix and await keyword, as can be seen in the Customer Orders UseCase upgrade to Async diff , e.g:

Sync:

db.Insert(new Employee { Id = 1, Name = "Employee 1" });
db.Save(product1, product2);
var customer = db.Single(new { customer.Email }); 

Async:

await db.InsertAsync(new Employee { Id = 1, Name = "Employee 1" });
await db.SaveAsync(product1, product2);
var customer = await db.SingleAsync(new { customer.Email });

Effectively the only Data Access API's that doesn't have async equivalents are *Lazy APIs yielding a lazy sequence (incompatible with async) as well as Schema DDL API's which are typically not used at runtime.

For a quick preview of many of the new Async API's in action, checkout ApiSqlServerTestsAsync.cs.

Async RDBMS Providers

Currently only a limited number of RDBMS providers offer async API's, which at this time are only:

  • SQL Server .NET 4.5+
  • PostgreSQL .NET 4.5+
  • MySQL .NET 4.5+

We've also added a .NET 4.5 build for Sqlite as it's a common use-case to swapout to use Sqlite's in-memory provider for faster tests. But as Sqlite doesn't provide async API's under-the-hood we fallback to pseudo async support where we just wrap its synchronous responses in Task results.

API Examples

OrmLite provides terse and intuitive typed API's for database querying from simple lambda expressions to more complex LINQ-Like Typed SQL Expressions which you can use to construct more complex queries. To give you a flavour here are some examples:

Querying with SELECT

int agesAgo = DateTime.Today.AddYears(-20).Year;
db.Select(x => x.Birthday >= new DateTime(agesAgo, 1, 1) 
                    && x.Birthday <= new DateTime(agesAgo, 12, 31));
db.Select(x => Sql.In(x.City, "London", "Madrid", "Berlin"));
db.Select(x => x.Earnings <= 50);
db.Select(x => x.Name.StartsWith("A"));
db.Select(x => x.Name.EndsWith("garzon"));
db.Select(x => x.Name.Contains("Benedict"));
db.Select(x => x.Rate == 10 && x.City == "Mexico");
db.Select(x => x.Rate.ToString() == "10"); //impicit string casting
db.Select(x => "Rate " + x.Rate == "Rate 10"); //server string concatenation

Convenient common usage data access patterns

OrmLite also includes a number of convenient API's providing DRY, typed data access for common queries:

Person person = db.SingleById(1);
Person person = db.Single(x => x.Age == 42);
var q = db.From()
          .Where(x => x.Age > 40)
          .Select(Sql.Count("*"));

int peopleOver40 = db.Scalar(q);
int peopleUnder50 = db.Count(x => x.Age < 50);
bool has42YearOlds = db.Exists(new { Age = 42 });
int maxAgeUnder50 = db.Scalar(x => Sql.Max(x.Age), x => x.Age < 50);
var q = db.From()
    .Where(x => x.Age == 27)
    .Select(x => x.LastName);
    
List results = db.Column(q);
var q = db.From()
          .Where(x => x.Age < 50)
          .Select(x => x.Age);

HashSet results = db.ColumnDistinct(q);
var q = db.From()
          .Where(x => x.Age < 50)
          .Select(x => new { x.Id, x.LastName });

Dictionary results = db.Dictionary(q);
var q = db.From()
          .Where(x => x.Age < 50)
          .Select(x => new { x.Age, x.LastName });

Dictionary> results = db.Lookup(q);

INSERT, UPDATE and DELETEs

To see the behaviour of the different APIs, all examples uses this simple model

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int? Age { get; set; }
}

UPDATE

In its most simple form, updating any model without any filters will update every field, except the Id which is used to filter the update to this specific record:

db.Update(new Person { Id = 1, FirstName = "Jimi", LastName = "Hendrix", Age = 27});

If you supply your own where expression, it updates every field (inc. Id) but uses your filter instead:

db.Update(new Person { Id = 1, FirstName = "JJ" }, p => p.LastName == "Hendrix");

One way to limit the fields which gets updated is to use an Anonymous Type:

db.Update(new { FirstName = "JJ" }, p => p.LastName == "Hendrix");

Or by using UpdateNonDefaults which only updates the non-default values in your model using the filter specified:

db.UpdateNonDefaults(new Person { FirstName = "JJ" }, p => p.LastName == "Hendrix");

UpdateOnly

As updating a partial row is a common use-case in Db's, we've added a number of methods for just this purpose, named UpdateOnly.

The lambda syntax lets you update only the fields listed in property initializers, e.g:

db.UpdateOnly(() => new Person { FirstName = "JJ" });

The second argument lets you specify a filter for updates:

db.UpdateOnly(() => new Person { FirstName = "JJ" }, where: p => p.LastName == "Hendrix");

Alternatively you can pass in a POCO directly, in which case the first expression in an UpdateOnly statement is used to specify which fields should be updated:

db.UpdateOnly(new Person { FirstName = "JJ" }, onlyFields: p => p.FirstName);

db.UpdateOnly(new Person { FirstName = "JJ", Age = 12 }, 
    onlyFields: p => new { p.FirstName, p.Age });

db.UpdateOnly(new Person { FirstName = "JJ", Age = 12 }, 
    onlyFields: p => new[] { "Name", "Age" });

When present, the second expression is used as the where filter:

db.UpdateOnly(new Person { FirstName = "JJ" }, 
    onlyFields: p => p.FirstName, 
    where: p => p.LastName == "Hendrix");

Instead of using the expression filters above you can choose to use an SqlExpression builder which provides more flexibility when you want to programatically construct the update statement:

var q = db.From()
    .Update(p => p.FirstName);

db.UpdateOnly(new Person { FirstName = "JJ", LastName = "Hendo" }, onlyFields: q);

Using an Object Dictionary:

var updateFields = new Dictionary {
    [nameof(Person.FirstName)] = "JJ",
};

db.UpdateOnly(updateFields, p => p.LastName == "Hendrix");

Using a typed SQL Expression:

var q = db.From()
    .Where(x => x.FirstName == "Jimi")
    .Update(p => p.FirstName);
          
db.UpdateOnly(new Person { FirstName = "JJ" }, onlyFields: q);

Updating existing values

The UpdateAdd API provides several Typed API's for updating existing values:

//Increase everyone's Score by 3 points
db.UpdateAdd(() => new Person { Score = 3 }); 

//Remove 5 points from Jackson Score
db.UpdateAdd(() => new Person { Score = -5 }, where: x => x.LastName == "Jackson");

//Graduate everyone and increase everyone's Score by 2 points 
db.UpdateAdd(() => new Person { Points = 2, Graduated = true });

//Add 10 points to Michael's score
var q = db.From()
    .Where(x => x.FirstName == "Michael");
db.UpdateAdd(() => new Person { Points = 10 }, q);

Note: Any non-numeric values in an UpdateAdd statement (e.g. strings) are replaced as normal.

INSERT

Insert's are pretty straight forward since in most cases you want to insert every field:

db.Insert(new Person { Id = 1, FirstName = "Jimi", LastName = "Hendrix", Age = 27 });

Partial Inserts

You can use InsertOnly for the rare cases you don't want to insert every field

db.InsertOnly(() => new Person { FirstName = "Amy" });

Alternative API using an SqlExpression

var q = db.From()
    .Insert(p => new { p.FirstName });

db.InsertOnly(new Person { FirstName = "Amy" }, onlyFields: q)

DELETE

Like updates for DELETE's we also provide APIs that take a where Expression:

db.Delete(p => p.Age == 27);

Or an SqlExpression:

var q = db.From()
    .Where(p => p.Age == 27);

db.Delete(q);

As well as un-typed, string-based expressions:

db.Delete(where: "Age = @age", new { age = 27 });

Delete from Table JOIN

Using a SqlExpression to delete rows by querying from a joined table:

var q = db.From()
    .Join((x, y) => x.Id == y.PersonId)
    .Where(x => x.Id == 2);

db.Delete(q);

Not supported in MySql

API Overview

The API is minimal, providing basic shortcuts for the primitive SQL statements:

ServiceStack.OrmLite_第3张图片

OrmLite makes available most of its functionality via extension methods to add enhancments over ADO.NET's IDbConnection, providing a Typed RDBMS-agnostic API that transparently handles differences in each supported RDBMS provider.

Create Tables Schemas

OrmLite is able to CREATEDROP and ALTER RDBMS Tables from your code-first Data Models with rich annotations for controlling how the underlying RDBMS Tables are constructed.

The Example below utilizes several annotations to customize the definition and behavior of RDBMS tables based on a POCOspublic properties:

public class Player
{
    public int Id { get; set; }                     // 'Id' is PrimaryKey by convention

    [Required]
    public string FirstName { get; set; }           // Creates NOT NULL Column

    [Alias("Surname")]                              // Maps to [Surname] RDBMS column
    public string LastName { get; set; }

    [Index(Unique = true)]                          // Creates Unique Index
    public string Email { get; set; }

    public List PhoneNumbers { get; set; }   // Complex Types blobbed by default

    [Reference]
    public List GameItems { get; set; }   // 1:M Reference Type saved separately

    [Reference]
    public Profile Profile { get; set; }            // 1:1 Reference Type saved separately
    public int ProfileId { get; set; }              // 1:1 Self Ref Id on Parent Table

    [ForeignKey(typeof(Level), OnDelete="CASCADE")] // Creates ON DELETE CASCADE Constraint
    public Guid SavedLevelId { get; set; }          // Creates Foreign Key Reference

    public ulong RowVersion { get; set; }           // Optimistic Concurrency Updates
}

public class Phone                                  // Blobbed Type only
{
    public PhoneKind Kind { get; set; }
    public string Number { get; set; }
    public string Ext { get; set; }
}

public enum PhoneKind
{
    Home,
    Mobile,
    Work,
}

[Alias("PlayerProfile")]                            // Maps to [PlayerProfile] RDBMS Table
[CompositeIndex(nameof(Username), nameof(Region))]  // Creates Composite Index
public class Profile
{
    [AutoIncrement]                                 // Auto Insert Id assigned by RDBMS
    public int Id { get; set; }

    public PlayerRole Role { get; set; }            // Native support for Enums
    public Region Region { get; set; }
    public string Username { get; set; }
    public long HighScore { get; set; }

    [Default(1)]                                    // Created in RDBMS with DEFAULT (1)
    public long GamesPlayed { get; set; }

    [CheckConstraint("Energy BETWEEN 0 AND 100")]   // Creates RDBMS Check Constraint
    public short Energy { get; set; }

    public string ProfileUrl { get; set; }
    public Dictionary Meta { get; set; }
}

public enum PlayerRole                              // Enums saved as strings by default
{
    Leader,
    Player,
    NonPlayer,
}

[EnumAsInt]                                         // Enum Saved as int
public enum Region
{
    Africa = 1,
    Americas = 2,
    Asia = 3,
    Australasia = 4,
    Europe = 5,
}

public class GameItem
{
    [PrimaryKey]                                    // Specify field to use as Primary Key
    [StringLength(50)]                              // Creates VARCHAR COLUMN
    public string Name { get; set; }

    public int PlayerId { get; set; }               // Foreign Table Reference Id

    [StringLength(StringLengthAttribute.MaxText)]   // Creates "TEXT" RDBMS Column 
    public string Description { get; set; }

    [Default(OrmLiteVariables.SystemUtc)]           // Populated with UTC Date by RDBMS
    public DateTime DateAdded { get; set; }
}

public class Level
{
    public Guid Id { get; set; }                    // Unique Identifer/GUID Primary Key
    public byte[] Data { get; set; }                // Saved as BLOB/Binary where possible
}

We can drop the existing tables and re-create the above table definitions with:

using (var db = dbFactory.Open())
{
    if (db.TableExists())
        db.DeleteAll();                      // Delete ForeignKey data if exists

    //DROP and CREATE ForeignKey Tables in dependent order
    db.DropTable();
    db.DropTable();
    db.CreateTable();
    db.CreateTable();

    //DROP and CREATE tables without Foreign Keys in any order
    db.DropAndCreateTable();
    db.DropAndCreateTable();

    var savedLevel = new Level
    {
        Id = Guid.NewGuid(),
        Data = new byte[]{ 1, 2, 3, 4, 5 },
    };
    db.Insert(savedLevel);

    var player = new Player
    {
        Id = 1,
        FirstName = "North",
        LastName = "West",
        Email = "[email protected]",
        PhoneNumbers = new List
        {
            new Phone { Kind = PhoneKind.Mobile, Number = "123-555-5555"},
            new Phone { Kind = PhoneKind.Home,   Number = "555-555-5555", Ext = "123"},
        },
        GameItems = new List
        {
            new GameItem { Name = "WAND", Description = "Golden Wand of Odyssey"},
            new GameItem { Name = "STAFF", Description = "Staff of the Magi"},
        },
        Profile = new Profile
        {
            Username = "north",
            Role = PlayerRole.Leader,
            Region = Region.Australasia,
            HighScore = 100,
            GamesPlayed = 10,
            ProfileUrl = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50.jpg",
            Meta = new Dictionary
            {
                {"Quote", "I am gamer"}
            },
        },
        SavedLevelId = savedLevel.Id,
    };
    db.Save(player, references: true);
}

This will add a record in all the above tables with all the Reference data properties automatically populated which we can quickly see by selecting the inserted Player record and all its referenced data by using OrmLite's Load APIs, e.g:

var dbPlayer = db.LoadSingleById(player.Id);

dbPlayer.PrintDump();

Which uses the Dump Utils to quickly display the populated data to the console:

{
    Id: 1,
    FirstName: North,
    LastName: West,
    Email: [email protected],
    PhoneNumbers: 
    [
        {
            Kind: Mobile,
            Number: 123-555-5555
        },
        {
            Kind: Home,
            Number: 555-555-5555,
            Ext: 123
        }
    ],
    GameItems: 
    [
        {
            Name: WAND,
            PlayerId: 1,
            Description: Golden Wand of Odyssey,
            DateAdded: 2018-01-17T07:53:45-05:00
        },
        {
            Name: STAFF,
            PlayerId: 1,
            Description: Staff of the Magi,
            DateAdded: 2018-01-17T07:53:45-05:00
        }
    ],
    Profile: 
    {
        Id: 1,
        Role: Leader,
        Region: Australasia,
        Username: north,
        HighScore: 100,
        GamesPlayed: 10,
        Energy: 0,
        ProfileUrl: "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50.jpg",
        Meta: 
        {
            Quote: I am gamer
        }
    },
    ProfileId: 1,
    SavedLevelId: 7690dfa4d31949ab9bce628c34d1c549,
    RowVersion: 2
}

Feel free to continue expirementing with this Example Live on Gistlyn.

Select APIs

If your SQL doesn't start with a SELECT statement, it is assumed a WHERE clause is being provided, e.g:

var tracks = db.Select("Artist = @artist AND Album = @album",
    new { artist = "Nirvana", album = "Heart Shaped Box" });

Which is equivalent to:

var tracks = db.Select("SELECT * FROM track WHERE Artist = @artist AND Album = @album", 
    new { artist = "Nirvana", album = "Heart Shaped Box" });

Use Sql* APIs for when you want to query custom SQL that is not a SELECT statement, e.g:

var tracks = db.SqlList("EXEC GetArtistTracks @artist, @album",
    new { artist = "Nirvana", album = "Heart Shaped Box" });

Select returns multiple records:

List tracks = db.Select()

Single returns a single record:

Track track = db.Single(x => x.RefId == refId)

Dictionary returns a Dictionary made from the first two columns:

Dictionary trackIdNamesMap = db.Dictionary(
    db.From().Select(x => new { x.Id, x.Name }))

Dictionary trackIdNamesMap = db.Dictionary(
    "select Id, Name from Track")

Lookup returns an Dictionary> made from the first two columns:

Dictionary> albumTrackNames = db.Lookup(
    db.From().Select(x => new { x.AlbumId, x.Name }))

Dictionary> albumTrackNames = db.Lookup(
    "select AlbumId, Name from Track")

Column returns a List of first column values:

List trackNames = db.Column(db.From().Select(x => x.Name))

List trackNames = db.Column("select Name from Track")

HashSet returns a HashSet of distinct first column values:

HashSet uniqueTrackNames = db.ColumnDistinct(
    db.From().Select(x => x.Name))

HashSet uniqueTrackNames = db.ColumnDistinct("select Name from Track")

Scalar returns a single scalar value:

var trackCount = db.Scalar(db.From().Select(Sql.Count("*")))

var trackCount = db.Scalar("select count(*) from Track")

Anonymous types passed into Where are treated like an AND filter:

var track3 = db.Where(new { AlbumName = "Throwing Copper", TrackNo = 3 })

SingleById(s), SelectById(s), etc provide strong-typed convenience methods to fetch by a Table's Id primary key field.

var track = db.SingleById(1);
var tracks = db.SelectByIds(new[]{ 1,2,3 });

Parametrized IN Values

OrmLite also supports providing collection of values which is automatically split into multiple DB parameters to simplify executing parameterized SQL with multiple IN Values, e.g:

var ids = new[]{ 1, 2, 3};
var results = db.Select("Id in (@ids)", new { ids });

var names = new List{ "foo", "bar", "qux" };
var results = db.SqlList
("SELECT * FROM Table WHERE Name IN (@names)", new { names });

Lazy Queries

API's ending with Lazy yield an IEnumerable sequence letting you stream the results without having to map the entire resultset into a disconnected List of POCO's first, e.g:

var lazyQuery = db.SelectLazy("Age > @age", new { age = 40 });
// Iterate over a lazy sequence 
foreach (var person in lazyQuery) {
   //...  
}

Other examples

var topVIPs = db.WhereLazy(new { Age = 27 }).Where(p => IsVip(p)).Take(5)

Other Notes

  • All InsertUpdate, and Delete methods take multiple params, while InsertAllUpdateAll and DeleteAll take IEnumerables.
  • Save and SaveAll will Insert if no record with Id exists, otherwise it Updates.
  • Methods containing the word Each return an IEnumerable and are lazily loaded (i.e. non-buffered).

Features

Whilst OrmLite aims to provide a light-weight typed wrapper around SQL, it offers a number of convenient features that makes working with RDBMS's a clean and enjoyable experience:

Typed SqlExpression support for JOIN's

Starting with the most basic example you can simply specify the table you want to join with:

var q = db.From()
          .Join();

var dbCustomers = db.Select(q);

This query rougly maps to the following SQL:

SELECT Customer.* 
  FROM Customer 
       INNER JOIN 
       CustomerAddress ON (Customer.Id == CustomerAddress.CustomerId)

Just like before q is an instance of SqlExpression which is bounded to the base Customer type (and what any subsequent implicit API's apply to).

To better illustrate the above query, lets expand it to the equivalent explicit query:

SqlExpression q = db.From();
q.Join((cust,address) => cust.Id == address.CustomerId);

List dbCustomers = db.Select(q);

Reference Conventions

The above query implicitly joins together the Customer and CustomerAddress POCO's using the same {ParentType}Idproperty convention used in OrmLite's support for References, e.g:

class Customer {
    public int Id { get; set; }
    ...
}
class CustomerAddress {
    public int Id { get; set; }
    public int CustomerId { get; set; }  // Reference based on Property name convention
}

References based on matching alias names is also supported, e.g:

[Alias("LegacyCustomer")]
class Customer {
    public int Id { get; set; }
    ...
}
class CustomerAddress {
    public int Id { get; set; }

    [Alias("LegacyCustomerId")]             // Matches `LegacyCustomer` Alias
    public int RenamedCustomerId { get; set; }  // Reference based on Alias Convention
}

Self References

Self References are also supported for 1:1 relations where the Foreign Key can instead be on the parent table:

public class Customer
{
    ...
    public int CustomerAddressId { get; set; }

    [Reference]
    public CustomerAddress PrimaryAddress { get; set; }
}

Foreign Key and References Attributes

References that don't follow the above naming conventions can be declared explicitly using the [References] and [ForeignKey] attributes:

public class Customer
{
    [References(typeof(CustomerAddress))]
    public int PrimaryAddressId { get; set; }

    [Reference]
    public CustomerAddress PrimaryAddress { get; set; }
}

Reference Attributes take precedence over naming conventions

Multiple Self References

The example below shows a customer with multiple CustomerAddress references which are able to be matched with the {PropertyReference}Id naming convention, e.g:

public class Customer
{
    [AutoIncrement]
    public int Id { get; set; }
    public string Name { get; set; }

    [References(typeof(CustomerAddress))]
    public int? HomeAddressId { get; set; }

    [References(typeof(CustomerAddress))]
    public int? WorkAddressId { get; set; }

    [Reference]
    public CustomerAddress HomeAddress { get; set; }

    [Reference]
    public CustomerAddress WorkAddress { get; set; }
}

Once defined, it can be saved and loaded via OrmLite's normal Reference and Select API's, e.g:

var customer = new Customer
{
    Name = "The Customer",
    HomeAddress = new CustomerAddress {
        Address = "1 Home Street",
        Country = "US"
    },
    WorkAddress = new CustomerAddress {
        Address = "2 Work Road",
        Country = "UK"
    },
};

db.Save(customer, references:true);

var c = db.LoadSelect(x => x.Name == "The Customer");
c.WorkAddress.Address.Print(); // 2 Work Road

var ukAddress = db.Single(x => x.Country == "UK");
ukAddress.Address.Print();     // 2 Work Road

Implicit Reference Conventions are applied by default

The implicit relationship above allows you to use any of these equilvalent APIs to JOIN tables:

q.Join();
q.Join();
q.Join((cust,address) => cust.Id == address.CustomerId);

Selecting multiple columns across joined tables

The SelectMulti API lets you select from multiple joined tables into a typed tuple

var q = db.From()
    .Join()
    .Join()
    .Where(x => x.CreatedDate >= new DateTime(2016,01,01))
    .And(x => x.Country == "Australia");

var results = db.SelectMulti(q);

foreach (var tuple in results)
{
    Customer customer = tuple.Item1;
    CustomerAddress custAddress = tuple.Item2;
    Order custOrder = tuple.Item3;
}

Thanks to Micro ORM's lightweight abstractions over ADO.NET that maps to clean POCOs, we can also use OrmLite's embedded version of Dapper's QueryMultiple:

var q = db.From()
    .Join()
    .Join()
    .Select("*");

using (var multi = db.QueryMultiple(q.ToSelectStatement()))
{
    var results = multi.Read>(Tuple.Create).ToList();

    foreach (var tuple in results)
    {
        Customer customer = tuple.Item1;
        CustomerAddress custAddress = tuple.Item2;
        Order custOrder = tuple.Item3;
    }
}

Select data from multiple tables into a Custom POCO

Another implicit behaviour when selecting from a typed SqlExpression is that results are mapped to the Customer POCO. To change this default we just need to explicitly specify what POCO it should map to instead:

List customers = db.Select(
    db.From().Join());

Where FullCustomerInfo is any POCO that contains a combination of properties matching any of the joined tables in the query.

The above example is also equivalent to the shorthand db.Select() API:

var q = db.From()
          .Join();

var customers = db.Select(q);

Rules for how results are mapped is simply each property on FullCustomerInfo is mapped to the first matching property in any of the tables in the order they were added to the SqlExpression.

The mapping also includes a fallback for referencing fully-qualified names in the format: {TableName}{FieldName} allowing you to reference ambiguous fields, e.g:

  • CustomerId => "Customer"."Id"
  • OrderId => "Order"."Id"
  • CustomerName => "Customer"."Name"
  • OrderCost => "Order"."Cost"

Dynamic Result Sets

In addition to populating Typed POCOs, OrmLite has a number of flexible options for accessing dynamic resultsets with adhoc schemas:

C# 7 Value Tuples

The C# 7 Value Tuple support enables a terse, clean and typed API for accessing the Dynamic Result Sets returned when using a custom Select expression:

var query = db.From()
    .Join()
    .OrderBy(e => e.Id)
    .Select(
        (e, d) => new { e.Id, e.LastName, d.Name });
 
var results = db.Select<(int id, string lastName, string deptName)>(query);
 
var row = results[i];
$"row: ${row.id}, ${row.lastName}, ${row.deptName}".Print();

Full Custom SQL Example:

var results = db.SqlList<(int count, string min, string max, int sum)>(
    "SELECT COUNT(*), MIN(Word), MAX(Word), Sum(Total) FROM Table");

Partial Custom SQL Select Example:

var query = db.From
() .Select("COUNT(*), MIN(Word), MAX(Word), Sum(Total)"); var result = db.Single<(int count, string min, string max, int sum)>(query);

Same as above, but using Typed APIs:

var result = db.Single<(int count, string min, string max, int sum)>(
    db.From
() .Select(x => new { Count = Sql.Count("*"), Min = Sql.Min(x.Word), Max = Sql.Max(x.Word), Sum = Sql.Sum(x.Total) }));

There's also support for returning unstructured resultsets in List, e.g:

var results = db.Select>(db.From()
  .Select("COUNT(*), MIN(Id), MAX(Id)"));

results[0].PrintDump();

Output of objects in the returned List:

[
    10,
    1,
    10
]

You can also Select Dictionary to return a dictionary of column names mapped with their values, e.g:

var results = db.Select>(db.From()
  .Select("COUNT(*) Total, MIN(Id) MinId, MAX(Id) MaxId"));

results[0].PrintDump();

Output of objects in the returned Dictionary:

{
    Total: 10,
    MinId: 1,
    MaxId: 10
}

and can be used for API's returning a Single row result:

var result = db.Single>(db.From()
  .Select("COUNT(*) Total, MIN(Id) MinId, MAX(Id) MaxId"));

or use object to fetch an unknown Scalar value:

object result = db.Scalar(db.From().Select(x => x.Id)); 
  

Select data from multiple tables into Dynamic ResultSets

You can also select data from multiple tables into dynamic result sets which provide several Convenience APIs for accessing data from an unstructured queries.

Using dynamic:

var q = db.From()
    .Join()
    .Select((e, d) => new { e.FirstName, d.Name });
    
List results = db.Select(q);
foreach (dynamic result in results)
{
    string firstName = result.FirstName;
    string deptName = result.Name;
}

Dictionary of Objects:

List> rows = db.Select>(q);

List of Objects:

List> rows = db.Select>(q);

Custom Key/Value Dictionary:

Dictionary rows = db.Dictionary(q);

BelongsTo Attribute

The [BelongTo] attribute can be used for specifying how Custom POCO results are mapped when the resultset is ambiguous, e.g:

class A { 
    public int Id { get; set; }
}
class B {
    public int Id { get; set; }
    public int AId { get; set; }
}
class C {
    public int Id { get; set; }
    public int BId { get; set; }
}
class Combined {
    public int Id { get; set; }
    [BelongTo(typeof(B))]
    public int BId { get; set; }
}

var q = db.From()
    .Join()
    .LeftJoin();

var results = db.Select(q); //Combined.BId = B.Id

Advanced Example

Seeing how the SqlExpression is constructed, joined and mapped, we can take a look at a more advanced example to showcase more of the new API's available:

List rows = db.Select(  // Map results to FullCustomerInfo POCO
  db.From()                                       // Create typed Customer SqlExpression
    .LeftJoin()                            // Implicit left join with base table
    .Join((c,o) => c.Id == o.CustomerId)   // Explicit join and condition
    .Where(c => c.Name == "Customer 1")                     // Implicit condition on base table
    .And(o => o.Cost < 2)                            // Explicit condition on joined Table
    .Or((c,o) => c.Name == o.LineItem));    // Explicit condition with joined Tables

The comments next to each line document each Type of API used. Some of the new API's introduced in this example include:

  • Usage of LeftJoin for specifying a LEFT JOIN, RightJoin and FullJoin also available
  • Usage of And

(), to specify an AND condition on a Joined table
  • Usage of Or, to specify an OR condition against 2 joined tables
  • More code examples of References and Joined tables are available in:

    • LoadReferencesTests.cs
    • LoadReferencesJoinTests.cs

    Reference Support, POCO style

    OrmLite lets you Store and Load related entities in separate tables using [Reference] attributes in primary tables in conjunction with {Parent}Id property convention in child tables, e.g:

    public class Customer
    {
        [AutoIncrement]
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Reference] // Save in CustomerAddress table
        public CustomerAddress PrimaryAddress { get; set; }
    
        [Reference] // Save in Order table
        public List Orders { get; set; }
    }
    
    public class CustomerAddress
    {
        [AutoIncrement]
        public int Id { get; set; }
        public int CustomerId { get; set; } //`{Parent}Id` convention to refer to Customer
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
    }
    
    public class Order
    {
        [AutoIncrement]
        public int Id { get; set; }
        public int CustomerId { get; set; } //`{Parent}Id` convention to refer to Customer
        public string LineItem { get; set; }
        public int Qty { get; set; }
        public decimal Cost { get; set; }
    }

    With the above structure you can save a POCO and all its entity references with db.Save(T,references:true), e.g:

    var customer =  new Customer {
        Name = "Customer 1",
        PrimaryAddress = new CustomerAddress {
            AddressLine1 = "1 Australia Street",
            Country = "Australia"
        },
        Orders = new[] {
            new Order { LineItem = "Line 1", Qty = 1, Cost = 1.99m },
            new Order { LineItem = "Line 2", Qty = 2, Cost = 2.99m },
        }.ToList(),
    };
    
    db.Save(customer, references:true);

    This saves the root customer POCO in the Customer table, its related PrimaryAddress in the CustomerAddress table and its 2 Orders in the Order table.

    Querying POCO's with References

    The Load* API's are used to automatically load a POCO and all it's child references, e.g:

    var customer = db.LoadSingleById(customerId);

    Using Typed SqlExpressions:

    var customers = db.LoadSelect(x => x.Name == "Customer 1");

    More examples available in LoadReferencesTests.cs

    Unlike normal complex properties, references:

    • Doesn't persist as complex type blobs
    • Doesn't impact normal querying
    • Saves and loads references independently from itself
    • Are serializable with Text serializers (only populated are visible).
    • Loads related data only 1-reference-level deep

    Basically they provides a better story when dealing with referential data that doesn't impact the POCO's ability to be used as DTO's.

    Merge Disconnected POCO Result Sets

    The Merge extension method can stitch disconnected POCO collections together as per their relationships defined in OrmLite's POCO References.

    For example you can select a collection of Customers who've made an order with quantities of 10 or more and in a separate query select their filtered Orders and then merge the results of these 2 distinct queries together with:

    //Select Customers who've had orders with Quantities of 10 or more
    var q = db.From()
              .Join()
              .Where(o => o.Qty >= 10)
              .SelectDistinct();
    
    List customers = db.Select(q);
    
    //Select Orders with Quantities of 10 or more
    List orders = db.Select(o => o.Qty >= 10);
    
    customers.Merge(orders); // Merge disconnected Orders with their related Customers
    
    customers.PrintDump();   // Print merged customers and orders datasets

    Custom Load References

    You can selectively specifying which references you want to load using the include parameter, e.g:

    var customerWithAddress = db.LoadSingleById(customer.Id, include: new[] { "PrimaryAddress" });
    
    //Alternative
    var customerWithAddress = db.LoadSingleById(customer.Id, include: x => new { x.PrimaryAddress });

    Custom Select with JOIN

    You can specify SQL Aliases for ambiguous columns using anonymous properties, e.g:

    var q = db.From
    () .Join() .Select((a, b) => new { a, JoinId = b.Id, JoinName = b.Name });

    Which is roughly equivalent to:

    SELECT a.*, b.Id AS JoinId, b.Name AS JoinName
    

    Where it selects all columns from the primary Table as well as Id and Name columns from JoinedTable, returning them in the JoinId and JoinName custom aliases.

    Nested JOIN Table Expressions

    You can also query POCO References on JOIN tables, e.g:

    var q = db.From
    () .Join() .Join() .Where(x => !x.IsValid.HasValue && x.Join1.IsValid && x.Join1.Join2.Name == theName && x.Join1.Join2.IntValue == intValue) .GroupBy(x => x.Join1.Join2.IntValue) .Having(x => Sql.Max(x.Join1.Join2.IntValue) != 10) .Select(x => x.Join1.Join2.IntValue);

    JOIN aliases

    You can specify join aliases when joining same table multiple times together to differentiate from any ambiguous columns, e.g:

    var q = db.From()
        .LeftJoin((s,c) => s.SellerId == c.Id, db.JoinAlias("seller"))
        .LeftJoin((s,c) => s.BuyerId == c.Id, db.JoinAlias("buyer"))
        .Select((s,c) => new {
            s,
            BuyerFirstName = Sql.JoinAlias(c.FirstName, "buyer"),
            BuyerLastName = Sql.JoinAlias(c.LastName, "buyer"),
            SellerFirstName = Sql.JoinAlias(c.FirstName, "seller"),
            SellerLastName = Sql.JoinAlias(c.LastName, "seller"),
        });

    Unique Constraints

    In addition to creating an Index with unique constraints using [Index(Unique=true)] you can now use [Unique] to enforce a single column should only contain unique values or annotate the class with [UniqueConstraint] to specify a composite unique constraint, e.g:

    [UniqueConstraint(nameof(PartialUnique1), nameof(PartialUnique2), nameof(PartialUnique3))]
    public class UniqueTest
    {
        [AutoIncrement]
        public int Id { get; set; }
    
        [Unique]
        public string UniqueField { get; set; }
    
        public string PartialUnique1 { get; set; }
        public string PartialUnique2 { get; set; }
        public string PartialUnique3 { get; set; }
    }

    Auto populated Guid Ids

    Support for Auto populating Guid Primary Keys was also added in this release with the new [AutoId] attribute, e.g:

    public class Table
    {
        [AutoId]
        public Guid Id { get; set; }
    }

    In SQL Server it will populate Id primary key with newid(), in PostgreSQL it uses uuid_generate_v4() which requires installing the the uuid-ossp extension by running the SQL below on each PostgreSQL RDBMS it's used on:

    CREATE EXTENSION IF NOT EXISTS "uuid-ossp"
    

    For all other RDBMS's OrmLite will populate the Id with Guid.NewGuid(). In all RDBMS's it will populate the Id property on db.Insert() or db.Save() with the new value, e.g:

    var row = new Table { ... };
    db.Insert(row);
    row.Id //= Auto populated with new Guid

    SQL Server 2012 Sequences

    The [Sequence] attribute can be used as an alternative to [AutoIncrement] for inserting rows with an auto incrementing integer value populated by SQL Server, but instead of needing an IDENTITY column it can populate a normal INT column from a user-defined Sequence, e.g:

    public class SequenceTest
    {
        [Sequence("Seq_SequenceTest_Id"), ReturnOnInsert]
        public int Id { get; set; }
    
        public string Name { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
    
        [Sequence("Seq_Counter")]
        public int Counter { get; set; }
    }
    
    var user = new SequenceTest { Name = "me", Email = "[email protected]" };
    db.Insert(user);
    
    user.Id //= Populated by next value in "Seq_SequenceTest_Id" SQL Server Sequence

    The new [ReturnOnInsert] attribute tells OrmLite which columns to return the values of, in this case it returns the new Sequence value the row was inserted with. Sequences offer more flexibility than IDENTITY columns where you can use multiple sequences in a table or have the same sequence shared across multiple tables.

    When creating tables, OrmLite will also create any missing Sequences automatically so you can continue to have reproducible tests and consistent Startups states that's unreliant on external state. But it doesn't drop sequences when OrmLite drops the table as they could have other external dependents.

    To be able to use the new sequence support you'll need to use an SQL Server dialect greater than SQL Server 2012+, e.g:

    var dbFactory = new OrmLiteConnectionFactory(connString, SqlServer2012Dialect.Provider);

    SQL Server Table Hints

    Using the same JOIN Filter feature OrmLite also lets you add SQL Server Hints on JOIN Table expressions, e.g:

    var q = db.From()
        .Join((c, t) => c.CarId == t.CarId, SqlServerTableHint.ReadUncommitted);

    Which emits the appropriate SQL Server hints:

    SELECT "Car"."CarId", "CarType"."CarTypeName" 
    FROM "Car" INNER JOIN "CarType" WITH (READUNCOMMITTED) ON ("Car"."CarId" = "CarType"."CarId")

    Custom SqlExpression Filter

    The generated SQL from a Typed SqlExpression can also be customized using .WithSqlFilter(), e.g:

    var q = db.From
    () .Where(x => x.Age == 27) .WithSqlFilter(sql => sql + " option (recompile)"); var q = db.From
    () .Where(x => x.Age == 27) .WithSqlFilter(sql => sql + " WITH UPDLOCK"); var results = db.Select(q);

    Nested Typed Sub SqlExpressions

    The Sql.In() API supports nesting and combining of multiple Typed SQL Expressions together in a single SQL Query, e.g:

    var usaCustomerIds = db.From(c => c.Country == "USA").Select(c => c.Id);
    var usaCustomerOrders = db.Select(db.From()
        .Where(x => Sql.In(x.CustomerId, usaCustomerIds)));

    Optimistic Concurrency

    Optimistic concurrency can be added to any table by adding the ulong RowVersion { get; set; } property, e.g:

    public class Poco
    {
        ...
        public ulong RowVersion { get; set; }
    }

    RowVersion is implemented efficiently in all major RDBMS's, i.e:

    • Uses rowversion datatype in SqlServer
    • Uses PostgreSql's xmin system column (no column on table required)
    • Uses UPDATE triggers on MySql, Sqlite and Oracle whose lifetime is attached to Create/Drop tables APIs

    Despite their differing implementations each provider works the same way where the RowVersion property is populated when the record is selected and only updates the record if the RowVersion matches with what's in the database, e.g:

    var rowId = db.Insert(new Poco { Text = "Text" }, selectIdentity:true);
    
    var row = db.SingleById(rowId);
    row.Text += " Updated";
    db.Update(row); //success!
    
    row.Text += "Attempting to update stale record";
    
    //Can't update stale record
    Assert.Throws(() =>
        db.Update(row));
    
    //Can update latest version
    var updatedRow = db.SingleById(rowId);  // fresh version
    updatedRow.Text += "Update Success!";
    db.Update(updatedRow);
    
    updatedRow = db.SingleById(rowId);
    db.Delete(updatedRow);                        // can delete fresh version

    Optimistic concurrency is only verified on API's that update or delete an entire entity, i.e. it's not enforced in partial updates. There's also an Alternative API available for DELETE's:

    db.DeleteById(id:updatedRow.Id, rowversion:updatedRow.RowVersion)

    RowVersion Byte Array

    To improve reuse of OrmLite's Data Models in Dapper, OrmLite also supports byte[] RowVersion which lets you use OrmLite Data Models with byte[] RowVersion properties in Dapper queries.

    Conflict Resolution using commandFilter

    An optional Func commandFilter is available in all INSERT and UPDATE APIs to allow customization and inspection of the populated IDbCommand before it's run. This feature is utilized in the Conflict Resolution Extension methodswhere you can specify the conflict resolution strategy when a Primary Key or Unique constraint violation occurs:

    db.InsertAll(rows, dbCmd => dbCmd.OnConflictIgnore());
    
    //Equivalent to: 
    db.InsertAll(rows, dbCmd => dbCmd.OnConflict(ConflictResolution.Ignore));

    In this case it will ignore any conflicts that occurs and continue inserting the remaining rows in SQLite, MySql and PostgreSQL, whilst in SQL Server it's a NOOP.

    SQLite offers additional fine-grained behavior that can be specified for when a conflict occurs:

    • ROLLBACK
    • ABORT
    • FAIL
    • IGNORE
    • REPLACE

    Modify Custom Schema

    OrmLite provides Typed APIs for modifying Table Schemas that makes it easy to inspect the state of an RDBMS Table which can be used to determine what modifications you want on it, e.g:

    class Poco 
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Ssn { get; set; }
    }
    
    db.DropTable();
    db.TableExists(); //= false
    
    db.CreateTable(); 
    db.TableExists(); //= true
    
    db.ColumnExists(x => x.Ssn); //= true
    db.DropColumn(x => x.Ssn);
    db.ColumnExists(x => x.Ssn); //= false

    In a future version of your Table POCO you can use ColumnExists to detect which columns haven't been added yet, then use AddColumn to add it, e.g:

    class Poco 
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        [Default(0)]
        public int Age { get; set; }
    }
    
    if (!db.ColumnExists(x => x.Age)) //= false
        db.AddColumn(x => x.Age);
    
    db.ColumnExists(x => x.Age); //= true

    Modify Schema APIs

    Additional Modify Schema APIs available in OrmLite include:

    • AlterTable
    • AddColumn
    • AlterColumn
    • ChangeColumnName
    • DropColumn
    • AddForeignKey
    • DropForeignKey
    • CreateIndex
    • DropIndex

    Typed Sql.Cast() SQL Modifier

    The Sql.Cast() provides a cross-database abstraction for casting columns or expressions in SQL queries, e.g:

    db.Insert(new SqlTest { Value = 123.456 });
    
    var results = db.Select<(int id, string text)>(db.From()
        .Select(x => new {
            x.Id,
            text = Sql.Cast(x.Id, Sql.VARCHAR) + " : " + Sql.Cast(x.Value, Sql.VARCHAR) + " : " 
                 + Sql.Cast("1 + 2", Sql.VARCHAR) + " string"
        }));
    
    results[0].text //= 1 : 123.456 : 3 string

    Typed Column and Table APIs

    You can use the Column and Table() methods to resolve the quoted names of a Column or Table within SQL Fragments (taking into account any configured aliases or naming strategies).

    Usage Example of the new APIs inside a CustomJoin() expression used to join on a custom SELECT expression:

    q.CustomJoin($"LEFT JOIN (SELECT {q.Column(x => x.Id)} ...")
    q.CustomJoin($"LEFT JOIN (SELECT {q.Column(nameof(Job.Id))} ...")
    
    q.CustomJoin($"LEFT JOIN (SELECT {q.Column(x => x.Id, tablePrefix:true)} ...")
    //Equivalent to:
    q.CustomJoin($"LEFT JOIN (SELECT {q.Table()}.{q.Column(x => x.Id)} ...")
    
    q.Select($"{q.Column(x => x.Id)} as JobId, {q.Column(x => x.Id)} as TaskId")
    //Equivalent to:
    q.Select((j,t) => new { JobId = j.Id, TaskId = t.Id })

    DB Parameter API's

    To enable even finer-grained control of parameterized queries we've added new overloads that take a collection of IDbDataParameter's:

    List Select(string sql, IEnumerable sqlParams)
    T Single(string sql, IEnumerable sqlParams)
    T Scalar(string sql, IEnumerable sqlParams)
    List Column(string sql, IEnumerable sqlParams)
    IEnumerable ColumnLazy(string sql, IEnumerable sqlParams)
    HashSet ColumnDistinct(string sql, IEnumerable sqlParams)
    Dictionary> Lookup(string sql, IEnumerable sqlParams)
    List SqlList(string sql, IEnumerable sqlParams)
    List SqlColumn(string sql, IEnumerable sqlParams)
    T SqlScalar(string sql, IEnumerable sqlParams)

    Including Async equivalents for each of the above Sync API's.

    The new API's let you execute parameterized SQL with finer-grained control over the IDbDataParameter used, e.g:

    IDbDataParameter pAge = db.CreateParam("age", 40, dbType:DbType.Int16);
    db.Select("SELECT * FROM Person WHERE Age > @pAge", new[] { pAge });

    The new CreateParam() extension method above is a useful helper for creating custom IDbDataParameter's.

    Customize null values

    The new OrmLiteConfig.OnDbNullFilter lets you to replace DBNull values with a custom value, so you could convert all null strings to be populated with "NULL" using:

    OrmLiteConfig.OnDbNullFilter = fieldDef => 
        fieldDef.FieldType == typeof(string)
            ? "NULL"
            : null;

    Logging an Introspection

    One way to see what queries OrmLite generates is to enable a debug enabled logger, e.g:

    LogManager.LogFactory = new ConsoleLogFactory(debugEnabled:true);

    Where it will log the generated SQL and Params OrmLite executes to the Console.

    BeforeExecFilter and AfterExecFilter filters

    An alternative to debug logging which can easily get lost in the noisy stream of other debug messages is to use the BeforeExecFilter and AfterExecFilter filters where you can inspect executed commands with a custom lambda expression before and after each query is executed. So if one of your a queries are failing you can put a breakpoint in BeforeExecFilter to inspect the populated IDbCommand object before it's executed or use the .GetDebugString() extension method for an easy way to print the Generated SQL and DB Params to the Console:

    OrmLiteConfig.BeforeExecFilter = dbCmd => Console.WriteLine(dbCmd.GetDebugString());
    
    //OrmLiteConfig.AfterExecFilter = dbCmd => Console.WriteLine(dbCmd.GetDebugString());

    Exec, Result and String Filters

    OrmLite's core Exec filters makes it possible to inject your own behavior, tracing, profiling, etc.

    It's useful in situations like wanting to use SqlServer in production but use an in-memory Sqlite database in tests and being able to emulate any missing SQL Server Stored Procedures in code:

    public class MockStoredProcExecFilter : OrmLiteExecFilter
    {
        public override T Exec(IDbConnection dbConn, Func filter)
        {
            try
            {
                return base.Exec(dbConn, filter);
            }
            catch (Exception ex)
            {
                if (dbConn.GetLastSql() == "exec sp_name @firstName, @age")
                    return (T)(object)new Person { FirstName = "Mocked" };
                throw;
            }
        }
    }
    
    OrmLiteConfig.ExecFilter = new MockStoredProcExecFilter();
    
    using (var db = OpenDbConnection())
    {
        var person = db.SqlScalar("exec sp_name @firstName, @age",
            new { firstName = "aName", age = 1 });
    
        person.FirstName.Print(); //Mocked
    }

    CaptureSqlFilter

    Results filters makes it trivial to implement the CaptureSqlFilter which allows you to capture SQL Statements without running them. CaptureSqlFilter is just a simple Results Filter which can be used to quickly found out what SQL your DB calls generate by surrounding DB access in a using scope, e.g:

    using (var captured = new CaptureSqlFilter())
    using (var db = OpenDbConnection())
    {
        db.Where(new { Age = 27 });
    
        captured.SqlStatements[0].PrintDump();
    }

    Emits the Executed SQL along with any DB Parameters:

    {
        Sql: "SELECT ""Id"", ""FirstName"", ""LastName"", ""Age"" FROM ""Person"" WHERE ""Age"" = @Age",
        Parameters: 
        {
            Age: 27
        }
    }
    

    Replay Exec Filter

    Or if you want to do things like executing each operation multiple times, e.g:

    public class ReplayOrmLiteExecFilter : OrmLiteExecFilter
    {
        public int ReplayTimes { get; set; }
    
        public override T Exec(IDbConnection dbConn, Func filter)
        {
            var holdProvider = OrmLiteConfig.DialectProvider;
            var dbCmd = CreateCommand(dbConn);
            try
            {
                var ret = default(T);
                for (var i = 0; i < ReplayTimes; i++)
                {
                    ret = filter(dbCmd);
                }
                return ret;
            }
            finally
            {
                DisposeCommand(dbCmd);
                OrmLiteConfig.DialectProvider = holdProvider;
            }
        }
    }
    
    OrmLiteConfig.ExecFilter = new ReplayOrmLiteExecFilter { ReplayTimes = 3 };
    
    using (var db = OpenDbConnection())
    {
        db.DropAndCreateTable();
        db.Insert(new PocoTable { Name = "Multiplicity" });
    
        var rowsInserted = db.Count(x => x.Name == "Multiplicity"); //3
    }

    Mockable extension methods

    The Result Filters also lets you easily mock results and avoid hitting the database, typically useful in Unit Testing Services to mock OrmLite API's directly instead of using a repository, e.g:

    using (new OrmLiteResultsFilter {
        PrintSql = true,
        SingleResult = new Person { 
          Id = 1, FirstName = "Mocked", LastName = "Person", Age = 100 
        },
    })
    {
        db.Single(x => x.Age == 42).FirstName // Mocked
        db.Single(db.From().Where(x => x.Age == 42)).FirstName // Mocked
        db.Single(new { Age = 42 }).FirstName // Mocked
        db.Single("Age = @age", new { age = 42 }).FirstName // Mocked
    }

    More examples showing how to mock different API's including support for nesting available in MockAllApiTests.cs

    String Filter

    There's also a specific filter for strings available which allows you to apply custom sanitization on String fields, e.g. you can ensure all strings are right trimmed with:

    OrmLiteConfig.StringFilter = s => s.TrimEnd();
    
    db.Insert(new Poco { Name = "Value with trailing   " });
    db.Select().First().Name // "Value with trailing"

    Pluggable Complex Type Serializers

    Pluggable serialization lets you specify different serialization strategies of Complex Types for each available RDBMS provider, e.g:

    //ServiceStack's JSON and JSV Format
    SqliteDialect.Provider.StringSerializer = new JsvStringSerializer();       
    PostgreSqlDialect.Provider.StringSerializer = new JsonStringSerializer();
    //.NET's XML and JSON DataContract serializers
    SqlServerDialect.Provider.StringSerializer = new DataContractSerializer();
    MySqlDialect.Provider.StringSerializer = new JsonDataContractSerializer();
    //.NET XmlSerializer
    OracleDialect.Provider.StringSerializer = new XmlSerializableSerializer();

    You can also provide a custom serialization strategy by implementing IStringSerializer.

    By default all dialects use the existing JsvStringSerializer, except for PostgreSQL which due to its built-in support for JSON, uses the JSON format by default.

    Global Insert / Update Filters

    Similar to interceptors in some heavy ORM's, Insert and Update filters get fired just before any INSERT or UPDATE operation using OrmLite's typed API's (i.e. not dynamic SQL or partial updates using anon types). This functionality can be used for easily auto-maintaining Audit information for your POCO data models, e.g:

    public interface IAudit 
    {
        DateTime CreatedDate { get; set; }
        DateTime ModifiedDate { get; set; }
        string ModifiedBy { get; set; }
    }
    
    OrmLiteConfig.InsertFilter = (dbCmd, row) => {
        if (row is IAudit auditRow)
            auditRow.CreatedDate = auditRow.ModifiedDate = DateTime.UtcNow;
    };
    
    OrmLiteConfig.UpdateFilter = (dbCmd, row) => {
        if (row is IAudit auditRow)
            auditRow.ModifiedDate = DateTime.UtcNow;
    };

    Which will ensure that the CreatedDate and ModifiedDate fields are populated on every insert and update.

    Validation Example

    The filters can also be used for validation where throwing an exception will prevent the operation and bubble the exception, e.g:

    OrmLiteConfig.InsertFilter = OrmLiteConfig.UpdateFilter = (dbCmd, row) => {
        if (row is IAudit auditRow && auditRow.ModifiedBy == null)
            throw new ArgumentNullException("ModifiedBy");
    };
    
    try
    {
        db.Insert(new AuditTable());
    }
    catch (ArgumentNullException) {
       //throws ArgumentNullException
    }
    
    db.Insert(new AuditTable { ModifiedBy = "Me!" }); //succeeds

    Custom SQL Customizations

    A number of new hooks are available to provide more flexibility when creating and dropping your RDBMS tables.

    CustomSelect Attribute

    The new [CustomSelect] can be used to define properties you want populated from a Custom SQL Function or Expression instead of a normal persisted column, e.g:

    public class Block
    {
        public int Id { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
    
        [CustomSelect("Width * Height")]
        public int Area { get; set; }
    
        [Default(OrmLiteVariables.SystemUtc)]
        public DateTime CreatedDate { get; set; }
    
        [CustomSelect("FORMAT(CreatedDate, 'yyyy-MM-dd')")]
        public string DateFormat { get; set; }
    }
    
    db.Insert(new Block { Id = 1, Width = 10, Height = 5 });
    
    var block = db.SingleById(1);
    
    block.Area.Print(); //= 50
    
    block.DateFormat.Print(); //= 2016-06-08 (SQL Server)

    Order by dynamic expressions

    The [CustomSelect] attribute can be used to populate a property with a dynamic SQL Expression instead of an existing column, e.g:

    public class FeatureRequest
    {
        public int Id { get; set; }
        public int Up { get; set; }
        public int Down { get; set; }
    
        [CustomSelect("1 + Up - Down")]
        public int Points { get; set; }
    }

    You can also order by the SQL Expression by referencing the property as you would a normal column. By extension this feature now also works in AutoQuery where you can select it in a partial result set and order the results by using its property name, e.g:

    /features?fields=id,points&orderBy=points
    

    Custom SQL Fragments

    The Sql.Custom() API lets you use raw SQL Fragments in Custom .Select() expressions, e.g:

    var q = db.From
    () .Select(x => new { FirstName = x.FirstName, LastName = x.LastName, Initials = Sql.Custom("CONCAT(LEFT(FirstName,1), LEFT(LastName,1))") });

    Custom Field Declarations

    The [CustomField] attribute can be used for specifying custom field declarations in the generated Create table DDL statements, e.g:

    public class PocoTable
    {
        public int Id { get; set; }
    
        [CustomField("CHAR(20)")]
        public string CharColumn { get; set; }
    
        [CustomField("DECIMAL(18,4)")]
        public decimal? DecimalColumn { get; set; }
    
        [CustomField(OrmLiteVariables.MaxText)]        //= {MAX_TEXT}
        public string MaxText { get; set; }
    
        [CustomField(OrmLiteVariables.MaxTextUnicode)] //= {NMAX_TEXT}
        public string MaxUnicodeText { get; set; }
    }
    
    db.CreateTable(); 

    Generates and executes the following SQL in SQL Server:

    CREATE TABLE "PocoTable" 
    (
      "Id" INTEGER PRIMARY KEY, 
      "CharColumn" CHAR(20) NULL, 
      "DecimalColumn" DECIMAL(18,4) NULL, 
      "MaxText" VARCHAR(MAX) NULL, 
      "MaxUnicodeText" NVARCHAR(MAX) NULL 
    ); 

    OrmLite replaces any variable placeholders with the value in each RDBMS DialectProvider's Variables Dictionary.

    Pre / Post Custom SQL Hooks when Creating and Dropping tables

    Pre / Post Custom SQL Hooks allow you to inject custom SQL before and after tables are created or dropped, e.g:

    [PostCreateTable("INSERT INTO TableWithSeedData (Name) VALUES ('Foo');" +
                     "INSERT INTO TableWithSeedData (Name) VALUES ('Bar');")]
    public class TableWithSeedData
    {
        [AutoIncrement]
        public int Id { get; set; }
        public string Name { get; set; }
    }

    Which like other ServiceStack attributes, can also be added dynamically, e.g:

    typeof(TableWithSeedData)
        .AddAttributes(new PostCreateTableAttribute(
            "INSERT INTO TableWithSeedData (Name) VALUES ('Foo');" +
            "INSERT INTO TableWithSeedData (Name) VALUES ('Bar');"));

    Custom SQL Hooks also allow executing custom SQL before and after a table has been created or dropped, i.e:

    [PreCreateTable(runSqlBeforeTableCreated)]
    [PostCreateTable(runSqlAfterTableCreated)]
    [PreDropTable(runSqlBeforeTableDropped)]
    [PostDropTable(runSqlAfterTableDropped)]
    public class Table {}

    Untyped API support

    The IUntypedApi interface is useful for when you only have access to a late-bound object runtime type which is accessible via db.CreateTypedApi, e.g:

    public class BaseClass
    {
        public int Id { get; set; }
    }
    
    public class Target : BaseClass
    {
        public string Name { get; set; }
    }
    
    var row = (BaseClass)new Target { Id = 1, Name = "Foo" };
    
    var useType = row.GetType();
    var typedApi = db.CreateTypedApi(useType);
    
    db.DropAndCreateTables(useType);
    
    typedApi.Save(row);
    
    var typedRow = db.SingleById(1);
    typedRow.Name //= Foo
    
    var updateRow = (BaseClass)new Target { Id = 1, Name = "Bar" };
    
    typedApi.Update(updateRow);
    
    typedRow = db.SingleById(1);
    typedRow.Name //= Bar
    
    typedApi.Delete(typedRow, new { Id = 1 });
    
    typedRow = db.SingleById(1); //= null

    T4 Template Support

    OrmLite's T4 Template are useful in database-first development or when wanting to use OrmLite with an existing RDBMS by automatically generating POCO's and strong-typed wrappers for executing stored procedures.

    OrmLite's T4 support can be added via NuGet with:

    PM> Install-Package ServiceStack.OrmLite.T4
    

    Typed SqlExpressions with Custom SQL APIs

    OrmLite's Expression support satisfies the most common RDBMS queries with a strong-typed API. For more complex queries you can easily fall back to raw SQL where the Custom SQL API's let you to map custom SqlExpressions into different responses:

    var q = db.From()
              .Where(x => x.Age < 50)
              .Select("*");
    List results = db.SqlList(q);
    
    List results = db.SqlList(
        "SELECT * FROM Person WHERE Age < @age", new { age=50});
    
    List results = db.SqlColumn(db.From().Select(x => x.LastName));
    List results = db.SqlColumn("SELECT LastName FROM Person");
    
    HashSet results = db.ColumnDistinct(db.From().Select(x => x.Age));
    HashSet results = db.ColumnDistinct("SELECT Age FROM Person");
    
    var q = db.From()
              .Where(x => x.Age < 50)
              .Select(Sql.Count("*"));
    int result = db.SqlScalar(q);
    int result = db.SqlScalar("SELCT COUNT(*) FROM Person WHERE Age < 50");

    Custom Insert and Updates

    Db.ExecuteSql("INSERT INTO page_stats (ref_id, fav_count) VALUES (@refId, @favCount)",
                  new { refId, favCount })
    
    //Async:
    Db.ExecuteSqlAsync("UPDATE page_stats SET view_count = view_count + 1 WHERE id = @id", new { id })

    Stored Procedures using Custom Raw SQL API's

    The Raw SQL API's provide a convenient way for mapping results of any Custom SQL like executing Stored Procedures:

    List results = db.SqlList("EXEC GetAnalyticsForWeek 1");
    List results = db.SqlList(
        "EXEC GetAnalyticsForWeek @weekNo", new { weekNo = 1 });
    
    List results = db.SqlList("EXEC GetTotalsForWeek 1");
    List results = db.SqlList(
        "EXEC GetTotalsForWeek @weekNo", new { weekNo = 1 });
    
    int result = db.SqlScalar("SELECT 10");

    Stored Procedures with output params

    The SqlProc API provides even greater customization by letting you modify the underlying ADO.NET Stored Procedure call by returning a prepared IDbCommand allowing for advanced customization like setting and retriving OUT parameters, e.g:

    string spSql = @"DROP PROCEDURE IF EXISTS spSearchLetters;
        CREATE PROCEDURE spSearchLetters (IN pLetter varchar(10), OUT pTotal int)
        BEGIN
            SELECT COUNT(*) FROM LetterFrequency WHERE Letter = pLetter INTO pTotal;
            SELECT * FROM LetterFrequency WHERE Letter = pLetter;
        END";
    
    db.ExecuteSql(spSql);
    
    using (var cmd = db.SqlProc("spSearchLetters", new { pLetter = "C" }))
    {
        var pTotal = cmd.AddParam("pTotal", direction: ParameterDirection.Output);
    
        var results = cmd.ConvertToList();
        var total = pTotal.Value;
    }

    An alternative approach is to use SqlList which lets you use a filter to customize a Stored Procedure or any other command type, e.g:

    IDbDataParameter pTotal = null;
    var results = db.SqlList("spSearchLetters", cmd => {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.AddParam("pLetter", "C");
            pTotal = cmd.AddParam("pTotal", direction: ParameterDirection.Output);
        });
    var total = pTotal.Value;

    More examples can be found in SqlServerProviderTests.

    Foreign Key attribute for referential actions on Update/Deletes

    Creating a foreign key in OrmLite can be done by adding [References(typeof(ForeignKeyTable))] on the relation property, which will result in OrmLite creating the Foreign Key relationship when it creates the DB table with db.CreateTable.

    Additional fine-grain options and behaviour are available in the [ForeignKey] attribute which will let you specify the desired behaviour when deleting or updating related rows in Foreign Key tables.

    An example of a table with the different available options:

    public class TableWithAllCascadeOptions
    {
    	[AutoIncrement] public int Id { get; set; }
    	
    	[References(typeof(ForeignKeyTable1))]
    	public int SimpleForeignKey { get; set; }
    	
    	[ForeignKey(typeof(ForeignKeyTable2), OnDelete = "CASCADE", OnUpdate = "CASCADE")]
    	public int? CascadeOnUpdateOrDelete { get; set; }
    	
    	[ForeignKey(typeof(ForeignKeyTable3), OnDelete = "NO ACTION")]
    	public int? NoActionOnCascade { get; set; }
    	
    	[Default(typeof(int), "17")]
    	[ForeignKey(typeof(ForeignKeyTable4), OnDelete = "SET DEFAULT")]
    	public int SetToDefaultValueOnDelete { get; set; }
    	
    	[ForeignKey(typeof(ForeignKeyTable5), OnDelete = "SET NULL")]
    	public int? SetToNullOnDelete { get; set; }
    }

    System Variables and Default Values

    To provide richer support for non-standard default values, each RDBMS Dialect Provider contains aOrmLiteDialectProvider.Variables placeholder dictionary for storing common, but non-standard RDBMS functionality. We can use this to declaratively define non-standard default values that works across all supported RDBMS's like automatically populating a column with the RDBMS UTC Date when Inserted with a default(T) Value:

    public class Poco
    {
        [Default(OrmLiteVariables.SystemUtc)]  //= {SYSTEM_UTC}
        public DateTime CreatedTimeUtc { get; set; }
    }

    OrmLite variables need to be surrounded with {} braces to identify that it's a placeholder variable, e.g {SYSTEM_UTC}.

    The ForeignKeyTests show the resulting behaviour with each of these configurations in more detail.

    Note: Only supported on RDBMS's with foreign key/referential action support, e.g. Sql Server, PostgreSQL, MySQL. Otherwise they're ignored.

    Multi nested database connections

    We now support multiple nested database connections so you can now trivially use OrmLite to access multiple databases on different connections. The OrmLiteConnectionFactory class has been extended to support named connections which allows you to conveniently define all your db connections when you register it in your IOC and access them with the named property when you use them.

    A popular way of scaling RDBMS's is to create a Master / Shard setup where datasets for queries that span entire system are kept in the master database, whilst context-specific related data can be kept together in an isolated shard. This feature makes it trivial to maintain multiple separate db shards with a master database in a different RDBMS.

    Here's an (entire source code) sample of the code needed to define, and populate a Master/Shard setup. Sqlite can create DB shards on the fly so only the blank SqlServer master database needed to be created out-of-band:

    Sharding 1000 Robots into 10 Sqlite DB shards - referencing each in a Master SqlServer RDBMS

    public class MasterRecord {
        public Guid Id { get; set; }
        public int RobotId { get; set; }
        public string RobotName { get; set; }
        public DateTime? LastActivated { get; set; }
    }
    
    public class Robot {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsActivated { get; set; }
        public long CellCount { get; set; }
        public DateTime CreatedDate { get; set; }
    }
    
    const int NoOfShards = 10;
    const int NoOfRobots = 1000;
    
    var dbFactory = new OrmLiteConnectionFactory(
        "Data Source=host;Initial Catalog=RobotsMaster;Integrated Security=SSPI",  //Connection String
        SqlServerDialect.Provider); 
    
    dbFactory.Run(db => db.CreateTable(overwrite:false));
    
    NoOfShards.Times(i => {
        var namedShard = "robots-shard" + i;
        dbFactory.RegisterConnection(namedShard, 
            $"~/App_Data/{shardId}.sqlite".MapAbsolutePath(),                //Connection String
            SqliteDialect.Provider);
    	
    	dbFactory.OpenDbConnection(namedShard).Run(db => db.CreateTable(overwrite:false));
    });
    
    var newRobots = NoOfRobots.Times(i => //Create 1000 Robots
        new Robot { Id=i, Name="R2D"+i, CreatedDate=DateTime.UtcNow, CellCount=DateTime.Now.ToUnixTimeMs() % 100000 });
    
    foreach (var newRobot in newRobots) 
    {
        using (IDbConnection db = dbFactory.OpenDbConnection()) //Open Connection to Master DB 
        {
            db.Insert(new MasterRecord { Id = Guid.NewGuid(), RobotId = newRobot.Id, RobotName = newRobot.Name });
            using (IDbConnection robotShard = dbFactory.OpenDbConnection("robots-shard"+newRobot.Id % NoOfShards)) //Shard
            {
                robotShard.Insert(newRobot);
            }
        }
    }

    Using the SQLite Manager Firefox extension we can peek at one of the created shards to see 100 Robots in each shard. This is the dump of robots-shard0.sqlite:

    As expected each shard has every 10th robot inside.

    Code-first Customer & Order example with complex types on POCO as text blobs

    Below is a complete stand-alone example. No other config or classes is required for it to run. It's also available as a stand-alone unit test.

    public enum PhoneType {
        Home,
        Work,
        Mobile,
    }
    
    public enum AddressType {
        Home,
        Work,
        Other,
    }
    
    public class Address {
        public string Line1 { get; set; }
        public string Line2 { get; set; }
        public string ZipCode { get; set; }
        public string State { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
    }
    
    public class Customer {
        public Customer() {
            this.PhoneNumbers = new Dictionary();
            this.Addresses = new Dictionary();
        }
    
        [AutoIncrement] // Creates Auto primary key
        public int Id { get; set; }
        
        public string FirstName { get; set; }
        public string LastName { get; set; }
        
        [Index(Unique = true)] // Creates Unique Index
        public string Email { get; set; }
        
        public Dictionary PhoneNumbers { get; set; }  //Blobbed
        public Dictionary Addresses { get; set; }  //Blobbed
        public DateTime CreatedAt { get; set; }
    }
    
    public class Order {
        
        [AutoIncrement]
        public int Id { get; set; }
        
        [References(typeof(Customer))]      //Creates Foreign Key
        public int CustomerId { get; set; }
        
        [References(typeof(Employee))]      //Creates Foreign Key
        public int EmployeeId { get; set; }
        
        public Address ShippingAddress { get; set; } //Blobbed (no Address table)
        
        public DateTime? OrderDate { get; set; }
        public DateTime? RequiredDate { get; set; }
        public DateTime? ShippedDate { get; set; }
        public int? ShipVia { get; set; }
        public decimal Freight { get; set; }
        public decimal Total { get; set; }
    }
    
    public class OrderDetail {
        
        [AutoIncrement]
        public int Id { get; set; }
        
        [References(typeof(Order))] //Creates Foreign Key
        public int OrderId { get; set; }
        
        public int ProductId { get; set; }
        public decimal UnitPrice { get; set; }
        public short Quantity { get; set; }
        public decimal Discount { get; set; }
    }
    
    public class Employee {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    
    public class Product {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal UnitPrice { get; set; }
    }
    
    //Setup SQL Server Connection Factory
    var dbFactory = new OrmLiteConnectionFactory(
    	@"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\App_Data\Database1.mdf;Integrated Security=True;User Instance=True",
    	SqlServerDialect.Provider);
    
    //Use in-memory Sqlite DB instead
    //var dbFactory = new OrmLiteConnectionFactory(
    //    ":memory:", false, SqliteDialect.Provider);
    
    //Non-intrusive: All extension methods hang off System.Data.* interfaces
    using (IDbConnection db = Config.OpenDbConnection())
    {
      //Re-Create all table schemas:
      db.DropTable();
      db.DropTable();
      db.DropTable();
      db.DropTable();
      db.DropTable();
    
      db.CreateTable();
      db.CreateTable();
      db.CreateTable();
      db.CreateTable();
      db.CreateTable();
    
      db.Insert(new Employee { Id = 1, Name = "Employee 1" });
      db.Insert(new Employee { Id = 2, Name = "Employee 2" });
      var product1 = new Product { Id = 1, Name = "Product 1", UnitPrice = 10 };
      var product2 = new Product { Id = 2, Name = "Product 2", UnitPrice = 20 };
      db.Save(product1, product2);
    
      var customer = new Customer {
          FirstName = "Orm",
          LastName = "Lite",
          Email = "[email protected]",
          PhoneNumbers =
          {
              { PhoneType.Home, "555-1234" },
              { PhoneType.Work, "1-800-1234" },
              { PhoneType.Mobile, "818-123-4567" },
          },
          Addresses =
          {
              { AddressType.Work, new Address { 
                Line1 = "1 Street", Country = "US", State = "NY", City = "New York", ZipCode = "10101" } 
              },
          },
          CreatedAt = DateTime.UtcNow,
      };
    
      var customerId = db.Insert(customer, selectIdentity: true); //Get Auto Inserted Id
      customer = db.Single(new { customer.Email }); //Query
      Assert.That(customer.Id, Is.EqualTo(customerId));
    
      //Direct access to System.Data.Transactions:
      using (IDbTransaction trans = db.OpenTransaction(IsolationLevel.ReadCommitted))
      {
          var order = new Order {
              CustomerId = customer.Id,
              EmployeeId = 1,
              OrderDate = DateTime.UtcNow,
              Freight = 10.50m,
              ShippingAddress = new Address { 
                Line1 = "3 Street", Country = "US", State = "NY", City = "New York", ZipCode = "12121" },
          };
          db.Save(order); //Inserts 1st time
    
          //order.Id populated on Save().
    
          var orderDetails = new[] {
              new OrderDetail {
                  OrderId = order.Id,
                  ProductId = product1.Id,
                  Quantity = 2,
                  UnitPrice = product1.UnitPrice,
              },
              new OrderDetail {
                  OrderId = order.Id,
                  ProductId = product2.Id,
                  Quantity = 2,
                  UnitPrice = product2.UnitPrice,
                  Discount = .15m,
              }
          };
    
          db.Save(orderDetails);
    
          order.Total = orderDetails.Sum(x => x.UnitPrice * x.Quantity * x.Discount) + order.Freight;
    
          db.Save(order); //Updates 2nd Time
    
          trans.Commit();
      }
    }

    Running this against a SQL Server database will yield the results below:

    Notice the POCO types are stored in the very fast and Versatile JSV Format which although hard to do - is actually more compact, human and parser-friendly than JSON :)

    Ignoring DTO Properties

    You may use the [Ignore] attribute to denote DTO properties that are not fields in the table. This will force the SQL generation to ignore that property.

    More Examples

    In its simplest useage, OrmLite can persist any POCO type without any attributes required:

    public class SimpleExample
    {
    	public int Id { get; set; }
    	public string Name { get; set; }
    }
    
    //Set once before use (i.e. in a static constructor).
    OrmLiteConfig.DialectProvider = SqliteDialect.Provider;
    
    using (IDbConnection db = "/path/to/db.sqlite".OpenDbConnection())
    {
    	db.CreateTable(true);
    	db.Insert(new SimpleExample { Id=1, Name="Hello, World!"});
    	var rows = db.Select();
    
    	Assert.That(rows, Has.Count(1));
    	Assert.That(rows[0].Id, Is.EqualTo(1));
    }

    To get a better idea of the features of OrmLite lets walk through a complete example using sample tables from the Northwind database. _ (Full source code for this example is available here.) _

    So with no other configuration using only the classes below:

    [Alias("Shippers")]
    public class Shipper
    	: IHasId
    {
    	[AutoIncrement]
    	[Alias("ShipperID")]
    	public int Id { get; set; }
    
    	[Required]
    	[Index(Unique = true)]
    	[StringLength(40)]
    	public string CompanyName { get; set; }
    
    	[StringLength(24)]
    	public string Phone { get; set; }
    
    	[References(typeof(ShipperType))]
    	public int ShipperTypeId { get; set; }
    }
    
    [Alias("ShipperTypes")]
    public class ShipperType
    	: IHasId
    {
    	[AutoIncrement]
    	[Alias("ShipperTypeID")]
    	public int Id { get; set; }
    
    	[Required]
    	[Index(Unique = true)]
    	[StringLength(40)]
    	public string Name { get; set; }
    }
    
    public class SubsetOfShipper
    {
    	public int ShipperId { get; set; }
    	public string CompanyName { get; set; }
    }
    
    public class ShipperTypeCount
    {
    	public int ShipperTypeId { get; set; }
    	public int Total { get; set; }
    }

    Creating tables

    Creating tables is a simple 1-liner:

    using (IDbConnection db = ":memory:".OpenDbConnection())
    {
        db.CreateTable();
        db.CreateTable();
    }
    
    /* In debug mode the line above prints:
    DEBUG: CREATE TABLE "ShipperTypes" 
    (
      "ShipperTypeID" INTEGER PRIMARY KEY AUTOINCREMENT, 
      "Name" VARCHAR(40) NOT NULL 
    );
    DEBUG: CREATE UNIQUE INDEX uidx_shippertypes_name ON "ShipperTypes" ("Name" ASC);
    DEBUG: CREATE TABLE "Shippers" 
    (
      "ShipperID" INTEGER PRIMARY KEY AUTOINCREMENT, 
      "CompanyName" VARCHAR(40) NOT NULL, 
      "Phone" VARCHAR(24) NULL, 
      "ShipperTypeId" INTEGER NOT NULL, 
    
      CONSTRAINT "FK_Shippers_ShipperTypes" FOREIGN KEY ("ShipperTypeId") REFERENCES "ShipperTypes" ("ShipperID") 
    );
    DEBUG: CREATE UNIQUE INDEX uidx_shippers_companyname ON "Shippers" ("CompanyName" ASC);
    */

    Transaction Support

    As we have direct access to IDbCommand and friends - playing with transactions is easy:

    var trainsType = new ShipperType { Name = "Trains" };
    var planesType = new ShipperType { Name = "Planes" };
    
    //Playing with transactions
    using (IDbTransaction dbTrans = db.OpenTransaction())
    {
        db.Save(trainsType);
        db.Save(planesType);
    
        dbTrans.Commit();
    }
    
    using (IDbTransaction dbTrans = db.OpenTransaction(IsolationLevel.ReadCommitted))
    {
        db.Insert(new ShipperType { Name = "Automobiles" });
        Assert.That(db.Select(), Has.Count.EqualTo(3));
    }
    Assert.That(db.Select(), Has.Count(2));

    CRUD Operations

    No ORM is complete without the standard crud operations:

    	//Performing standard Insert's and Selects
      db.Insert(new Shipper { CompanyName = "Trains R Us", Phone = "555-TRAINS", ShipperTypeId = trainsType.Id });
      db.Insert(new Shipper { CompanyName = "Planes R Us", Phone = "555-PLANES", ShipperTypeId = planesType.Id });
      db.Insert(new Shipper { CompanyName = "We do everything!", Phone = "555-UNICORNS", ShipperTypeId = planesType.Id });
    
      var trainsAreUs = db.Single("ShipperTypeId = @Id", new { trainsType.Id });
      Assert.That(trainsAreUs.CompanyName, Is.EqualTo("Trains R Us"));
      Assert.That(db.Select("CompanyName = @company OR Phone = @phone", 
            new { company = "Trains R Us", phone = "555-UNICORNS" }), Has.Count.EqualTo(2));
      Assert.That(db.Select("ShipperTypeId = @Id", new { planesType.Id }), Has.Count.EqualTo(2));
    
      //Lets update a record
      trainsAreUs.Phone = "666-TRAINS";
      db.Update(trainsAreUs);
              Assert.That(db.SingleById(trainsAreUs.Id).Phone, Is.EqualTo("666-TRAINS"));
      
      //Then make it dissappear
      db.Delete(trainsAreUs);
              Assert.That(db.SingleById(trainsAreUs.Id), Is.Null);
    
      //And bring it back again
      db.Insert(trainsAreUs);

    Performing custom queries

    And with access to raw sql when you need it - the database is your oyster :)

    var partialColumns = db.Select(typeof(Shipper), 
        "ShipperTypeId = @Id", new { planesType.Id });
    Assert.That(partialColumns, Has.Count.EqualTo(2));
    
    //Select into another POCO class that matches sql
    var rows = db.Select(
        "SELECT ShipperTypeId, COUNT(*) AS Total FROM Shippers GROUP BY ShipperTypeId ORDER BY COUNT(*)");
    
    Assert.That(rows, Has.Count.EqualTo(2));
    Assert.That(rows[0].ShipperTypeId, Is.EqualTo(trainsType.Id));
    Assert.That(rows[0].Total, Is.EqualTo(1));
    Assert.That(rows[1].ShipperTypeId, Is.EqualTo(planesType.Id));
    Assert.That(rows[1].Total, Is.EqualTo(2));
    
    
    //And finally lets quickly clean up the mess we've made:
    db.DeleteAll();
    db.DeleteAll();
    
    Assert.That(db.Select(), Has.Count.EqualTo(0));
    Assert.That(db.Select(), Has.Count.EqualTo(0));

    Soft Deletes

    Select Filters let you specify a custom SelectFilter that lets you modify queries that use SqlExpression before they're executed. This could be used to make working with "Soft Deletes" Tables easier where it can be made to apply a custom x.IsDeleted != true condition on every SqlExpression.

    By either using a SelectFilter on concrete POCO Table Types, e.g:

    SqlExpression.SelectFilter = q => q.Where(x => x.IsDeleted != true);
    SqlExpression.SelectFilter = q => q.Where(x => x.IsDeleted != true);

    Or alternatively using generic delegate that applies to all SqlExpressions, but you'll only have access to aIUntypedSqlExpression which offers a limited API surface area but will still let you execute a custom filter for all SqlExpression that could be used to add a condition for all tables implementing a custom ISoftDelete interface with:

    OrmLiteConfig.SqlExpressionSelectFilter = q =>
    {
        if (q.ModelDef.ModelType.HasInterface(typeof(ISoftDelete)))
        {
            q.Where(x => x.IsDeleted != true);
        }
    };

    Both solutions above will transparently add the x.IsDeleted != true to all SqlExpression based queries so it only returns results which aren't IsDeleted from any of queries below:

    var results = db.Select(db.From
    ()); var result = db.Single(db.From
    ().Where(x => x.Name == "foo")); var result = db.Single(x => x.Name == "foo");

    Check Constraints

    OrmLite includes support for SQL Check Constraints which will create your Table schema with the [CheckConstraint]specified, e.g:

    public class Table
    {
        [AutoIncrement]
        public int Id { get; set; }
    
        [Required]
        [CheckConstraint("Age > 1")]
        public int Age { get; set; }
    
        [CheckConstraint("Name IS NOT NULL")]
        public string Name { get; set; }
    }

    Bitwise operators

    The Typed SqlExpression bitwise operations support depends on the RDBMS used.

    E.g. all RDBMS's support Bitwise And and Or operators:

    db.Select
    (x => (x.Id | 2) == 3); db.Select
    (x => (x.Id & 2) == 2);

    All RDBMS Except for SQL Server support bit shift operators:

    db.Select
    (x => (x.Id << 1) == 4); db.Select
    (x => (x.Id >> 1) == 1);

    Whilst only SQL Server and MySQL Support Exclusive Or:

    db.Select
    (x => (x.Id ^ 2) == 3);

    SQL Server Features

    Memory Optimized Tables

    OrmLite allows access to many advanced SQL Server features including Memory-Optimized Tables where you can tell SQL Server to maintain specific tables in Memory using the [SqlServerMemoryOptimized] attribute, e.g:

    [SqlServerMemoryOptimized(SqlServerDurability.SchemaOnly)]
    public class SqlServerMemoryOptimizedCacheEntry : ICacheEntry
    {
        [PrimaryKey]
        [StringLength(StringLengthAttribute.MaxText)]
        [SqlServerBucketCount(10000000)]
        public string Id { get; set; }
        [StringLength(StringLengthAttribute.MaxText)]
        public string Data { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime? ExpiryDate { get; set; }
        public DateTime ModifiedDate { get; set; }
    }

    The [SqlServerBucketCount] attribute can be used to configure the bucket count for a hash index whilst the new [SqlServerCollate] attribute can be used to specify an SQL Server collation.

    PostgreSQL Features

    PostgreSQL Data Types

    The [PgSql*] specific attributes lets you use attributes to define PostgreSQL rich data types, e.g:

    public class MyPostgreSqlTable
    {
        [PgSqlJson]
        public List AsJson { get; set; }
    
        [PgSqlJsonB]
        public List AsJsonB { get; set; }
    
        [PgSqlTextArray]
        public string[] AsTextArray { get; set; }
    
        [PgSqlIntArray]
        public int[] AsIntArray { get; set; }
    
        [PgSqlBigIntArray]
        public long[] AsLongArray { get; set; }
    }

    Limitations

    Single Primary Key

    For simplicity, and to be able to have the same POCO class persisted in db4o, memcached, redis or on the filesystem (i.e. providers included in ServiceStack), each model must have a single primary key, by convention OrmLite expects it to be Idalthough you use [Alias("DbFieldName")] attribute it map it to a column with a different name or use the [PrimaryKey]attribute to tell OrmLite to use a different property for the primary key.

    You can still SELECT from these tables, you will just be unable to make use of APIs that rely on it, e.g. Update or Deletewhere the filter is implied (i.e. not specified), all the APIs that end with ById, etc.

    Optimize LIKE Searches

    One of the primary goals of OrmLite is to expose and RDBMS agnostic Typed API Surface which will allow you to easily switch databases, or access multiple databases at the same time with the same behavior.

    One instance where this can have an impact is needing to use UPPER() in LIKE searches to enable case-insensitive LIKEqueries across all RDBMS. The drawback of this is that LIKE Queries are not able to use any existing RDBMS indexes. We can disable this feature and return to the default RDBMS behavior with:

    OrmLiteConfig.StripUpperInLike = true;

    Allowing all LIKE Searches in OrmLite or AutoQuery to use any available RDBMS Index.

    Oracle Provider Notes

    The Oracle provider requires an installation of Oracle's ODP.NET. It has been tested with Oracle 11g but should work with 10g and perhaps even older versions. It has not been tested with Oracle 12c and does not support any new 12c features such as AutoIncrement keys. It also does not support the new Oracle fully-managed client.

    By default the Oracle provider stores Guids in the database as character strings and when generating SQL it quotes only table and column names that are reserved words in Oracle. That requires that you use the same quoting if you code your own SQL. Both of these options can be overridden, but overriding them will cause problems: the provider can store Guids as raw(16) but it cannot read them.

    The Oracle provider uses Oracle sequences to implement AutoIncrement columns and it queries the sequence to get a new value in a separate database call. You can override the automatically generated sequence name with a

    [Sequence("name")]

    attribute on a field. The Sequence attribute implies [AutoIncrement], but you can use both on the same field.

    Since Oracle has a very restrictive 30 character limit on names, it is strongly suggested that you use short entity class and field names or aliases, remembering that indexes and foreign keys get compound names. If you use long names, the provider will squash them to make them compliant with the restriction. The algorithm used is to remove all vowels ("aeiouy") and if still too long then every fourth letter starting with the third one and finally if still too long to truncate the name. You must apply the same squashing algorithm if you are coding your own SQL.

    The previous version of ServiceStack.OrmLite.Oracle used System.Data.OracleClient to talk to the database. Microsoft has deprecated that client, but it does still mostly work if you construct the Oracle provider like this:

    OracleOrmLiteDialectProvider.Instance = new OracleOrmLiteDialectProvider(
    compactGuid: false,
    quoteNames: false,
    clientProvider: OracleOrmLiteDialectProvider.MicrosoftProvider); 
    

    DateTimeOffset fields and, in locales that use a comma to separate the fractional part of a floating point number, some aspects of using floating point numbers, do not work with System.Data.OracleClient.

    Community Resources

    • OrmLite and Redis: New alternatives for handling db communication by @abtosoftware
    • Object Serialization as Step Towards Normalization by @ 82unpluggd
    • Creating a Data Access Layer using OrmLite by Lydon Bergin
    • Code Generation using ServiceStack.OrmLite and T4 Text templates by @jokecamp
    • Simple ServiceStack OrmLite Example by @robrtc
    • OrmLite Blobbing done with NHibernate and Serialized JSON by @philliphaydon
    • Creating An ASP.NET MVC Blog With ServiceStack.OrmLite by @peterbromberg

    Other notable Micro ORMs for .NET

    Many performance problems can be mitigated and a lot of use-cases can be simplified without the use of a heavyweight ORM, and their config, mappings and infrastructure. As performance is the most important feature we can recommend the following list, each with their own unique special blend of features.

    • Dapper - by @samsaffron and @marcgravell
      • The current performance king, supports both POCO and dynamic access, fits in a single class. Put in production to solve StackOverflow's DB Perf issues. Requires .NET 4.
    • PetaPoco - by @toptensoftware
      • Fast, supports dynamics, expandos and typed POCOs, fits in a single class, runs on .NET 3.5 and Mono. Includes optional T4 templates for POCO table generation.
    • Massive - by @robconery
      • Fast, supports dynamics and expandos, smart use of optional params to provide a wrist-friendly api, fits in a single class. Multiple RDBMS support. Requires .NET 4.
    • Simple.Data - by @markrendle
      • A little slower than above ORMS, most wrist-friendly courtesy of a dynamic API, multiple RDBMS support inc. Mongo DB. Requires .NET 4.

    你可能感兴趣的:(ServiceStack.OrmLite)