1. 1-1 Child Mappings (One to One)

A 1-1 mapping means that you have an object with exactly one corresponding object. A good example of this would be a Product which can only be listed in one Category. Continuing our original example, here is our new Category object:

using System;
using Adapdev.Text;
using Elementary.Attributes;

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToOneInferred
{
    [ElementaryClass]
	public class Category
	{
        [Persist(Column="categoryid")]
		public int Id;
        [Persist(Column="categoryname")]
		public string Name;
        [Persist]
		public string Description;
        [Persist]
		public byte[] Picture;

		public override string ToString() 
		{
			return StringUtil.ToString(this);
		}
	}
}

And our modified Product object:

using System;
using Adapdev.Text;
using Elementary.Attributes;

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToOneInferred
{   
    [ElementaryClass]
	public class Product
	{
        [Persist]
		public int CategoryID = 0;
        [Persist]
		public int ProductID = 0;
        [Persist]
		public string ProductName = "";
        [Persist]
		public int SupplierID = 0;
        [HasOne]
		public Category Category = null;

		public override string ToString() 
		{
			return StringUtil.ToString(this);
		}
	}
}

This means that, when we load a Product, we can look at it's Category property to find out more about the category that it's in. For example:

Product p = new Product();
... // set values
Console.WriteLine(p.Category.Name); // outputs the category name

Lastly, let's see how we can access this child property. Create the following Test class:

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

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToOneGenericInferred
{
	/// <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 GetObjectWithChildren()
		{
			IObjectMapper<Product> mapper = Elementary.GetIObjectMapper<Product>();
			
			// Retrieve product #1
			Product product = mapper.GetObjectWithChildren(1);
			
			Console.WriteLine(product);
		}
	}
}

When you run the test, you should see the following output:

Properties for: Product
	CategoryID(System.Int32): 1
	ProductID(System.Int32): 1
	ProductName(System.String): Chai
	SupplierID(System.Int32): 1
	Category(Elementary.Northwind.Samples.Attributes.ChildMappingOneToOneGeneric.Category): 
Properties for: Category
	Id(System.Int32): 1
	Name(System.String): Beverages
	Description(System.String): Soft drinks, coffees, teas, beers, and ales
	Picture(System.Byte[]): System.Byte[]

As you can see, Elementary not only retrieved the Product information, but also the information for all child properties (in this case, we've only defined the Category property).

You can still call the GetObject method, and it will ignore child properties.

		[Test]
		public void GetObject()
		{
			IObjectMapper<Product> mapper = Elementary.GetIObjectMapper<Product>();
			
			// Retrieve product #1
			Product product = mapper.GetObject(1);
			
			Console.WriteLine(product);
		}
Output:
Properties for: Product
	CategoryID(System.Int32): 1
	ProductID(System.Int32): 1
	ProductName(System.String): Chai
	SupplierID(System.Int32): 1
	Category(Elementary.Northwind.Samples.ChildMappingOneToOneGeneric.Category): 

You can also retrieve ALL Products and their child properties

		[Test]
		public void GetCollectionWithChildren()
		{
			IObjectMapper<Product> mapper = Elementary.GetIObjectMapper<Product>();

			List<Product> al = mapper.GetCollectionWithChildren();
			
			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]);
		}
Output:
Loaded 77 products.

First product:
Properties for: Product
	CategoryID(System.Int32): 1
	ProductID(System.Int32): 1
	ProductName(System.String): Chai
	SupplierID(System.Int32): 1
	Category(Elementary.Northwind.Samples.Attributes.ChildMappingOneToOneGeneric.Category): 
Properties for: Category
	Id(System.Int32): 1
	Name(System.String): Beverages
	Description(System.String): Soft drinks, coffees, teas, beers, and ales
	Picture(System.Byte[]): System.Byte[]

You can also save an object and its children:

		[Test]
		public void SaveWithChildren()
		{
			// Run in a trans so it doesn't commit
			using(TransactionScope trans = new TransactionScope())
			{
				IObjectMapper mapper = Elementary.GetIObjectMapper<Product>();
				Product e = new Product();
				e.ProductName = "Johns Sandwiches";
				
				Category cat = new Category();
				cat.Name = "J";
				cat.Description = "Some Description";

				e.Category = cat;

				mapper.SaveWithChildren(e);
				mapper.Delete(e);
			}
		}

Warning

If you run the above SaveWithChildren test, it will fail in Northwind due to the fact that the Product.CategoryId isn't being updated when the child Category is saved. This is resolved using SyncSource and SyncTarget properties, which are explained later. See the samples for a working version.

1.1. One-to-One Explained

In the example above, we tagged Category with the [HasOne] attribute. This tells Product that it has one Category (1-1). To understand how this translates to SQL, essentially the following statement is generated for the Categories table:

SELECT CategoryId, CategoryName, Description, Picture
FROM Categories
WHERE Categories.CategoryId = @categoryId
' childColumn maps to Categories.CategoryId
' parentColumn maps to Products.CategoryId, which results in the value for @categoryId 

The results are then translated into a Category object and assigned to the Product object.

You'll notice that we're using inferrence again. Elementary runs through several scenarios to find the matching columns for the parent and child tables. If you want to specify them instead, you can do the following:

using System;
using Adapdev.Text;
using Elementary.Attributes;

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToOneInferred
{   
    [ElementaryClass]
	public class Product
	{
        [Persist]
		public int CategoryID = 0;
        [Persist]
		public int ProductID = 0;
        [Persist]
		public string ProductName = "";
        [Persist]
		public int SupplierID = 0;
        [HasOne(ChildColumn="categoryid", ParentColumn="categoryid")]
		public Category Category = null;

		public override string ToString() 
		{
			return StringUtil.ToString(this);
		}
	}
}

This tells Elementary explicitly what the parent (Products table) and child (Categories table) columns are that should be used.