Packages
The module contains the following packages:
- Build - factory to create PostreSQL persistence components.
- Connect - connection component to configure a connection to a PostgreSQL database.
- Persistence - abstract persistence components to perform basic CRUD operations.
Use
Install the dotnet package as
dotnet add package PipServices4.Postgres
As an example, lets create persistence for the following data object:
using PipServices4.Commons.Data;
class MyObject : IIdentifiable<string>
{
public string Id { get; set; }
public string key;
public int value;
}
The persistence component shall implement the following interface with a basic set of CRUD operations.
interface IMyPersistance
{
void GetPageByFilter(string correlationId, FilterParams filter, PagingParams paging);
void GetOneById(string correlationId, string id);
void GetOneByKey(string correlationId, string id);
void Create(string correlationId, MyObject item);
void Update(string correlationId, MyObject item);
void DeleteById(string correlationId, string id);
}
To implement postgresql persistence component you shall inherit IdentifiablePostgresPersistence
.
Most CRUD operations will come from the base class. You only need to override GetPageByFilter
method with a custom filter function.
And implement a GetOneByKey
custom persistence method that doesn’t exist in the base class.
class MyPostgresPersistence: IdentifiablePostgresPersistence<MyObject, string>
{
public MyPostgresPersistence(): base("myobjects")
{
this.AutoCreateObject("CREATE TABLE myobjects (id VARCHAR(32) PRIMARY KEY, key VARCHAR(50), value VARCHAR(255)");
IndexOptions options = new IndexOptions();
Dictionary<string, bool> keys = new Dictionary<string, bool>{
{"unique", true},
};
this.EnsureIndex("myobjects_key", keys, options);
}
private string ComposeFilter(FilterParams filter)
{
filter = filter != null ? filter : new FilterParams();
List<string> criteria = new List<string>();
string id = filter.GetAsNullableString("id");
if (id != null)
criteria.Add("id='" + id + "'");
string tempIds = filter.GetAsNullableString("ids");
if (tempIds != null)
{
string[] ids = tempIds.Split(",");
filters.Add("id IN ('" + string.Join("','", ids) + "')");
}
string key = filter.GetAsNullableString("key");
if (key != null)
criteria.Add("key='" + key + "'");
return criteria.Count > 0 ? string.Join(" AND ", criteria) : null;
}
public async Task<DataPage<MyObject>> GetPageByFilter(string correlationId, FilterParams filter, PagingParams paging)
{
return await base.GetPageByFilterAsync(correlationId, this.ComposeFilter(filter), paging, select: "id");
}
public async Task<MyObject> GetOneByKey(string correlationId, string key)
{
string query = "SELECT * FROM " + this.QuoteIdentifier(this._tableName) + " WHERE \"key\"=$1";
List<string> param = new List<string> { key };
var result = await this.ExecuteReaderAsync(query, param);
AnyValueMap item = result != null && result[0] != null ? result[0]: null;
if (item == null)
this._logger.Trace(correlationId, "Nothing found from %s with key = %s", this._tableName, key);
else
this._logger.Trace(correlationId, "Retrieved from %s with key = %s", this._tableName, key);
item = this.ConvertToPublic(item);
return item;
}
}
Alternatively you can store data in non-relational format using IdentificableJsonPostgresPersistence
.
It stores data in tables with two columns - id
with unique object id and data
with object data serialized as JSON.
To access data fields you shall use data->'field'
expression or data->>'field'
expression for string values.
class MyPostgresPersistence : IdentifiableJsonPostgresPersistence<MyObject, string>
{
public MyPostgresPersistence() : base("myobjects")
{
this.EnsureTable("VARCHAR(32)", "JSONB");
IndexOptions options = new IndexOptions();
Dictionary<string, bool> keys = new Dictionary<string, bool>{
{ "data->>'key'", true },
};
this.EnsureIndex("myobjects_key", keys, options);
}
private string ComposeFilter(FilterParams filter)
{
filter = filter != null ? filter : new FilterParams();
List<string> criteria = new List<string>();
string id = filter.GetAsNullableString("id");
if (id != null)
criteria.Add("data->>'id'='" + id + "'");
string tempIds = filter.GetAsNullableString("ids");
if (tempIds != null)
{
string[] ids = tempIds.Split(",");
filters.Add("data->>'id' IN ('" + string.Join("','", ids) + "')");
}
string key = filter.GetAsNullableString("key");
if (key != null)
criteria.Add("data->>'key'='" + key + "'");
return criteria.Count > 0 ? string.Join(" AND ", criteria) : null;
}
public async Task<DataPage<MyObject>> GetPageByFilter(string correlationId, FilterParams filter, PagingParams paging)
{
return await base.GetPageByFilterAsync(correlationId, this.ComposeFilter(filter), paging, "id").Result;
}
public async Task<MyObject> GetOneByKey(string correlationId, string key)
{
string query = "SELECT * FROM " + this.QuoteIdentifier(this._tableName) + " WHERE data->>'key'=$1";
List<string> param = new List<string> { key };
var result = await this.ExecuteReaderAsync(query, param);
AnyValueMap item = result != null && result[0] != null ? result[0] : null;
if (item == null)
this._logger.Trace(correlationId, "Nothing found from %s with key = %s", this._tableName, key);
else
this._logger.Trace(correlationId, "Retrieved from %s with key = %s", this._tableName, key);
item = this.ConvertToPublic(item);
return item;
}
}
The configuration for your microservice that includes postgresql persistence may look the following way:
...
{{#if POSTGRES_ENABLED}}
- descriptor: pip-services:connection:postgres:con1:1.0
connection:
uri: {{{POSTGRES_SERVICE_URI}}}
host: {{{POSTGRES_SERVICE_HOST}}}{{#unless POSTGRES_SERVICE_HOST}}localhost{{/unless}}
port: {{POSTGRES_SERVICE_PORT}}{{#unless POSTGRES_SERVICE_PORT}}5432{{/unless}}
database: {{POSTGRES_DB}}{{#unless POSTGRES_DB}}app{{/unless}}
credential:
username: {{POSTGRES_USER}}
password: {{POSTGRES_PASS}}
- descriptor: myservice:persistence:postgres:default:1.0
dependencies:
connection: pip-services:connection:postgres:con1:1.0
table: {{POSTGRES_TABLE}}{{#unless POSTGRES_TABLE}}myobjects{{/unless}}
{{/if}}
...