2. 1-n Child Mappings (One to Many)

A 1-n mapping means that a parent object can have multiple child objects. In the Northwind example, the relationship between Supplier and Product is 1-n, since 1 Supplier can have many (n) Products. Let's continue building on our original example by adding a Supplier class. For now, we'll keep it simple:

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

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToNGenericInferred
{
    [ElementaryClass]
	public class Supplier
	{
        [Persist]
		public String CompanyName = "";
        
        [Persist]
		public int SupplierID = 0;

        [HasMany(typeof(Product))]
		public IList<Product> Products = null;

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

Now, let's write a test to make sure it works:

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

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToNGenericInferred
{
	/// <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 mapper = Elementary.GetIObjectMapper<Supplier>();
			
			// Retrieve supplier #1
			Supplier supplier = mapper.GetObjectWithChildren(1);

			Console.WriteLine(supplier);
			Console.WriteLine("Products for " + supplier.CompanyName + ":");
			foreach(Product product in supplier.Products)
			{
				Console.WriteLine(product.ProductName);
			}
		}
	}
}

This results in the following output:

Properties for: Supplier
	CompanyName(System.String): Exotic Liquids
	SupplierID(System.Int32): 1
	Products(System.Collections.Generic.IList`1
[Elementary.Northwind.Samples.Attributes.ChildMappingOneToNGenericInferred.Product]): 
System.Collections.Generic.List`1
[Elementary.Northwind.Samples.Attributes.ChildMappingOneToNGenericInferred.Product]

Products for Exotic Liquids:
Chai
Chang
Aniseed Syrup

All of the operations that are available for 1-1 mappings are also available for 1-n mappings. You can retrieve ALL Suppliers and their child Products. You can also add or update a Supplier and it's children. Let's look at example where we'll add a new Product and modify an existing one.

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

			// Get an existing Supplier
			Supplier supplier = mapper.GetObjectWithChildren(1);

			// Track the number of products assigned
			int count = supplier.Products.Count;
			
			// Add a new product
			Product product = new Product();
			product.CategoryID = 1;
			product.ProductName = "Test Product";
			product.SupplierID = 1;

			supplier.Products.Add(product);

			// Modify an existing product
			Product p = supplier.Products[0];
			int pId = p.ProductID;
			p.ProductName = "Bogus";

			// Run in a transaction and rollback so that database changes aren't saved
			using(TransactionScope scope = new TransactionScope())
			{
				mapper.SaveWithChildren(supplier);
				
				Supplier supplier2 = mapper.GetObjectWithChildren(1) as Supplier;

				// Make sure the new product was added
				int newCount = supplier2.Products.Count;
				Assert.AreNotSame(count, newCount, "Object wasn't saved.");
				Assert.AreEqual(newCount, count+1, "Invalid save.");

				// Make sure the modified product was updated
				Assert.AreEqual("Bogus", (supplier2.Products[0] as Product).ProductName, "Child wasn't updated.");
			}
		}

In the example above, we did the following:

  1. Retrieved Supplier #1

  2. Added a new Product

  3. Retrieved one of the existing Supplier Products and modified the Product name

  4. Saved the Supplier and all of its children

  5. Validated that the new Product was added

  6. Validated that the modified Product was correctly saved

2.1. One-To-Many Explained

Assigning 1-n relationships simply requires the [HasMany] attribute:

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

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToNGenericInferred
{
    [ElementaryClass]
	public class Supplier
	{
        [Persist]
		public String CompanyName = "";
        
        [Persist]
		public int SupplierID = 0;

        [HasMany(typeof(Product))]
		public IList<Product> Products = null;

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

Elementary infers all of the appripriate columns and tables where possible. To understand how this translates to SQL, essentially the following statement is generated for the Categories table:

SELECT ProductId, ProductName, SupplierId, CategoryId
FROM Products
WHERE Products.SupplierId = @supplierId
' childColumn maps to Products.SupplierId
' parentColumn maps to Suppliers.SupplierId, which results in the value for @supplierId 

The results are then translated into an IList of Product objects and assigned to the Supplier object.

If you need to define the tables and columns explicitly, you can do so:

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

namespace Elementary.Northwind.Samples.Attributes.ChildMappingOneToNGeneric
{
    [ElementaryClass(Table="suppliers")]
	public class Supplier
	{
        [Persist]
        public String CompanyName = "";
        [Persist]
        public int SupplierID = 0;

		[HasMany(typeof(Product),
                ChildColumn="supplierid", ParentColumn="supplierid")]
        public IList<Product> Products = null;

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

This tells Elementary what childColumn (Categories.supplierid) and what parentColumn (Products.supplierid) to use, rather than trying to infer it.