Complex Attributes
The Attribute tutorial described how the Mapper populates simple Model class properties, like numbers, strings, etc. This guide extends this information to account for complex objects that combine mapped values into a new object.
There are three types of complex objects handled by the Mapper:
- Model Properties, via the MapToModel attribute
- List Properties
- Shard Identifiers, via the ShardKey attributes
- Models that inherit from parent Models
The MapToModel Attribute
Some Model objects may have properties that are objects with their own properties, which also need to be mapped to the underlying data.
For example, you might have an Address object that contains street address, city, region, etc. Since this data is used for customer addresses, store addresses, vendor addresses, etc. you might have a single, shared Address model. The Customer, Store, and Vendor Models would all have an Address property of type Address Model.
The MapToModel attribute tells the Mapper that this property contains an object that should also be mapped.
public class Vendor
{
[MapToSqlInt("VendorId")]
public int VendorId { get; set; }
//include other properties...
[MapToModel]
public Address VendorAddress { get; set; }
}
public class Store
{
[MapToSqlInt("StoreId")]
public int StoreId { get; set; }
//include other properties...
[MapToModel]
public Address StoreAddress { get; set; }
}
public class Customer
{
[MapToSqlInt("CustomerId")]
public int CustomerId { get; set; }
//include other properties...
[MapToModel]
public Address CustomerAddress { get; set; }
}
public class Address
{
[MapToSqlNVarChar("StreetAddress", 255)]
public string StreetAddress { get; set; }
[MapToSqlNVarChar("Locality", 100)]
public string Locality { get; set; }
[MapToSqlNVarChar("Region", 100)]
public string Region { get; set; }
[MapToSqlNVarChar("PostalCode", 25)]
public string PostalCode { get; set; }
}
In this example, the Vendor, Store, and Customer Models each have a property with an Address Model type. When mapping Vendor database results, the Mapper will map the StreetAddress column or parameter to Vendor.VendorAddress.StreetAddress. Likewise for the Store and Customer Models.
In each case, the Address properties and attributes are simply included in the properties that the Mapper expects to see in the data results or parameters.
In short, MapToModel
simply extends the expected parameter or column lists with further values, but it offers the capability to reuse or encapsulate a column/parameter set in your code.
Null Values
The Mapper will automatically set the property to a new instance of the model object, if:
- The property value is null
- The property is settable (not readonly)
- The property model class has a default constructor
Otherwise, simply instantiate the property’s object when the base Model class is created.
public class Customer
{
[MapToModel]
public Address CustomerAddress { get; } = new Address();
}
As discussed earlier, the mapping attributes have a required argument — when set to true, a database null will cause the entire object to be null. This may not behave like you expect in this context: this does not cause the MapToModel property to become null; the entire parent model will be null.
Essentially, a MapToModel property simply extends the Model with additional properties. Consequently, a “required” column that has a null database value will cause the entire Model to be null, not just the MapToModel property.
Chaining MapToModel Properties
In the example above, suppose the Address Model also had a MapToModel property — perhaps a Coordinates
Model object. Theoretically, the Coordinates
Model itself could also have a MapToModel property; the Mapper should be able to resolve nearly any number of chained MapToModel properties. All of these attributes are included in the data mapping.
Lists
The Mapper can also map to properties that contain lists (List<Model>
or IList<Model>
) of Model objects. This allows for one-to-many relationships.
Mapping to a List works quite differently than MapToModel. The List data can only come from a data reader result. Because data readers can contain multiple results — which might be mapped to different list properties — the mapping and result order is specified at query time. For this reason, no attribute is required.
Models in a List may themselves have child lists, but unlike MapToModel properties, they will be ignored by the Mapper. In cases where a result set must be transformed into a complete Model hierarchy, you can use the Query methods to bypass the Mapper. This allows you to hydrate the results in your own code, optionally invoking Mapper on parts of the result stream.
List properties cannot be read-only.
For the details on how to populate Models with List<Model>
properties, see the discussion on querying.
MapShardKey Attributes
A ShardKey is a record identifier containing a shard Id and one or more record Ids. Essentially, it uniquely identifies a sharded record by combining the table key with the shard Id. Because the record identifier type is generic, you can use the ShardKey for any primary key or unique identity value. The shard id is always short/Int16/tinyint. ShardKey classes with multiple generic definitions can be used for tables that use up to four columns as compound keys.
The MapShardKey attribute is used to decorate properties of type ShardKey.
These are special types are reviewed in detail in the sharding section.
Next: Mapping Targets