A full O/RM, focused on strong separation between the data structures and the business entity representation.
See our ethos for how and why Pho/rm is different to other O/RMs.
The wiki contains lots of useful examples of the various features, as well as a getting started guide.
Pho/rm supports:
- Entity data mapping
- Child entities
- Entity polymorphism
- All CRUD operations
- Transactions
- Logging unexpected behaviour
- Your DI framework
- And more!
Packages | ||
---|---|---|
IFY.Phorm.Core | ||
IFY.Phorm.SqlClient |
The are many, brilliant O/RM frameworks available using different paradigms for database interaction.
Many of these allow for rapid adoption by strongly-coupling to the storage schema at the expense of control over the efficiency of the query and future structural mutability.
As such solutions grow, it can become quickly difficult to evolve the underlying structures as well as to improve the way the data is accessed and managed.
Pho/rm was designed to provide a small and controlled surface between the business logic layer and the data layer by pushing the shaping of data to the data provider and encouraging the use of discrete contracts.
Our goal is to have a strongly-typed data surface and allow for a mutable physical data structure, where responsibility of the layers can be strictly segregated.
With this approach, the data management team can provide access contracts to meet the business logic requirements, which the implementing team can rely on without concern over the underlying structures and query efficiency.
flowchart RL
subgraph Database
D[(Data)]
V((vw))
SP((sp))
end
subgraph Application
O[DTO]
I[/Interface/]
end
D -->|Get| O;
D --> V -->|Get| O;
SP -->|From.Get| O;
O -.->|Call/From| I --> SP --> D;
For typical entity CRUD support, a Pho/rm solution would require a minimum of:
- Existing tables in the data source
- A POCO to represent the entity (DTO); ideally with a contract for each database action
- A stored procedure to fetch the entity
- At least one stored procedure to handle create, update, delete (though, ideally, one for each)
A simple Pho/rm use would have the structure:
CREATE TABLE [dbo].[Data] (
[Id] BIGINT NOT NULL PRIMARY KEY,
[Key] NVARCHAR(50) NOT NULL UNIQUE,
[Value] NVARCHAR(256) NULL
)
CREATE PROCEDURE [dbo].[usp_SaveData] (
@Key NVARCHAR(50),
@Value NVARCHAR(256),
@Id BIGINT = NULL OUTPUT
) AS
SET NOCOUNT ON
INSERT INTO [dbo].[Data] ([Key], [Value])
SELECT @Key, @Value
SET @Id = SCOPE_IDENTITY()
RETURN 1 -- Success
// DTO and contracts
[PhormContract(Name = "Data")] // Name of underlying table (optional)
class DataItem : ISaveData
{
public long Id { get; set; }
public string Key { get; set; } = string.Empty;
public string? Value { get; set; }
}
interface ISaveData : IPhormContract
{
long Id { set; } // Output
string Key { get; }
string? Value { get; }
}
// Configure Pho/rm session to SQL Server
IPhormSession session = new SqlPhormSession(connectionString);
// Get all existing records from the table
DataItem[] allData = session.Get<DataItem[]>()!; // Table dbo.Data
// Add a new record to the table, getting back the new id
var newItem = new { Id = ContractMember.Out<long>(), Key = "Name", Value = "T Ester" };
int result = session.Call<ISaveData>(newItem); // Procedure dbo.usp_SaveData
DataItem? itemById = session.Get<DataItem>(new { Id = newItem.Id }); // Table dbo.Data
DataItem? itemByKey = session.Get<DataItem>(new { Key = "Name" }); // Table dbo.Data
IPhormSession
// Calling a contract
int Call(string contractName, object? args = null);
int Call<TActionContract>(object? args = null);
Task<int> CallAsync(string contractName, object? args = null, CancellationToken cancellationToken = CancellationToken.None);
Task<int> CallAsync<TActionContract>(object? args = null, CancellationToken cancellationToken = CancellationToken.None);
// Fetching from a DTO definition (table, view)
TResult? Get<TResult>(object? args = null);
Task<TResult?> GetAsync<TResult>(object? args = null, CancellationToken cancellationToken = CancellationToken.None);
// Fetching from a named procedure
From(string contractName, object? args = null)
TResult? Get<TResult>();
Task<TResult?> GetAsync<TResult>(CancellationToken cancellationToken = CancellationToken.None);
// Resultset filtering
Where<TEntity>(Expression<Func<TEntity, bool>> predicate)
IEnumerable<TEntity> GetAll();
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancellationToken = CancellationToken.None);
// Fetching from a contract definition (procedure, table, view)
From<TActionContract>(object? args = null)
TResult? Get<TResult>();
Task<TResult?> GetAsync<TResult>(CancellationToken cancellationToken = CancellationToken.None);
// Resultset filtering
Where<TEntity>(Expression<Func<TEntity, bool>> predicate)
IEnumerable<TEntity> GetAll();
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancellationToken = CancellationToken.None);