-
Notifications
You must be signed in to change notification settings - Fork 601
Mapping Pocos
Mapping Pocos (aka Models/Entities), in our opinion, is super simple. However, as there's a lot of flexibility built-in, it might seem a little confusing to begin with. Please don't confuse complexity with flexibility. Because different methods of mapping suit different situations it's unlikely you'll use everything. Pick what works for you and the project. In many cases, simple turn-key convention mapping will work.
Note: The Standard mapper has been replaced with the newer Convention mapper, with regression tests in-place. The Convention mapper offers more flexibility in customizing the mapper's functionality, without having to "roll your own" using inheritance. While the Standard mapper will continue to be shipped and supported, we suggest that you try out the newer Convention mapper, as it may simplify your existing implementation.
A few common questions:
- Do I have to use attributes? No
- Can I use my own attributes? Yes (possible with either a custom mapper or an altered convention mapper)
- Can I register mappers per type/assembly? Yes
- Can I have different default mappers for each PetaPoco instance? Yes (however, note that mappers registered using the static
Mapper
methods are global) - Can I remove/unregister a mapper by type/assembly? Yes
The convention mapper carries over the conventions used by the standard mapper, but is also configurable. Having an easily configurable convention mapper fits nicely with fluent configuration. For example, the convention mapper makes altering the convention for PostgreSQL very simple, or customizing the naming convention for the table and column names, as shown below:
.UsingDefaultMapper<ConventionMapper>(m =>
{
// OrderLine => order_lines
m.InflectTableName = (inflector, tbl) => inflector.Pluralise(inflector.Underscore(tbl));
// OrderLineId => order_line_id
m.InflectColumnName = (inflector, col) => inflector.Underscore(col);
})
PetaPoco uses the following process when mapping tables:
- The convention mapper first checks if the class is decorated with
TableNameAttribute
to obtain the table name. If not found, theType.Name
for the class and is passed through theInflectTableName
hook (which by convention does nothing), and used as the database table name. - The mapper then checks if the class is decorated with
PrimaryKeyAttribute
. If found, the attribute's properties are used to determine the primary key column name, auto-increment flag, and (if Oracle) an optional sequence name. If found, table mapping ends here. - If not found, it attempts to find the first property in that class that fits the naming format
"Id"
,"{Type.Name}Id"
, or"{Type.Name}_Id"
, case-insensitive. If no property is found, the table is treated as one without a primary key, and table mapping ends here. - If a property fitting this naming convention is found, the
PropertyInfo.Name
for that property is passed through theInflectColumnName
hook (which by convention does nothing), and used as the primary key column name. - The property is then passed through
IsPrimaryKeyAutoIncrement
to determine whether the column is auto-incrementing. By convention, it will be true if thePropertyInfo.PropertyType
is of typelong
,ulong
,int
,uint
,short
, orushort
. - Finally, the property is passed through the
GetSequenceName
hook (which by convention does nothing).
Pseudocode for MapTable:
Does class have a TableNameAttribute?
Yes: table name = TableNameAttribute.Value
No: table name = call.InflectTableName(Type.Name)
Continue
Does class have a PrimaryKeyAttribute?
Yes:
primary key = PrimaryKeyAttribute.Value
auto increment = PrimaryKeyAttribute.AutoIncrement
sequence name = PrimaryKeyAttribute.SequenceName
No:
Does any property match "Id", "{Type.Name}Id" or "{Type.Name}_Id"?
Yes:
primary key = call.InflectColumnName(PropertyInfo.Name)
auto increment = call.IsPrimaryKeyAutoIncrement(PropertyInfo.PropertyType)
sequence name = call.GetSequenceName(Type, PropertyInfo)
No:
no primary key
Pseudocode for IsPrimaryKeyAutoIncrement:
Is property type `long`, `ulong`, `int`, `uint`, `short`, or `ushort`?
Yes: primary key is auto-increment
No: primary key is not auto-increment
PetaPoco uses the following process when mapping columns:
- The convention mapper first checks if the class containing the property is decorated with
ExplicitColumnsAttribute
. If it is, the current property must be decorated with either theColumnAttribute
orResultColumnAttribute
, otherwise the property is skipped. - The mapper then checks if the current property is decorated with the
IgnoreAttribute
. If it is, the property is skipped. - Next, the mapper maps the column name by checking for the
ColumnAttribute
orResultColumnAttribute
value. If decorated and set, this value is used. Otherwise, thePropertyInfo.Name
value is passed through theInflectColumnName
hook (which by convention does nothing). - If the property is decorated with the
ResultColumnAttribute
, theColumnInfo.ResultColumn
property is set to true.
Pseudocode for Map Column:
Does class have a ExplicitColumnsAttribute?
Yes:
Does property have a ColumnAttribute or ResultColumnAttribute?
Yes: Continue
No: End - property not mapped
No: Continue
Does property have a IgnoreAttribute?
Yes: End - property not mapped
No: Continue
Does property have a ColumnAttribute or ResultColumnAttribute?
Yes:
Does the attribute have Name value?
Yes: column name = Attribute.Name
No: column name = call.InflectColumnName(PropertyInfo.Name)
Continue
Is attribute a ResultColumnAttribute?
Yes: result column = true
No:
column name = call.InflectColumnName(PropertyInfo.Name)
Decorate your POCO class with this attribute to specify a specific DB table it should be mapped to.
Decorate your POCO class with this attribute to specify the primary key column. Additionally, specifies whether the column is auto incrementing and the optional sequence name for Oracle sequence columns.
Decorate your POCO class with this attribute to ignore all properties, except ones explicitly marked with a Column
or ResultColumn
attribute. The result is equivalent to adding the Ignore
attribute to all properties except the ones you want.
Used to explitly identify a property as a column. which can decorate a Poco property to mark the property as a column. It may also optionally supply the DB column name.
Note: when no column name is supplied and the Convention mapper is being used, the property name is passed through the InflectColumnName
hook.
Used to explicitly identify a property as a result only column. A result only column is a column that is only populated in queries and is not used for updates or inserts operations.
Note: By default, ResultColumns are not included in auto-generated SQL. To read a ResultColumn from the database, you must either specify IncludeInAutoSelect.Yes
when applying the attribute, or build the SELECT
statement yourself and specify the columns you want.
Any properties in your POCO class marked with this attribute will be ignored by PetaPoco. The result is equivalent to using the ExplicitColumns
class attribute in conjunction with the Column
/ResultColumn
attributes.
The global Mappers
class is a static helper class which is used to register mappings in a global manner. Any global registered mapping overrides a PetaPoco's instance default mapper (see Default Mapper Per PetaPoco Instance below).
The mapper class API, as it stands:
public static class Mappers
{
// Registers a mapper for all POCO types in a specific assembly.
public static void Register(Assembly assembly, IMapper mapper);
// Registers a mapper for a single POCO type.
public static void Register(Type type, IMapper mapper);
// Removes all mappers for all POCO types in a specific assembly.
public static void Revoke(Assembly assembly);
// Removes the mapper for a specific POCO type.
public static void Revoke(Type type);
// Removes an instance of a mapper.
public static void Revoke(IMapper mapper);
// Revokes all registered mappers.
public static void RevokeAll();
}
When using the fluent configuration or the constructor which accepts an instance of IMapper, a PetaPoco consumer is able to control the default mapper. Put simply, the default mapper is the mapper which is used when no mapper has been registered for the Poco (See Global mapper registrations above).
If no default mapper is supplied, the default mapper PetaPoco will use is the Convention mapper (without modification).
To create your own mapper, you'll need to extend the IMapper interface.
The contract, as it stands:
TableInfo GetTableInfo(Type pocoType);
ColumnInfo GetColumnInfo(PropertyInfo pocoProperty);
Func<object, object> GetFromDbConverter(PropertyInfo targetProperty, Type sourceType);
Func<object, object> GetToDbConverter(PropertyInfo sourceProperty);
For this method, the implementer must return a TableInfo
object populated using the details for the given Poco type, or null
if the mapper cannot map the given Poco type.
For this method, the implementer must return a ColumnInfo
object populated using the details for the given Poco column, or null
if the column is not mapped. A handy tip to get the parent poco's type is pocoProperty.DeclaringType
.
For these methods, the implementer may apply any conversions to<->from DB and Poco types. For instance, in SQLite, all numbers are stored as longs. In addition, there's no real support for a DateTime type, so it is common to store the value as a number using DateTime.Ticks
. Given that all numbers are stored as longs, a conversion step to and from is required - both the GetFromDbConverter
and GetToDbConverter
participate in fulfilling this.
If you need to control conversion on a per-field basis, see ValueConverters
Checkout SqliteDBTestProvider as an example. Another good example is the ConventionMapper
PetaPoco is proudly maintained by the Collaborating Platypus group and originally the brainchild of Brad Robinson