-
Notifications
You must be signed in to change notification settings - Fork 600
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, and because different methods of mapping suit different situations it's unlikely you'll use everything. Pick what works for you and the project. Most of the time, simple turn-key convention mapping will work for you!
Note: The Standard mapper has been replaced with newer Convention mapper (Regression tests in-place). The primary reason for the replacement is so that more scenarios can be supported without needing inheritance/"roll your own" to alter functionality. The Standard mapper will continue to be shipped and supported. However, we suggest a look at the newer convention mapper as it may simplify existing code.
A few common questions:
- Do I have to use attributes? No
- Can I register mappers per type/assembly? Yes
- Can I remove a registration per type/assembly? Yes
- Can I have different default mappers for PetaPoco instance? Yes (However, mappers registered through
Mappers
are global) - Can I use my own attributes? Yes (this possible with a custom mapper or altered convention mapper)
The convention mapper carries over the conventions used by the standard mapper, but the key difference is that the convention is now 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.
.UsingDefaultMapper<ConventionMapper>(m =>
{
// OrderLine becomes order_lines
m.InflectTableName = (inflector, tn) => inflector.Pluralise(inflector.Underscore(tn));
// OrderLineId becomes order_line_id
m.InflectColumnName = (inflector, cn) => inflector.Underscore(cn);
})
When mapping the table info, the convention mapper will first try and find a TableNameAttribute
for the DB table name, or when not found the table name will be the Type.Name
, with the Type.Name
value being passed through the InflectTableName
hook (by convention does nothing). Next, the mapper will then look for a PrimaryKeyAttribute
to get the primary key column name, auto increment flag and optional sequence name (Oracle). When no PrimaryKeyAttribute
can be found, it will try and find the first Poco property fitting the naming format (casing does not matter) Id
, Type.Name + "Id" or
Type.Name + "_Id". Should a property fitting this naming convention be found, the PropertyInfo.Name
is passed through the InflectColumnName
(by convention does nothing) hook and used as the DB table column name. The property is then passed through the IsPrimaryKeyAutoIncrement
for a decision of whether the column is an auto-incrementing column or not, which by convention will be true if the PropertyInfo.PropertyType
is any type of these types long, ulong, int, uint, short or ushort. Lastly, the property is passed through the GetSequenceName
hook, which by convention does nothing.
Pseudo code - map table
Does type have a TableNameAttribute?
Table name = TableNameAttribute.Value
Else
Table name = call.InflectTableName(Type.Name)
Continue
call.MapPrimaryKey
Pseudo code - map primary key
Does type have a PrimaryKeyAttribute?
Primary Key = PrimaryKeyAttribute.Value
Auto Increment = PrimaryKeyAttribute.AutoIncrement
Sequence Name = PrimaryKeyAttribute.SequenceName
Else does any property match "Id", Type.Name + "Id" or Type.Name + "_Id"
Primary Key = call.InflectTableName(PropertyInfo.Name)
Auto Increment = call.IsPrimaryKeyAutoIncrement(PropertyInfo.PropertyType)
Sequence Name = call.GetSequenceName(Type, PropertyInfo)
Else
No primary key
Pseudo code - is primary key autoincrement
Is property type any of these Long, ULong, Int, UInt, Short or UShort
Yes - is auto increment
Else
No - no is not auto increment
Pseduo code - Get sequence name/Inflect table name/Inflect column name
Does nothing
When mapping the column info, the convention mapper will first check if the parent class is decorated with the ExplicitColumnsAttribute
. If the parent is decorated, it will then require the current property to be decorated with either the ColumnAttribute
or ResultColumnAttribute
, else no mapping is produced. Then the mapper will check if the current property is decorated with the IgnoreAttribute
, which when found will not produce a mapping. The mapper will next map the column name by checking for the ColumnAttribute
or ResultColumnAttribute
value, if decorated and set, else the PropertyInfo.Name
with the value being passed through the InflectColumnName
hook (by convention does nothing) will be used. Where the property was decorated with the ResultColumnAttribute
, the ColumnInfo.ResultColumn
property will be flagged.
Pseudo code - map column
Does parent have ExplicitColumnsAttribute?
Does property not have either ColumnAttribute or ResultColumnAttribute?
End = Do not map
Continue
Continue
Does property have IgnoreAttribute?
End = Do not map
Continue
Does property have ColumnAttribute or ResultColumnAttribute
Does the attribute have Name value?
Column name = Attribute.Name
Else
Column name = call.InflectColumnName(PropertyInfo.Name)
Continue
Is attribute a ResultColumnAttribute?
Result column = true
Else
Column name = call.InflectColumnName(PropertyInfo.Name)
Pseudo code - Inflect column name
Does nothing
TableName
Is an attribute, which when applied to a Poco class, specifies the DB table name which it maps to.
PrimaryKey
Is an attribute, which when applied to a Poco class, specifies the primary key column. Additionally, specifies whether the column is auto incrementing and the optional sequence name for Oracle sequence columns.
ExplicitColumns
Is an attribute which, when applied to a Poco class, states all columns must be explicitly mapped using either the Column or ResultColumn attribute.
Column
Is an attribute which can decorate a Poco property to mark the property as a column. It may also optionally supply the DB column name.
ResultColumn
Is an attribute which can decorate a Poco 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: when no column name is supplied and the Convention mapper is being used, the property name is passed through the InflectColumnName
hook.
Note: ResultColumns are not included in auto-generated SQL. To read a ResultColumn from the database, you must explicitly state all columns required in a complete SELECT statement.
Ignore
Is an attribute which can decorate a Poco property to ensure PetaPoco does not map column and therefore ignores the column.
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 below Default mapper per PetaPoco instance
.
The mapper class API, as it stands is:
public static class Mappers
{
public static void Register(Assembly assembly, IMapper mapper)
public static void Register(Type type, IMapper mapper)
public static void Revoke(Assembly assembly)
public static void Revoke(Type type)
public static void Revoke(IMapper mapper)
public static void RevokeAll()
}
Register(Assembly assembly, IMapper mapper) Registers a mapper for all types in a specific assembly.
Register(Type type, IMapper mapper) Registers a mapper for a single POCO type.
Revoke(Assembly assembly) Remove all mappers for all types in a specific assembly.
Revoke(Type type) Remove the mapper for a specific type.
Revoke(IMapper mapper) Revoke an instance of a mapper.
RevokeAll() Revokes all registered mappers.
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 above Global mapper registrations.
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);
GetTableInfo
For this method, the implementer must a TableInfo object populated using the details for the given Poco type, or null if the mapper cannot map the given Poco type.
GetColumnInfo
For this method, the implementer must return a ColumnInfo object populated using the details for the given Poco column or null. A handy tip to get the parent poco's type is pocoProperty.DeclaringType
. The returning of null means the column is not mapped.
GetFromDbConverter and GetToDbConverter
For these methods, the implementer may apply any conversions to/from DB types/Poco types. For instance, in Sqlite all numbers are stored as longs. In addition, there's no real support for DateTime, so it can be easier to store the value as a number using the DateTime.Ticks
. Given that all numbers are stored as longs, a conversion step to and from is required and 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
public class Note
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public string Text { get; set; }
}
[ExplicitColumns]
[TableName("Orders")]
[PrimaryKey("Id")]
public class Order
{
[Column]
public int Id { get; set; }
[Column]
public Guid PersonId { get; set; }
[Column]
public string PoNumber { get; set; }
[Column]
public DateTime CreatedOn { get; set; }
[Column]
public string CreatedBy { get; set; }
[Column("OrderStatus")]
}
[TableName("OrderLines")]
[PrimaryKey("Id")]
public class OrderLine
{
[Column]
public int Id { get; set; }
[Column]
public int OrderId { get; set; }
[Column(Name = "Qty")]
public short Quantity { get; set; }
[Column]
public decimal SellPrice { get; set; }
[ResultColumn]
public decimal Total { get; set; }
}
[TableName("People")]
[PrimaryKey("Id", AutoIncrement = false)]
public class Person
{
[Column]
public Guid Id { get; set; }
[Column(Name = "FullName")]
public string Name { get; set; }
[Column]
public long Age { get; set; }
[Column]
public int Height { get; set; }
[Column]
public DateTime? Dob { get; set; }
[Ignore]
public string NameAndAge => $"{Name} is of {Age}";
}
[TableName("TransactionLogs")]
public class TransactionLog
{
public string Description { get; set; }
public DateTime CreatedOn { get; set; }
}
PetaPoco is proudly maintained by the Collaborating Platypus group and originally the brainchild of Brad Robinson