static void

Linq2Sql

Links

Limitations

SqlServer + SqlServerCe3.5 only. Generally simple single-table mapping, but you can do use an InheritanceMappingAttribute.

It's simple, and Microsoft steers you to Entity Framework, but as a simple ORM it's good enough as long as you Sql-Profile what you're doing. I used it in integration tests when I want to check what the "real" data access layer did to the database.

Mapping

The dbml designer/SqlMetal uses DataAnnotations by default- but you can also use XML.

Entities (and the datacontext) can use a BaseClass. Individual properties (eg binary blobs) can be "DelayLoaded" (i.e. lazy loaded).

Selecting

Selecting with Linq is easy (where, groupby, select projections, paging with skip/take)

Joins

//eager fetching
var dlo = new DataLoadOptions();
dlo.LoadWith<Products>(p => p.Categories);
context.LoadOptions = dlo;

(there's also a DataLoadOptions.AssociateWith to filter subsets of associations)

Updates

Generated entities implement INotifyPropertyChanging/Changed for tracking.

var transport = new Category {CategoryName = "Transport"};
context.Categories.InsertOnSubmit(transport);
context.SubmitChanges();

transport.Description = "All about transport";
context.SubmitChanges();

context.Categories.DeleteOnSubmit(transport);
context.SubmitChanges();

Concurrency

It uses optimistic concurrency by default (unless the table has a timestamp column - mark it as IsVersion). In designer, set the ColumnAttribute UpdateCheck enum to Always (default), Never or WhenChanged.

try
{
    //ConflictMode.FailOnFirstConflict or
    context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
    foreach (var conflict in context.ChangeConflicts)
    {
        //RefreshMode.KeepChanges || RefreshMode.KeepCurrentValues
        conflict.Resolve(RefreshMode.OverwriteCurrentValues);
    }
}

Disconnected entities

Beware of optimistic concurrency (generally best to use timestamp or set each column to [Column(UpdateCheck.Never)])

var original = context.Categories.Where(x => x.CategoryID == 1).Single();
Category clone;
//pretend this is going over a webservice
using (var ms = new MemoryStream())
{
    //ensure SerializationMode=Unidirectional
    var dcs = new DataContractSerializer(typeof(Category));
    dcs.WriteObject(ms, original);
    ms.Seek(0, SeekOrigin.Begin);
    clone = (Category)dcs.ReadObject(ms);
}
clone.Description = "Chocolate products";

//Optimistic concurrency with timestamps
//context.Categories.Attach(clone, true /* mark modified */);
//otherwise
using (var context2 = new NorthwindDataContext())
{
    //we need another context or DuplicateKeyException
    context2.Categories.Attach(clone, original);
    context2.SubmitChanges();
}

Logging

var sw = new StringWriter();
context.Log = sw;

context.SubmitChanges();

Debug.WriteLine(sw.GetStringBuilder().ToString());

Serialization

Default none, or "unidirectional" (only parent primary key side for foreign keys -uses DataContract serialization)

Mixing in ADO

//easy mapping
var category1 = context.ExecuteQuery<Category>(
    //NB {0} for parameters, not @x
    "SELECT CategoryId, CategoryName FROM Categories WHERE CategoryId={0}",
    1).Single();
//also context.ExecuteCommand("sql", "parameters");