Chapter 8. Dynamic Objects

One problem with ORM is that it takes time to create your classes and then create the mappings. Elementary provides a way to get around this through the use of DynamicObjects. DynamicObjects allow you to create the mapping structure without having to create the corresponding classes and compile them. Instead, the mapping will create an in-memory object that uses a Hashtable like approach for retrieving information, similar to DataSets. This allows for faster development. It's best to use an example to illustrate. Let's return to our original example using Product. However, this time we're not going to create a Product class. Instead, we're going to create dynamic classes.

[assembly:ElementaryDatabase(Name="Northwind",
                             ConnectionString="Data Source=localhost; Initial Catalog=northwind; User ID=sa; Password=; Trusted_Connection=false;",
                             OledbConnectionString="Provider=sqloledb;Data Source=localhost; Initial Catalog=northwind; User ID=sa; Password=;",
                             CreateDynamicClasses=true]

Now we can query for any Product:

using System;
using System.Collections;
using System.Transactions;
using Elementary;
using Elementary.Northwind.Samples;
using NUnit.Framework;

namespace Elementary.Northwind.Samples.SimpleExample
{
	/// <summary>
	/// Summary description for Test.
	/// </summary>
	[TestFixture]
	public class Test
	{
		[TestFixtureSetUp]
		public void SetUp()
		{
			Elementary.Clear();
			Elementary.LoadAssembly("Elementary.Northwind.Samples.dll");
		}
	
		[Test]
		public void GetCollection()
		{
			IObjectMapper<DynamicObject> mapper = Elementary.GetIObjectMapper<DynamicObject>("products");

			IList<DynamicObject> al = mapper.GetCollection();
			
			Assert.IsTrue(al.Count > 0, "No products loaded.");
			Assert.AreEqual(77, al.Count, "Not all products were loaded.");
			
			Console.WriteLine("Loaded {0} products.", al.Count);
			Console.WriteLine("\nFirst product:\n{0}", al[0]);
		}
	}
}

When we run this, we return the same number of products - 77 - but the object structure is different. Instead of returning a Product object, which doesn't exist, Elementary returned a DynamicObject.

Loaded 77 products.

First product:
[DynamicObject]
AssemblyName: Adapdev.Dynamic
FullName: northwind.products
Alias: products
productname: Chai
supplierid: 1
productid: 1
categoryid: 1

We can query for a specific product, which will also return a DynamicObject:

		[Test]
		public void GetObject()
		{
			// Retrieves the mapping via object type
			// Elementary looks at the full object name and locates the corresponding node in the mappings file
			IObjectMapper<DynamicObject> mapper = Elementary.GetIObjectMapper<DynamicObject>("products");
			
			// Retrieve product #1
			DynamicObject product = mapper.GetObject(1);
			
			Console.WriteLine(product);
		}

Output

[DynamicObject]
AssemblyName: Adapdev.Dynamic
FullName: northwind.products
Alias: products
productname: Chai
supplierid: 1
productid: 1
categoryid: 1

If we want to get/modify a specific property, we access it using its name:

		[Test]
		public void GetObjectAndModifyProperty()
		{
			// Retrieves the mapping via object type
			// Elementary looks at the full object name and locates the corresponding node in the mappings file
			IObjectMapper<DynamicObject> mapper = Elementary.GetIObjectMapper<DynamicObject>("products");
			
			// Retrieve product #1
			DynamicObject product = mapper.GetObject(1);
			product["productname"] = "Johnny's Subs";
			
			Console.WriteLine(product);
		}

Output

[DynamicObject]
AssemblyName: Adapdev.Dynamic
FullName: northwind.products
Alias: products
productname: Johnny's Subs
supplierid: 1
productid: 1
categoryid: 1

For DynamicObjects, you can do everything that you can do with static classes. In the example below, we'll create a new Product and save it.

		[Test]
		public void SaveObject()
		{
			IObjectMapper<DynamicObject> mapper = Elementary.GetIObjectMapper<DynamicObject>("products");
			int count = mapper.Count;
			
			DynamicObject product = new DynamicObject();
			product.Alias = "products";
			product["categoryid"] = 1;
			product["productname"] = "Test Product";
			product["supplierid"] = 1;

			// Run in a transaction and rollback so that database changes aren't saved
			using(TransactionScope scope = new TransactionScope())
			{
				mapper.Save(product);
				
				int newCount = mapper.Count;
				Assert.AreNotSame(count, newCount, "Object wasn't saved.");

				Console.WriteLine(mapper.GetObject(product["productid"]));
			}
		}

Here's the output. Note that we never set the ProductId, but Elementary saved the Product and then set the ProductId generated by the database:

[DynamicObject]
AssemblyName: Adapdev.Dynamic
FullName: northwind.products
Alias: products
productname: Test Product
supplierid: 1
productid: 684
categoryid: 1

You can also access child objects, such as the following n-n relationship:

		[Test]
		public void GetEmployeeWithChildren()
		{
			// Retrieves the mapping via object type
			// Elementary looks at the full object name and locates the corresponding node in the mappings file
			IObjectMapper<DynamicObject> mapper = Elementary.GetIObjectMapper<DynamicObject>("employees");
			
			// Retrieve supplier #1
			DynamicObject employee = mapper.GetObjectWithChildren(1);
			Assert.IsNotNull(employee["firstname"]);
			Assert.IsNotNull(employee["territories"], "No territories retrieved");
			Assert.AreEqual(2, (employee["territories"] as IList).Count, "Incorrect number of territories retrieved.");
			
			Console.WriteLine(employee);
			Console.WriteLine("Territories for " + employee["firstname"] + ":");
			foreach(DynamicObject territory in (employee["territories"] as IList))
			{
				Console.WriteLine(territory["territorydescription"]);
			}
		}

See the Northwind Samples project for a full set of examples.