RDO.Data Overview
RDO.Data is the core of RDO.Net. It helps developers to build data and business layer of the your applications:
RDO.Data Architecture
Building on top of ADO.Net, RDO.Data implements relational schema and data into a comprehensive yet simple object model:
The following data objects are provided with rich set of properties, methods and events:
- Model/Model<T>: Defines the meta data of model, and declarative business logic such as data constraints, automatic calculated field and validation, which can be consumed for both database and local in-memory code.
- DataSet<T>: Stores hierarchical data locally and acts as domain model of your business logic. It can be conveniently exchanged with relational database in set-based operations (CRUD), or external system via JSON.
Db
: Defines the database session, which contains:- DbTable<T>: Permanent database tables for data storage;
- Instance methods of
Db
class to implement procedural business logic, using DataSet<T> objects as input/output. The business logic can be simple CRUD operations, or complex operation such as MRP calculation:- You can use DbQuery<T> objects to encapsulate data as reusable view, and/or temporary DbTable<T> objects to store intermediate result, to write stored procedure alike, set-based operations (CRUD) business logic.
- On the other hand, DataSet<T> objects, in addition to be used as input/output of your procedural business logic, can also be used to write in-memory code to implement your business logic locally.
- Since these objects are database agnostic, you can easily port your business logic into different relational databases.
- DbMock<T>: Easily mock the database in an isolated, known state for testing.
The following is an example of business layer implementation, to deal with sales orders in AdventureWorksLT
sample. Please note the example is just CRUD operations for simplicity, RDO.Data is capable of doing much more than it.
private async Task EnsureConnectionOpenAsync(CancellationToken ct)
{
if (Connection.State != ConnectionState.Open)
await OpenConnectionAsync(ct);
}
public async Task<DataSet<SalesOrderInfo>> GetSalesOrderInfoAsync(_Int32 salesOrderID, CancellationToken ct = default(CancellationToken))
{
var result = CreateQuery((DbQueryBuilder builder, SalesOrderInfo _) =>
{
builder.From(SalesOrderHeader, out var o)
.LeftJoin(Customer, o.FK_Customer, out var c)
.LeftJoin(Address, o.FK_ShipToAddress, out var shipTo)
.LeftJoin(Address, o.FK_BillToAddress, out var billTo)
.AutoSelect()
.AutoSelect(c, _.Customer)
.AutoSelect(shipTo, _.ShipToAddress)
.AutoSelect(billTo, _.BillToAddress)
.Where(o.SalesOrderID == salesOrderID);
});
await result.CreateChildAsync(_ => _.SalesOrderDetails, (DbQueryBuilder builder, SalesOrderInfoDetail _) =>
{
builder.From(SalesOrderDetail, out var d)
.LeftJoin(Product, d.FK_Product, out var p)
.AutoSelect()
.AutoSelect(p, _.Product)
.OrderBy(d.SalesOrderDetailID);
}, ct);
return await result.ToDataSetAsync(ct);
}
public async Task<int?> CreateSalesOrderAsync(DataSet<SalesOrderInfo> salesOrders, CancellationToken ct)
{
await EnsureConnectionOpenAsync(ct);
using (var transaction = BeginTransaction())
{
salesOrders._.ResetRowIdentifiers();
await SalesOrderHeader.InsertAsync(salesOrders, true, ct);
var salesOrderDetails = salesOrders.GetChild(_ => _.SalesOrderDetails);
salesOrderDetails._.ResetRowIdentifiers();
await SalesOrderDetail.InsertAsync(salesOrderDetails, ct);
await transaction.CommitAsync(ct);
return salesOrders.Count > 0 ? salesOrders._.SalesOrderID[0] : null;
}
}
public async Task UpdateSalesOrderAsync(DataSet<SalesOrderInfo> salesOrders, CancellationToken ct)
{
await EnsureConnectionOpenAsync(ct);
using (var transaction = BeginTransaction())
{
salesOrders._.ResetRowIdentifiers();
await SalesOrderHeader.UpdateAsync(salesOrders, ct);
await SalesOrderDetail.DeleteAsync(salesOrders, (s, _) => s.Match(_.FK_SalesOrderHeader), ct);
var salesOrderDetails = salesOrders.GetChild(_ => _.SalesOrderDetails);
salesOrderDetails._.ResetRowIdentifiers();
await SalesOrderDetail.InsertAsync(salesOrderDetails, ct);
await transaction.CommitAsync(ct);
}
}
public Task<int> DeleteSalesOrderAsync(DataSet<SalesOrderHeader.Key> dataSet, CancellationToken ct)
{
return SalesOrderHeader.DeleteAsync(dataSet, (s, _) => s.Match(_), ct);
}
RDO.Data Features, Pros and Cons
RDO.Data Features
- Comprehensive hierarchical data support.
- Rich declarative business logic support: constraints, automatic calculated filed, validations, etc, for both server side and client side.
- Comprehensive inter-table join/lookup support.
- Reusable view via DbQuery<T> objects.
- Intermediate result store via temporary DbTable<T> objects.
- Comprehensive JSON support, better performance because no reflection required.
- Fully customizable data types and user-defined functions.
- Built-in logging for database operations.
- Extensive support for testing.
- Rich design time tools support.
- And much more...
Pros
- Unified programming model for all scenarios. You have full control of your data and business layer, no magic black box.
- Your data and business layer is best balanced for both programmability and performance. Rich set of data objects are provided, no more object-relational impedance mismatch.
- Data and business layer testing is a first class citizen which can be performed easily - your application can be much more robust and adaptive to change.
- Easy to use. The APIs are clean and intuitive, with rich design time tools support.
- Rich feature and lightweight. The runtime
DevZest.Data.dll
is less than 500KB in size, whereasDevZest.Data.SqlServer
is only 108KB in size, without any 3rd party dependency. - The rich metadata can be consumed conveniently by other layer of your application such as the presentation layer.
Cons
- It's new. Although APIs are designed clean and intuitive, you or your team still need some time to get familiar with the framework. Particularly, your domain model objects are split into two parts: the Model/Model<T> objects and DataSet<T> objects. It's not complex, but you or your team may need some time to get used to it.
- To best utilize RDO.Data, your team should be comfortable with SQL, at least to an intermediate level. This is one of those situations where you have to take into account the make up of your team - people do affect architectural decisions.
- Although data objects are lightweight, there are some overhead compares to POCO objects, especially for the simplest scenarios. In terms of performance, It may get close to, but cannot beat native stored procedure.