-
Notifications
You must be signed in to change notification settings - Fork 5
ORM Dao
The BaseDao
class is the abstract super class from which all Dao classes inherit. This class exposes the following methods that can be overriden in case htis is necessary to add extra business logic that needs to be executed in this layer. The following methods are available to be overriden by derived partial
classes:
- BeforeUpdate: perform any operations required to implement business logic in the ORM layer before the Update operation is executed.
- AfterUpdate: perform any operations required to implement business logic in the ORM layer after the Update operation is executed.
- BeforeDelete: perform any operations required to implement business logic in the ORM layer before the Delete operation is executed.
- AfterDelete: perform any operations required to implement business logic in the ORM layer after the Delete operation is executed.
- BeforeWrite: perform any operations required to implement business logic in the ORM layer before the Write operation is executed.
- AfterWrite: perform any operations required to implement business logic in the ORM layer after the Write operation is executed.
For each class in the COMET Data Model a corresponding Dao
class is generated. For each concrete class an IDao
interface is generated as well that is then also implemented by the correspding Dao
class.
The
IDao
interfaces are injected into the Service classes of theCDP4WebServices.API
project using Autofac property injection.
The code snippets below show the generated ElementDefinitionDao class with an explanation of the structure of the class
Each generated Dao
inherits from its super class according to the COMET Data Model. Concrete COMET Data Model classess also implement the corresponding IDao
interface.
The example below shows that the ElementDefinitionDao
derives from ElementBaseDao
and implements the IElementDefinitionDao
interface.
public partial class ElementDefinitionDao : ElementBaseDao, IElementDefinitionDao
The read method is used to read the data from the database, in this case the availalbe ElementDefinition
instances. The following parameters need to be provided:
- NpgsqlTransaction transaction: the Npgsql transaction that is used to read from the database.
- string partition: the database partition the data is read from, this is the name of the PostgreSQL Schema that contains the data
- IEnumerable ids: a list of unique identifiers that can be used to filter the requested data. In case this list is null or empty the data will not be filtered and all instances will be returned
- bool isCachedDtoReadEnabledAndInstant: a value that indicates whether the data is to be read from the the cache, or from the relational tables. When reading from the relational tables (captured by predefined views in the database) it is possible to get data from the past. The cache tables contain the most recent data.
public virtual IEnumerable<CDP4Common.DTO.ElementDefinition> Read(NpgsqlTransaction transaction, string partition, IEnumerable<Guid> ids = null, bool isCachedDtoReadEnabledAndInstant = false)
{
using (var command = new NpgsqlCommand())
{
var sqlBuilder = new System.Text.StringBuilder();
if (isCachedDtoReadEnabledAndInstant)
{
sqlBuilder.AppendFormat("SELECT \"Jsonb\" FROM \"{0}\".\"ElementDefinition_Cache\"", partition);
if (ids != null && ids.Any())
{
sqlBuilder.Append(" WHERE \"Iid\" = ANY(:ids)");
command.Parameters.Add("ids", NpgsqlDbType.Array | NpgsqlDbType.Uuid).Value = ids;
}
sqlBuilder.Append(";");
command.Connection = transaction.Connection;
command.Transaction = transaction;
command.CommandText = sqlBuilder.ToString();
// log the sql command
this.LogCommand(command);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var thing = this.MapJsonbToDto(reader);
if (thing != null)
{
yield return thing as ElementDefinition;
}
}
}
}
else
{
sqlBuilder.AppendFormat("SELECT * FROM \"{0}\".\"ElementDefinition_View\"", partition);
if (ids != null && ids.Any())
{
sqlBuilder.Append(" WHERE \"Iid\" = ANY(:ids)");
command.Parameters.Add("ids", NpgsqlDbType.Array | NpgsqlDbType.Uuid).Value = ids;
}
sqlBuilder.Append(";");
command.Connection = transaction.Connection;
command.Transaction = transaction;
command.CommandText = sqlBuilder.ToString();
// log the sql command
this.LogCommand(command);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return this.MapToDto(reader);
}
}
}
}
}
The MapToDto
method converts the data that is returned from the database as a recordset into a DTOs specific for the Dao
, in this case instances of ElementDefinition
. Due to the fact that the Dao classes are generated fromt the COMET Data Model (as are the PostgreSQL SQL schema) it makes sense to have these mappings hard-coded and not rely on reflection or existing ORM implementations such as Dapper, NHibernate or Entity Framework.
public virtual CDP4Common.DTO.ElementDefinition MapToDto(NpgsqlDataReader reader)
{
string tempModifiedOn;
string tempName;
string tempShortName;
var valueDict = (Dictionary<string, string>)reader["ValueTypeSet"];
var iid = Guid.Parse(reader["Iid"].ToString());
var revisionNumber = int.Parse(valueDict["RevisionNumber"]);
var dto = new CDP4Common.DTO.ElementDefinition(iid, revisionNumber);
dto.Alias.AddRange(Array.ConvertAll((string[])reader["Alias"], Guid.Parse));
dto.Category.AddRange(Array.ConvertAll((string[])reader["Category"], Guid.Parse));
dto.ContainedElement.AddRange(Array.ConvertAll((string[])reader["ContainedElement"], Guid.Parse));
dto.Definition.AddRange(Array.ConvertAll((string[])reader["Definition"], Guid.Parse));
dto.ExcludedDomain.AddRange(Array.ConvertAll((string[])reader["ExcludedDomain"], Guid.Parse));
dto.ExcludedPerson.AddRange(Array.ConvertAll((string[])reader["ExcludedPerson"], Guid.Parse));
dto.HyperLink.AddRange(Array.ConvertAll((string[])reader["HyperLink"], Guid.Parse));
dto.Owner = Guid.Parse(reader["Owner"].ToString());
dto.Parameter.AddRange(Array.ConvertAll((string[])reader["Parameter"], Guid.Parse));
dto.ParameterGroup.AddRange(Array.ConvertAll((string[])reader["ParameterGroup"], Guid.Parse));
dto.ReferencedElement.AddRange(Array.ConvertAll((string[])reader["ReferencedElement"], Guid.Parse));
if (valueDict.TryGetValue("ModifiedOn", out tempModifiedOn))
{
dto.ModifiedOn = Utils.ParseUtcDate(tempModifiedOn);
}
if (valueDict.TryGetValue("Name", out tempName))
{
dto.Name = tempName.UnEscape();
}
if (valueDict.TryGetValue("ShortName", out tempShortName))
{
dto.ShortName = tempShortName.UnEscape();
}
return dto;
}
The Write
method is use to INSERT new data into the database. Property values are persisted as an Hstore
, reference properties (associations, composite aggregation) are persisted as unique identifiers using columns and foreign key constraints. The following parameters need to be provided:
- NpgsqlTransaction transaction: the Npgsql transaction that is used to write to the database.
- string partition: the database partition the data is written to, this is the name of the PostgreSQL Schema that contains the data
- CDP4Common.DTO.DefinedThing definedThing: the instance of DefinedThing that is to be persisted
- Thing container: the container of the DefinedThing, which is an Iteration.
public virtual bool Write(NpgsqlTransaction transaction, string partition, CDP4Common.DTO.DefinedThing definedThing, CDP4Common.DTO.Thing container = null)
{
bool isHandled;
var valueTypeDictionaryAdditions = new Dictionary<string, string>();
var beforeWrite = this.BeforeWrite(transaction, partition, definedThing, container, out isHandled, valueTypeDictionaryAdditions);
if (!isHandled)
{
beforeWrite = beforeWrite && base.Write(transaction, partition, definedThing, container);
var valueTypeDictionaryContents = new Dictionary<string, string>
{
{ "Name", !this.IsDerived(definedThing, "Name") ? definedThing.Name.Escape() : string.Empty },
{ "ShortName", !this.IsDerived(definedThing, "ShortName") ? definedThing.ShortName.Escape() : string.Empty },
}.Concat(valueTypeDictionaryAdditions).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
using (var command = new NpgsqlCommand())
{
var sqlBuilder = new System.Text.StringBuilder();
sqlBuilder.AppendFormat("INSERT INTO \"{0}\".\"DefinedThing\"", partition);
sqlBuilder.AppendFormat(" (\"Iid\", \"ValueTypeDictionary\")");
sqlBuilder.AppendFormat(" VALUES (:iid, :valueTypeDictionary);");
command.Parameters.Add("iid", NpgsqlDbType.Uuid).Value = definedThing.Iid;
command.Parameters.Add("valueTypeDictionary", NpgsqlDbType.Hstore).Value = valueTypeDictionaryContents;
command.CommandText = sqlBuilder.ToString();
command.Connection = transaction.Connection;
command.Transaction = transaction;
this.ExecuteAndLogCommand(command);
}
}
return this.AfterWrite(beforeWrite, transaction, partition, definedThing, container);
}
-
In case extra business logic is required in the ORM before or after the data is written to the database, the
BeforeWrite
andAfterWrite
can be implemented in a hand-coded partialElementDefinitionDao
class. -
Persisting the properties of the superclass of (in this
ElementBase
) is delegated to the Dao super class (in this caseElementBaseDao
). -
persistence of reference properties (that are not composite aggregation) is delegated to the appropriate metod (in this case
AddReferencedElement
)
The Update
method is use to UPDATE data in the database. The following parameters need to be provided:
- NpgsqlTransaction transaction: the Npgsql transaction that is used to write to the database.
- string partition: the database partition the data is written to, this is the name of the PostgreSQL Schema that contains the data
- CDP4Common.DTO.DefinedThing definedThing: the instance of DefinedThing that is to be updated (persisted)
- Thing container: the container of the DefinedThing, which is an Iteration.
public virtual bool Update(NpgsqlTransaction transaction, string partition, CDP4Common.DTO.ElementDefinition elementDefinition, CDP4Common.DTO.Thing container = null)
{
bool isHandled;
var valueTypeDictionaryAdditions = new Dictionary<string, string>();
var beforeUpdate = this.BeforeUpdate(transaction, partition, elementDefinition, container, out isHandled, valueTypeDictionaryAdditions);
if (!isHandled)
{
beforeUpdate = beforeUpdate && base.Update(transaction, partition, elementDefinition, container);
using (var command = new NpgsqlCommand())
{
var sqlBuilder = new System.Text.StringBuilder();
sqlBuilder.AppendFormat("UPDATE \"{0}\".\"ElementDefinition\"", partition);
sqlBuilder.AppendFormat(" SET \"Container\"");
sqlBuilder.AppendFormat(" = :container");
sqlBuilder.AppendFormat(" WHERE \"Iid\" = :iid;");
command.Parameters.Add("iid", NpgsqlDbType.Uuid).Value = elementDefinition.Iid;
command.Parameters.Add("container", NpgsqlDbType.Uuid).Value = container.Iid;
command.CommandText = sqlBuilder.ToString();
command.Connection = transaction.Connection;
command.Transaction = transaction;
this.ExecuteAndLogCommand(command);
}
}
return this.AfterUpdate(beforeUpdate, transaction, partition, elementDefinition, container);
}
-
In case extra business logic is required in the ORM before or after the data is written to the database, the
BeforeUpdate
andAfterUpdate
can be implemented in a hand-coded partialElementDefinitionDao
class. -
Persisting the properties of the superclass of (in this case
ElementBase
) is delegated to the Dao super class (in this caseElementBaseDao
).
The Delete
method is use to DELETE data from the database. The following parameters need to be provided:
- NpgsqlTransaction transaction: the Npgsql transaction that is used to write to the database.
- string partition: the database partition the data is deleted from, this is the name of the PostgreSQL Schema that contains the data
- Guid iid: the unique identifier of the ElementDefinition that is to be deleted.
public override bool Delete(NpgsqlTransaction transaction, string partition, Guid iid)
{
bool isHandled;
var beforeDelete = this.BeforeDelete(transaction, partition, iid, out isHandled);
if (!isHandled)
{
beforeDelete = beforeDelete && base.Delete(transaction, partition, iid);
}
return this.AfterDelete(beforeDelete, transaction, partition, iid);
}
-
In case extra business logic is required in the ORM before or after the data is deleted from the database, the
BeforeDelete
andAfterDelete
can be implemented in a hand-coded partialElementDefinitionDao
class. -
Deleting the instance of the superclass of (in this case
ElementBase
) is delegated to the Dao super class (in this caseElementBaseDao
).
copyright @ Starion Group S.A.