Skip to content

New Methods to define references as collections "SetCollectionOf"

thiscode edited this page Apr 18, 2013 · 1 revision

Map to Collections

Imagine the "ReferenceProperty" is a Collection Type:

class ExampleTargetClass
{
	int IntProperty;
	List<ExampleReferenceClass> ReferenceProperty;
}
class ExampleReferenceClass
{
	int IntRefProperty;
	string StringRefProperty;
}

Now you can say to map a property to a CsvField and add objects to the collection instead assign them directly to the property:

class MapExampleTargetClass : CsvClassMap<ExampleTargetClass>
{
	public MapExampleTargetClass()
	{
		Map( x => x.IntProperty ).Name("Column 1");
		References<MapExampleReferenceClass>( x => x.ReferenceProperty ).SetCollectionOf( typeof(ExampleReferenceClass) );
	}
}
class MapExampleReferenceClass : CsvClassMap<ExampleReferenceClass>
{
	public MapExampleReferenceClass()
	{
		Map( x => x.IntRefProperty ).Name("Column 2");
		Map( x => x.StringRefProperty ).Name("Column 3");
	}
}

The only requirement is, the collection type must have an method "Add". Such a method have for example all collection inherited from "ICollection".

If we put this and the "Constructor" feature of a ReferencePropertyMap-Class together, we can map csv files directly to an entity framework POCO object with navigation properties. A class like this will be defined as follows:

public class Order
{
    public Order()
    {
        this.OrderItems = new HashSet<OrderItem>();
    }
    public int OrderId { get; set; }
    public virtual ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
    public int OrderItemId { get; set; }
}

As you see a 1:N navigation property is defined via an "ICollection". The concrete implementiation used is "HashSet". Now you can built a mapping like this:

class MapOrder : CsvClassMap<Order>
{
	public MapOrder()
	{
		Map( x => x.OrderId ).Name("OrderId");
		References<MapOrderItem>(x => x.OrderItems).IsCollectionOf(typeof(OrderItem));
	}
}
class MapOrderItem : CsvClassMap<OrderItem>
{	
	public MapOrder()
	{
		//Do not forget this:
		Constructor = Expression.New(typeof(HashSet<OrderItem>));
		Map( x => x.OrderItemId ).Name("OrderItemId");
	}
}

Map multiple Columns to one Collection

Some thoughts on the method "Reference", and using it multiple times:

Using the method "Reference" multiple times, with the same property, will not throw an error but it make no sense (so far):

Reference(x=>x.IntProperty).Name("Column1");
Reference(x=>x.IntProperty).Name("Column2");

This design will always use the value of "Column2" to map to the property. But introducing the new features it start to make sense to use it like this. Imagine a csv file with 3 columns: "ItemId", "ItemNameInEnglish", "ItemNameInSpanish". The database will, of course, have a 1:N relation between Item and Itemnames in different languages. The class you will have to map generated by the entity framework is:

public class Item
{
    public Item()
    {
        this.ItemNames = new HashSet<ItemName>();
    }
    public int ItemId { get; set; }
    public virtual ICollection<ItemName> ItemNames { get; set; }
}
public class ItemName
{
    public int LanguageId { get; set; }
    public string Name { get; set; }
}

Now you can map a csv file this way:

class MapItem : CsvClassMap<Item>
{
	public MapItem()
	{
		Map( x => x.ItemId ).Name("ItemId");
		References<MapItemNameInEnglish>(x => x.ItemNames).IsCollectionOf(typeof(ItemName));
		References<MapItemNameInSpanish>(x => x.ItemNames).IsCollectionOf(typeof(ItemName));
	}
}
class MapItemNameInEnglish : CsvClassMap<ItemName>
{	
	public MapItemNameInEnglish()
	{
		Constructor = Expression.New(typeof(HashSet<ItemName>));
		Map( x => x.LanguageId ).SetConstantValue(1); //Here you have to set the language id of "english"
		Map( x => x.Name ).Name("ItemNameInEnglish");
	}
}
class MapItemNameInSpanish : CsvClassMap<ItemName>
{	
	public MapItemNameInSpanish()
	{
		Constructor = Expression.New(typeof(HashSet<ItemName>));
		Map( x => x.LanguageId ).SetConstantValue(2); //Here you have to set the language id of "spanish"
		Map( x => x.Name ).Name("ItemNameInSpanish");
	}
}

For me it is a relevant feature. If in future versions reference should be restricted to be used for every property only one time this would not work.

Generate Mapping Classes on the Fly

Now imagine a csv file with 6 columns, specifying names in different languages. Or 6 columns specifying images of a product you want to map into a collection. You would have to write 10 different classes. To avoid this the method "References" now have a optional "params object[]" parameter. With this you can specify parameters for constructing your CsvClassMap. Now you have to write only one class:

class MapItem : CsvClassMap<Item>
{
	Map( x => x.ItemId ).Name("ItemId");
	References<MapItemName>(x => x.ItemNames, "English", 1).IsCollectionOf(typeof(ItemName));
	References<MapItemName>(x => x.ItemNames, "Spanish", 2).IsCollectionOf(typeof(ItemName));
	References<MapItemName>(x => x.ItemNames, "Greek", 3).IsCollectionOf(typeof(ItemName));
	References<MapItemName>(x => x.ItemNames, "German", 4).IsCollectionOf(typeof(ItemName));
	References<MapItemName>(x => x.ItemNames, "Italian", 5).IsCollectionOf(typeof(ItemName));
	References<MapItemName>(x => x.ItemNames, "French", 6).IsCollectionOf(typeof(ItemName));
}
class MapItemName : CsvClassMap<ItemName>
{	
	public MapItemName(string columnSuffix, int languageId)
	{
		Constructor = Expression.New(typeof(HashSet<ItemName>));
		Map( x => x.LanguageId ).SetConstantValue(languageId);
		Map( x => x.Name ).Name("ItemNameIn"+columnSuffix);
	}
}

Clone this wiki locally