November 29, 2009

DataObjects.Net v4.1 features: nested transactions + cheat sheet

I just added the following example to manual:
// Building the domain
var domain = BuildDomain();

Key dmitriKey;
int dmitriId;
string dmitriKeyString;

// Opening Session
using (Session.Open(domain)) {

  // Opening transaction
  using (var transactionScope = Transaction.Open()) {

    // Creating user
    var dmitri = new User {
      Name = "Dmitri"
    };
          
    // Storing entity key
    dmitriKey = dmitri.Key;
    dmitriKeyString = dmitriKey.Format();
    dmitriId = dmitri.Id;

    Console.WriteLine("Dmitri's Key (human readable): {0}", dmitriKey);
    Console.WriteLine("Dmitri's Key (serializable): {0}", dmitriKeyString);
    Console.WriteLine("Dmitri's Id: {0}", dmitriId);

    // Marking the transaction scope as completed to commit it 
    transactionScope.Complete();
  }

  // Opening another transaction
  using (var transactionScope = Transaction.Open()) {
    // Parses the serialized key
    var anotherDimtriKey = Key.Parse(Domain.Current, dmitriKeyString);
    // Keys are equal
    Assert.AreEqual(dmitriKey, anotherDimtriKey);

    // Materialization on fetch
    var dmitri = Query<User>.Single(dmitriKey);
    // Alternative way to do the same
    var anotherDmitri = Query<User>.SingleOrDefault(dmitriKey);
    Assert.AreSame(dmitri, anotherDmitri);
    // Fetching by key value(s)
    anotherDmitri = Query<User>.Single(dmitriId);
    Assert.AreSame(dmitri, anotherDmitri);

    // Modifying the entity
    dmitri.Name = "Dmitri Maximov";

    // Opening new nested transaction
    using (var nestedScope = Transaction.Open(TransactionOpenMode.New)) {
      // Removing the entity
      dmitri.Remove();
      Assert.IsTrue(dmitri.IsRemoved);
      AssertEx.Throws<InvalidOperationException>(() => {
        var dmitryName = dmitri.Name;
      });
      // No nestedScope.Complete(), so nested transaction will be rolled back
    }

    // Transparent Entity state update
    Assert.IsFalse(dmitri.IsRemoved);
    Assert.AreEqual("Dmitri Maximov", dmitri.Name);
          
    // Creating few more objects
    var xtensiveWebPage = new WebPage {
      Title = "Xtensive Web Site", 
      Url = "http://www.x-tensive.com"
    };
    var alexYakuninBlogPage = new WebPage {
      Title = "Alex Yakunin's Blog", 
      Url = "http://blog.alexyakunin.com"
    };
    var subsonicPage = new WebPage {
      Title = "SubSonic project page", 
      Url = "http://www.subsonicproject.com/"
    };

    // Adding the items to EntitySet
    dmitri.FavoritePages.Add(xtensiveWebPage);
    dmitri.FavoritePages.Add(alexYakuninBlogPage);
    dmitri.FavoritePages.Add(subsonicPage);

    // Removing the item from EntitySet
    dmitri.FavoritePages.Remove(subsonicPage);

    // Getting count of items in EntitySet
    Console.WriteLine("Dmitri's favorite page count: {0}", dmitri.FavoritePages.Count);
    Assert.AreEqual(2, dmitri.FavoritePages.Count);
    Assert.AreEqual(2, dmitri.FavoritePages.Count()); // The same, but by LINQ query

    // Enumerating EntitySet
    foreach (var page in dmitri.FavoritePages)
      Console.WriteLine("Dmitri's favorite page: {0} ({1})", page.Title, page.Url);

    // Checking for the containment
    Assert.IsTrue(dmitri.FavoritePages.Contains(xtensiveWebPage));
    Assert.IsTrue(dmitri.FavoritePages.Contains(alexYakuninBlogPage));
    Assert.IsFalse(dmitri.FavoritePages.Contains(subsonicPage));

    // Opening new nested transaction
    using (var nestedScope = Transaction.Open(TransactionOpenMode.New)) {
      // Clearing the EntitySet
      dmitri.FavoritePages.Clear();
      Assert.IsFalse(dmitri.FavoritePages.Contains(xtensiveWebPage));
      Assert.IsFalse(dmitri.FavoritePages.Contains(alexYakuninBlogPage));
      Assert.IsFalse(dmitri.FavoritePages.Contains(subsonicPage));
      Assert.AreEqual(0, dmitri.FavoritePages.Count);
      Assert.AreEqual(0, dmitri.FavoritePages.Count()); // By query
      // No nestedScope.Complete(), so nested transaction will be rolled back
    }

    // Transparent EntitySet state update
    Assert.IsTrue(dmitri.FavoritePages.Contains(xtensiveWebPage));
    Assert.IsTrue(dmitri.FavoritePages.Contains(alexYakuninBlogPage));
    Assert.IsFalse(dmitri.FavoritePages.Contains(subsonicPage));
    Assert.AreEqual(2, dmitri.FavoritePages.Count);
    Assert.AreEqual(2, dmitri.FavoritePages.Count()); // The same, but by LINQ query

    // Finally, let's query the EntitySet:
          
    // Query construction
    var dmitryFavoriteBlogs =
      from page in dmitri.FavoritePages
      where page.Url.ToLower().Contains("blog")
      select page;
          
    // Query execution
    var dmitryFavoriteBlogList = dmitryFavoriteBlogs.ToList();

    // Printing the results
    Console.WriteLine("Dmitri's favorite blog count: {0}", dmitryFavoriteBlogList.Count);
    foreach (var page in dmitryFavoriteBlogList)
      Console.WriteLine("Dmitri's favorite blog: {0} ({1})", page.Title, page.Url);

    Assert.IsTrue(dmitryFavoriteBlogList.Contains(alexYakuninBlogPage));
    Assert.IsFalse(dmitryFavoriteBlogList.Contains(xtensiveWebPage));
    Assert.AreEqual(1, dmitryFavoriteBlogList.Count);
          
    // Marking the transaction scope as completed to commit it 
    transactionScope.Complete();
  }
}
Its output:
Dmitri's Key (human readable): User, (1)
Dmitri's Key (serializable): Xtensive.Storage.Manual.EntitySets.TestFixture+User,
  Xtensive.Storage.Manual, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: 1
Dmitri's Id: 1
Dmitri's favorite page count: 2
Dmitri's favorite page: Alex Yakunin's Blog (http://blog.alexyakunin.com)
Dmitri's favorite page: Xtensive Web Site (http://www.x-tensive.com)
Dmitri's favorite blog count: 1
Dmitri's favorite blog: Alex Yakunin's Blog (http://blog.alexyakunin.com)

So Denis Krjuchkov recently has made a great job ;) Nested transactions are fully operable.

Btw, this sample is a kind of "cheat sheet" for basic operations on Entities and EntitySets.