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:
Retrieved Supplier #1
Added a new Product
Retrieved one of the existing Supplier Products and modified the Product name
Saved the Supplier and all of its children
Validated that the new Product was added
Validated that the modified Product was correctly saved
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.