Mappers - lobodava/artisan-orm GitHub Wiki
Mappers in Artisan.Orm are classes decorated with the MapperFor attribute. The conventional form is a static mapper class with public static methods; the class name does not matter. Keep one mapper registration per entity type. Use Microsoft.Data.SqlClient.SqlDataReader with Artisan.Orm.
The method fragments below belong inside one UserMapper class decorated with [MapperFor(typeof(User), RequiredMethod.All)] when all four methods are provided. For a read-only mapper, include only CreateObject and request RequiredMethod.CreateObject. See RequiredMethod for other combinations and compile-time mapper validation (Roslyn) for how the analyzer checks that contract in the editor and during builds.
Mapper methods work for two directions and each may have two static methods with reserved names:
- DataToObject direction — is to read data by SqlDataReader and return an object of
MapperFortype or anObjectRow:CreateObjectCreateObjectRow
- ObjectToData direction — is to create DataTable and populate it with an object data (for further passing it as a parameter to a stored procedure):
CreateDataTableCreateDataRow
CreateObject method reads data by SqlDataReader and returns an object of MapperFor type:
public static User CreateObject(SqlDataReader dr)
{
var i = 0;
return new User
{
Id = dr.GetInt32(i) ,
Login = dr.GetString(++i) ,
Name = dr.GetString(++i) ,
Email = dr.GetString(++i)
};
}The reader ordinals and getter types must match the actual SELECT columns; C# property declaration order does not establish this contract. This example assumes all four columns are non-null. For a nullable column, use IsDBNull or its nullable getter, such as GetStringNullable, and preserve the application's null semantics.
The SELECT for this mapper is:
select Id, Login, Name, Email from dbo.Users;CreateObjectRow method reads data by SqlDataReader and returns an ObjectRow:
public static ObjectRow CreateObjectRow(SqlDataReader dr)
{
var i = 0;
return new ObjectRow(4)
{
/* 0 - Id = */ dr.GetInt32(i) ,
/* 1 - Login = */ dr.GetString(++i) ,
/* 2 - Name = */ dr.GetString(++i) ,
/* 3 - Email = */ dr.GetString(++i)
};
}CreateDataTable method creates DataTable which corresponds to a user-defined table type already existing in a database by quantity, type and order:
public static DataTable CreateDataTable()
{
return new DataTable("dbo.UserTableType")
.AddColumn< Int32 >( "Id", isNullable: false )
.AddColumn< String >( "Login", isNullable: false )
.AddColumn< String >( "Name", isNullable: false )
.AddColumn< String >( "Email", isNullable: false );
}The corresponding user-defined table type:
create type dbo.UserTableType as table
(
Id int not null primary key clustered,
[Login] varchar(20) not null ,
Name nvarchar(50) not null ,
Email varchar(50) not null
);CreateDataRow method creates Object[] where items correspond to columns in DataTable by quantity and order:
public static Object[] CreateDataRow(User obj)
{
if (obj.Id == 0)
obj.Id = Int32NegativeIdentity.Next;
return new object[]
{
obj.Id ,
obj.Login ,
obj.Name ,
obj.Email
};
}Assigning a negative ID when Id == 0 is an optional graph-saving convention used by this sample, not a requirement for every mapper or SQL schema. If existing mappers prepare a graph during conversion, CreateDataRow can:
- assign Negative Identity for Id of new objects,
- propagate an object Id value to child items in its collections to make a link for a foreign key,
- assign integer ParentId from Parent object,
- control and change data before it gets to a table valued parameter.
When these callbacks change parent IDs or child foreign keys, materialize parent TVPs before child TVPs. AddTableParam converts the collection immediately; it does not assign identities or propagate relationships by itself. Alternatively, prepare the graph explicitly before creating any TVPs.
See more examples of Mappers in Tests project:
- MapperFor(typeof(User) in Users
- MapperFor(typeof(Record) in Records
- MapperFor(typeof(Record) in GrandRecords
- MapperFor(typeof(RecordType) in GrandRecords
- MapperFor(typeof(GrandRecord) in GrandRecords
- MapperFor(typeof(ChildRecord) in GrandRecords
See also:
and: