Welcome to RDO.Net

RDO.Net (Relational Data Objects for .Net) is an open source framework under MIT license to handle data in .Net platform, consists of the following libraries and tools:

image

Why RDO.Net

Enterprise application, typically backed by a relational database, has decades of history. Today's enterprise applications are unnecessarily complex and heavyweight, due to the following technical constraints:

  • Object-Relational Mapping (ORM, O/RM, and O/R mapping tool), as the core of enterprise applications, is still The Vietnam of Computer Science. Particularly, these difficulties are referred to as the object-relational impedance mismatch.
  • Database testing, still stays on principles and guidelines. No widely practical use yet. Refactoring or changing an enterprise application is time consuming and error prone.
  • Existing data presentation solutions are far from ideal. Taking MVVM for example: it can be overkill for simple UI; in bigger cases, it can be hard to design the ViewModel up front in order to get the right amount of generality. Refactoring or changing data presentation code is time consuming and error prone.

The above challenges impose great burdens for developing and further changing an enterprise application. Many frameworks are trying to solve these problems however they are all far from ideal. RDO.Net is the first solution to these problems in an integral, not an after-thought way (strongly recommended reading through):

  • Enterprise Application, the Direction
  • Data and Business Layer, the New Way
  • Presentation Layer, the New Way

In the end, your application follows your business in a no-more-no-less basis - it adapts to your business, not vice versa:

  • Your application is 100% strongly typed from database to GUI, all in clean C#/VB.Net code. Refactoring or changing your code is much easier than ever before.
  • Your data and business layer is best balanced for both programmability and performance. Rich set of data objects such as Model, Db, DbTable, DbQuery and DataSet 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.
  • A one-for-all, fully customizable data presenter is provided to handle presentation logic including layout, data binding and data validation, all consumed in clean C#/VB.Net code (no XAML needed). You don't need complex controls such as ListBox, TreeView, DataGrid any more. You UI code is greatly simplified because you can reuse all the presentation logic.
  • And much more with a lightweight runtime - you only need to add several dlls into your application, size ranged from tens to several hundereds KBs.

A Taste of RDO.Net

A fully featured sample application, AdventureWorksLT, together with others, is provided to demonstrate the use of RDO.Net:

image

The Model

  • C#
  • VB.Net
using DevZest.Data;
using DevZest.Data.Annotations;
using DevZest.Data.SqlServer;

namespace DevZest.Samples.AdventureWorksLT
{
    [Computation(nameof(ComputeLineTotal))]
    [CheckConstraint(nameof(CK_SalesOrderDetail_OrderQty), typeof(UserMessages), nameof(UserMessages.CK_SalesOrderDetail_OrderQty), Description = "Check constraint [OrderQty] > (0)")]
    [CheckConstraint(nameof(CK_SalesOrderDetail_UnitPrice), typeof(UserMessages), nameof(UserMessages.CK_SalesOrderDetail_UnitPrice), Description = "Check constraint [UnitPrice] >= (0.00)")]
    [CheckConstraint(nameof(CK_SalesOrderDetail_UnitPriceDiscount), typeof(UserMessages), nameof(UserMessages.CK_SalesOrderDetail_UnitPriceDiscount), Description = "Check constraint [UnitPriceDiscount] >= (0.00)")]
    [DbIndex(nameof(IX_SalesOrderDetail_ProductID), Description = "Nonclustered index.")]
    public class SalesOrderDetail : BaseModel<SalesOrderDetail.PK>
    {
        [DbPrimaryKey("PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID", Description = "Clustered index created by a primary key constraint.")]
        public sealed class PK : CandidateKey
        {
            public PK(_Int32 salesOrderID, _Int32 salesOrderDetailID)
                : base(salesOrderID, salesOrderDetailID)
            {
            }
        }

        public class Key : Key<PK>
        {
            static Key()
            {
                Register((Key _) => _.SalesOrderID, _SalesOrderID);
                Register((Key _) => _.SalesOrderDetailID, _SalesOrderDetailID);
            }

            protected override PK CreatePrimaryKey()
            {
                return new PK(SalesOrderID, SalesOrderDetailID);
            }

            public _Int32 SalesOrderID { get; private set; }

            public _Int32 SalesOrderDetailID { get; private set; }
        }

        public static readonly Mounter<_Int32> _SalesOrderID = RegisterColumn((SalesOrderDetail _) => _.SalesOrderID);
        public static readonly Mounter<_Int32> _SalesOrderDetailID = RegisterColumn((SalesOrderDetail _) => _.SalesOrderDetailID);
        public static readonly Mounter<_Int16> _OrderQty = RegisterColumn((SalesOrderDetail _) => _.OrderQty);
        public static readonly Mounter<_Int32> _ProductID = RegisterColumn((SalesOrderDetail _) => _.ProductID);
        public static readonly Mounter<_Decimal> _UnitPrice = RegisterColumn((SalesOrderDetail _) => _.UnitPrice);
        public static readonly Mounter<_Decimal> _UnitPriceDiscount = RegisterColumn((SalesOrderDetail _) => _.UnitPriceDiscount);
        public static readonly Mounter<_Decimal> _LineTotal = RegisterColumn((SalesOrderDetail _) => _.LineTotal);

        public SalesOrderDetail()
        {
        }

        protected sealed override PK CreatePrimaryKey()
        {
            return new PK(SalesOrderID, SalesOrderDetailID);
        }

        private SalesOrderHeader.PK _fk_salesOrderHeader;
        public SalesOrderHeader.PK FK_SalesOrderHeader
        {
            get { return _fk_salesOrderHeader ?? (_fk_salesOrderHeader = new SalesOrderHeader.PK(SalesOrderID)); }
        }

        private Product.PK _fk_product;
        public Product.PK FK_Product
        {
            get { return _fk_product ?? (_fk_product = new Product.PK(ProductID)); }
        }

        [DbColumn(Description = "Primary key. Foreign key to SalesOrderHeader.SalesOrderID.")]
        public _Int32 SalesOrderID { get; private set; }

        [Identity]
        [DbColumn(Description = "Primary key. One incremental unique number per product sold.")]
        public _Int32 SalesOrderDetailID { get; private set; }

        [Required]
        [DbColumn(Description = "Quantity ordered per product.")]
        public _Int16 OrderQty { get; private set; }

        [Required]
        [DbColumn(Description = "Product sold to customer. Foreign key to Product.ProductID.")]
        public _Int32 ProductID { get; private set; }

        [Required]
        [SqlMoney]
        [DbColumn(Description = "Selling price of a single product.")]
        public _Decimal UnitPrice { get; private set; }

        [Required]
        [SqlMoney]
        [DefaultValue(typeof(decimal), "0", Name = "DF_SalesOrderDetail_UnitPriceDiscount")]
        [DbColumn(Description = "Discount amount.")]
        public _Decimal UnitPriceDiscount { get; private set; }

        [Required]
        [SqlMoney]
        [DbColumn(Description = "Per product subtotal. Computed as UnitPrice * (1 - UnitPriceDiscount) * OrderQty.")]
        public _Decimal LineTotal { get; private set; }

        [_Computation]
        private void ComputeLineTotal()
        {
            LineTotal.ComputedAs((UnitPrice * (_Decimal.Const(1) - UnitPriceDiscount) * OrderQty).IfNull(_Decimal.Const(0)));
        }

        [_CheckConstraint]
        private _Boolean CK_SalesOrderDetail_OrderQty
        {
            get { return OrderQty > _Decimal.Const(0); }
        }

        [_CheckConstraint]
        private _Boolean CK_SalesOrderDetail_UnitPrice
        {
            get { return UnitPrice >= _Decimal.Const(0); }
        }

        [_CheckConstraint]
        private _Boolean CK_SalesOrderDetail_UnitPriceDiscount
        {
            get { return UnitPriceDiscount >= _Decimal.Const(0); }
        }

        [_DbIndex]
        private ColumnSort[] IX_SalesOrderDetail_ProductID => new ColumnSort[] { ProductID };
    }
}
<Computation("ComputeLineTotal")>
<CheckConstraint("CK_SalesOrderDetail_OrderQty", GetType(My.UserMessages), NameOf(My.UserMessages.CK_SalesOrderDetail_OrderQty), Description:="Check constraint [OrderQty] > (0)")>
<CheckConstraint("CK_SalesOrderDetail_UnitPrice", GetType(My.UserMessages), NameOf(My.UserMessages.CK_SalesOrderDetail_UnitPrice), Description:="heck constraint [UnitPrice] >= (0.00)")>
<CheckConstraint("CK_SalesOrderDetail_UnitPriceDiscount", GetType(My.UserMessages), NameOf(My.UserMessages.CK_SalesOrderDetail_UnitPriceDiscount), Description:="Check constraint [UnitPriceDiscount] >= (0.00)")>
<DbIndex("IX_SalesOrderDetail_ProductID", Description:="Nonclustered index.")>
Public Class SalesOrderDetail
    Inherits BaseModel(Of PK)

    <DbPrimaryKey("PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID", Description:="Clustered index created by a primary key constraint.")>
    Public NotInheritable Class PK
        Inherits CandidateKey

        Public Sub New(salesOrderID As _Int32, salesOrderDetailID As _Int32)
            MyBase.New(salesOrderID, salesOrderDetailID)
        End Sub
    End Class

    Public Class Key
        Inherits Key(Of PK)

        Shared Sub New()
            Register(Function(x As Key) x.SalesOrderID, _SalesOrderID)
            Register(Function(x As Key) x.SalesOrderDetailID, _SalesOrderDetailID)
        End Sub

        Protected Overrides Function CreatePrimaryKey() As PK
            Return New PK(SalesOrderID, SalesOrderDetailID)
        End Function

        Private m_SalesOrderID As _Int32
        Public Property SalesOrderID As _Int32
            Get
                Return m_SalesOrderID
            End Get
            Private Set
                m_SalesOrderID = Value
            End Set
        End Property

        Private m_SalesOrderDetailID As _Int32
        Public Property SalesOrderDetailID As _Int32
            Get
                Return m_SalesOrderDetailID
            End Get
            Private Set
                m_SalesOrderDetailID = Value
            End Set
        End Property
    End Class

    Public Shared ReadOnly _SalesOrderID As Mounter(Of _Int32) = RegisterColumn(Function(x As SalesOrderDetail) x.SalesOrderID)
    Public Shared ReadOnly _SalesOrderDetailID As Mounter(Of _Int32) = RegisterColumn(Function(x As SalesOrderDetail) x.SalesOrderDetailID)
    Public Shared ReadOnly _OrderQty As Mounter(Of _Int16) = RegisterColumn(Function(x As SalesOrderDetail) x.OrderQty)
    Public Shared ReadOnly _ProductID As Mounter(Of _Int32) = RegisterColumn(Function(x As SalesOrderDetail) x.ProductID)
    Public Shared ReadOnly _UnitPrice As Mounter(Of _Decimal) = RegisterColumn(Function(x As SalesOrderDetail) x.UnitPrice)
    Public Shared ReadOnly _UnitPriceDiscount As Mounter(Of _Decimal) = RegisterColumn(Function(x As SalesOrderDetail) x.UnitPriceDiscount)
    Public Shared ReadOnly _LineTotal As Mounter(Of _Decimal) = RegisterColumn(Function(x As SalesOrderDetail) x.LineTotal)

    Protected NotOverridable Overrides Function CreatePrimaryKey() As PK
        Return New PK(SalesOrderID, SalesOrderDetailID)
    End Function

    Private m_FK_SalesOrderHeader As SalesOrderHeader.PK
    Public ReadOnly Property FK_SalesOrderHeader As SalesOrderHeader.PK
        Get
            If m_FK_SalesOrderHeader Is Nothing Then
                m_FK_SalesOrderHeader = New SalesOrderHeader.PK(SalesOrderID)
            End If
            Return m_FK_SalesOrderHeader
        End Get
    End Property

    Private m_FK_Product As Product.PK
    Public ReadOnly Property FK_Product As Product.PK
        Get
            If m_FK_Product Is Nothing Then
                m_FK_Product = New Product.PK(ProductID)
            End If
            Return m_FK_Product
        End Get
    End Property

    Private m_SalesOrderID As _Int32
    <DbColumn(Description:="Primary key. Foreign key to SalesOrderHeader.SalesOrderID.")>
    Public Property SalesOrderID As _Int32
        Get
            Return m_SalesOrderID
        End Get
        Private Set
            m_SalesOrderID = Value
        End Set
    End Property

    Private m_SalesOrderDetailID As _Int32
    <Identity>
    <DbColumn(Description:="Primary key. One incremental unique number per product sold.")>
    Public Property SalesOrderDetailID As _Int32
        Get
            Return m_SalesOrderDetailID
        End Get
        Private Set
            m_SalesOrderDetailID = Value
        End Set
    End Property

    Private m_OrderQty As _Int16
    <Required>
    <DbColumn(Description:="Quantity ordered per product.")>
    Public Property OrderQty As _Int16
        Get
            Return m_OrderQty
        End Get
        Private Set
            m_OrderQty = Value
        End Set
    End Property

    Private m_ProductID As _Int32
    <Required>
    <DbColumn(Description:="Product sold to customer. Foreign key to Product.ProductID.")>
    Public Property ProductID As _Int32
        Get
            Return m_ProductID
        End Get
        Private Set
            m_ProductID = Value
        End Set
    End Property

    Private m_UnitPrice As _Decimal
    <Required>
    <SqlMoney>
    <DbColumn(Description:="Selling price of a single product.")>
    Public Property UnitPrice As _Decimal
        Get
            Return m_UnitPrice
        End Get
        Private Set
            m_UnitPrice = Value
        End Set
    End Property

    Private m_UnitPriceDiscount As _Decimal
    <Required>
    <SqlMoney>
    <DefaultValue(GetType(Decimal), "0", Name:="DF_SalesOrderDetail_UnitPriceDiscount")>
    <DbColumn(Description:="Discount amount.")>
    Public Property UnitPriceDiscount As _Decimal
        Get
            Return m_UnitPriceDiscount
        End Get
        Private Set
            m_UnitPriceDiscount = Value
        End Set
    End Property

    Private m_LineTotal As _Decimal
    <Required>
    <SqlMoney>
    <DbColumn(Description:="Per product subtotal. Computed as UnitPrice * (1 - UnitPriceDiscount) * OrderQty.")>
    Public Property LineTotal As _Decimal
        Get
            Return m_LineTotal
        End Get
        Private Set
            m_LineTotal = Value
        End Set
    End Property

    <_Computation>
    Private Sub ComputeLineTotal()
        LineTotal.ComputedAs((UnitPrice * (_Decimal.[Const](1) - UnitPriceDiscount) * OrderQty).IfNull(_Decimal.[Const](0)))
    End Sub

    <_CheckConstraint>
    Private ReadOnly Property CK_SalesOrderDetail_OrderQty As _Boolean
        Get
            Return OrderQty > _Decimal.Const(0)
        End Get
    End Property

    <_CheckConstraint>
    Private ReadOnly Property CK_SalesOrderDetail_UnitPrice As _Boolean
        Get
            Return UnitPrice >= _Decimal.Const(0)
        End Get
    End Property

    <_CheckConstraint>
    Private ReadOnly Property CK_SalesOrderDetail_UnitPriceDiscount As _Boolean
        Get
            Return UnitPriceDiscount >= _Decimal.Const(0)
        End Get
    End Property

    <_DbIndex>
    Private ReadOnly Property IX_SalesOrderDetail_ProductID As ColumnSort()
        Get
            Return New ColumnSort() {ProductID}
        End Get
    End Property
End Class

The code of model can be manipulated in Model Visualizer tool window in Visual Studio:

image

The Database

  • C#
  • VB.Net
using DevZest.Data;
using DevZest.Data.Annotations;
using DevZest.Data.SqlServer;
using System;
using System.Data.SqlClient;

namespace DevZest.Samples.AdventureWorksLT
{
    public partial class Db : SqlSession
    {
        public Db(string connectionString, Action<Db> initializer = null)
            : base(CreateSqlConnection(connectionString))
        {
            initializer?.Invoke(this);
        }

        private static SqlConnection CreateSqlConnection(string connectionString)
        {
            if (string.IsNullOrEmpty(connectionString))
                throw new ArgumentNullException(nameof(connectionString));
            return new SqlConnection(connectionString);
        }
#if !DEPLOY
        // For unit tests
        public Db(SqlVersion sqlVersion)
            : base(new SqlConnection())
        {
            SqlVersion = sqlVersion;
        }
#endif

        public Db(SqlConnection sqlConnection)
            : base(sqlConnection)
        {
        }

        private DbTable<Address> _address;
        [DbTable("[SalesLT].[Address]", Description = "Street address information for customers.")]
        public DbTable<Address> Address
        {
            get { return GetTable(ref _address); }
        }

        private DbTable<Customer> _customer;
        [DbTable("[SalesLT].[Customer]", Description = "Customer information.")]
        public DbTable<Customer> Customer
        {
            get { return GetTable(ref _customer); }
        }

        private DbTable<CustomerAddress> _customerAddress;
        [DbTable("[SalesLT].[CustomerAddress]", Description = "Cross-reference table mapping customers to their address(es).")]
        [Relationship(nameof(FK_CustomerAddress_Customer_CustomerID), Description = "Foreign key constraint referencing Customer.CustomerID.")]
        [Relationship(nameof(FK_CustomerAddress_Address_AddressID), Description = "Foreign key constraint referencing Address.AddressID.")]
        public DbTable<CustomerAddress> CustomerAddress
        {
            get { return GetTable(ref _customerAddress); }
        }

        [_Relationship]
        private KeyMapping FK_CustomerAddress_Customer_CustomerID(CustomerAddress _)
        {
            return _.FK_Customer.Join(Customer._);
        }

        [_Relationship]
        private KeyMapping FK_CustomerAddress_Address_AddressID(CustomerAddress _)
        {
            return _.FK_Address.Join(Address._);
        }

        private DbTable<ProductCategory> _productCategory;
        [DbTable("[SalesLT].[ProductCategory]", Description = "High-level product categorization.")]
        [Relationship(nameof(FK_ProductCategory_ProductCategory_ParentProductCategoryID_ProductCategoryID), Description = "Foreign key constraint referencing ProductCategory.ProductCategoryID.")]
        public DbTable<ProductCategory> ProductCategory
        {
            get { return GetTable(ref _productCategory); }
        }

        [_Relationship]
        private KeyMapping FK_ProductCategory_ProductCategory_ParentProductCategoryID_ProductCategoryID(ProductCategory _)
        {
            return _.FK_ParentProductCategory.Join(_);
        }

        private DbTable<ProductModel> _productModel;
        [DbTable("[SalesLT].[ProductModel]")]
        public DbTable<ProductModel> ProductModel
        {
            get { return GetTable(ref _productModel); }
        }

        private DbTable<ProductDescription> _productDescription;
        [DbTable("[SalesLT].[ProductDescription]", Description = "Product descriptions in several languages.")]
        public DbTable<ProductDescription> ProductDescription
        {
            get { return GetTable(ref _productDescription); }
        }

        private DbTable<ProductModelProductDescription> _productModelProductDescription;
        [DbTable("[SalesLT].[ProductModelProductDescription]", Description = "Cross-reference table mapping product descriptions and the language the description is written in.")]
        [Relationship(nameof(FK_ProductModelProductDescription_ProductModel_ProductModelID), Description = "Foreign key constraint referencing ProductModel.ProductModelID.")]
        [Relationship(nameof(FK_ProductModelProductDescription_ProductDescription_ProductDescriptionID), Description = "Foreign key constraint referencing ProductDescription.ProductDescriptionID.")]
        public DbTable<ProductModelProductDescription> ProductModelProductDescription
        {
            get { return GetTable(ref _productModelProductDescription); }
        }

        [_Relationship]
        private KeyMapping FK_ProductModelProductDescription_ProductModel_ProductModelID(ProductModelProductDescription _)
        {
            return _.FK_ProductModel.Join(ProductModel._);
        }

        [_Relationship]
        private KeyMapping FK_ProductModelProductDescription_ProductDescription_ProductDescriptionID(ProductModelProductDescription _)
        {
            return _.FK_ProductDescription.Join(ProductDescription._);
        }

        private DbTable<Product> _product;
        [DbTable("[SalesLT].[Product]", Description = "Products sold or used in the manfacturing of sold products.")]
        [Relationship(nameof(FK_Product_ProductModel_ProductModelID))]
        [Relationship(nameof(FK_Product_ProductCategory_ProductCategoryID))]
        public DbTable<Product> Product
        {
            get { return GetTable(ref _product); }
        }

        [_Relationship]
        private KeyMapping FK_Product_ProductModel_ProductModelID(Product _)
        {
            return _.FK_ProductModel.Join(ProductModel._);
        }

        [_Relationship]
        private KeyMapping FK_Product_ProductCategory_ProductCategoryID(Product _)
        {
            return _.FK_ProductCategory.Join(ProductCategory._);
        }

        private DbTable<SalesOrderHeader> _salesOrderHeader;
        [DbTable("[SalesLT].[SalesOrderHeader]", Description = "General sales order information.")]
        [Relationship(nameof(FK_SalesOrderHeader_Customer_CustomerID))]
        [Relationship(nameof(FK_SalesOrderHeader_Address_BillTo_AddressID))]
        [Relationship(nameof(FK_SalesOrderHeader_Address_ShipTo_AddressID))]
        public DbTable<SalesOrderHeader> SalesOrderHeader
        {
            get { return GetTable(ref _salesOrderHeader); }
        }

        [_Relationship]
        private KeyMapping FK_SalesOrderHeader_Customer_CustomerID(SalesOrderHeader _)
        {
            return _.FK_Customer.Join(Customer._);
        }

        [_Relationship]
        private KeyMapping FK_SalesOrderHeader_Address_BillTo_AddressID(SalesOrderHeader _)
        {
            return _.FK_BillToCustomerAddress.Join(CustomerAddress._);
        }

        [_Relationship]
        private KeyMapping FK_SalesOrderHeader_Address_ShipTo_AddressID(SalesOrderHeader _)
        {
            return _.FK_ShipToCustomerAddress.Join(CustomerAddress._);
        }

        private DbTable<SalesOrderDetail> _salesOrderDetail;
        [DbTable("[SalesLT].[SalesOrderDetail]", Description = "Individual products associated with a specific sales order. See SalesOrderHeader.")]
        [Relationship(nameof(FK_SalesOrderDetail_SalesOrderHeader))]
        [Relationship(nameof(FK_SalesOrderDetail_Product))]
        public DbTable<SalesOrderDetail> SalesOrderDetail
        {
            get { return GetTable(ref _salesOrderDetail); }
        }

        [_Relationship]
        private KeyMapping FK_SalesOrderDetail_SalesOrderHeader(SalesOrderDetail _)
        {
            return _.FK_SalesOrderHeader.Join(SalesOrderHeader._);
        }

        [_Relationship]
        private KeyMapping FK_SalesOrderDetail_Product(SalesOrderDetail _)
        {
            return _.FK_Product.Join(Product._);
        }
    }
}
Imports System.Data.SqlClient

Partial Public Class Db
    Inherits SqlSession

    Public Sub New(connectionString As String, Optional initializer As Action(Of Db) = Nothing)
        MyBase.New(CreateSqlConnection(connectionString))
        If initializer IsNot Nothing Then initializer(Me)
    End Sub

    Private Shared Function CreateSqlConnection(connectionString As String) As SqlConnection
        If String.IsNullOrEmpty(connectionString) Then Throw New ArgumentNullException(NameOf(connectionString))
        Return New SqlConnection(connectionString)
    End Function

    Public Sub New(ByVal sqlVersion As SqlVersion)
        MyBase.New(New SqlConnection())
        sqlVersion = sqlVersion
    End Sub

    Public Sub New(ByVal sqlConnection As SqlConnection)
        MyBase.New(sqlConnection)
    End Sub

    Private m_Address As DbTable(Of Address)
    <DbTable("[SalesLT].[Address]", Description:="Street address information for customers.")>
    Public ReadOnly Property Address As DbTable(Of Address)
        Get
            Return GetTable(m_Address)
        End Get
    End Property

    Private m_Customer As DbTable(Of Customer)
    <DbTable("[SalesLT].[Customer]", Description:="Customer information.")>
    Public ReadOnly Property Customer As DbTable(Of Customer)
        Get
            Return GetTable(m_Customer)
        End Get
    End Property

    Private m_CustomerAddress As DbTable(Of CustomerAddress)
    <DbTable("[SalesLT].[CustomerAddress]", Description:="Cross-reference table mapping customers to their address(es).")>
    <Relationship(NameOf(FK_CustomerAddress_Customer_CustomerID), Description:="Foreign key constraint referencing Customer.CustomerID.")>
    <Relationship(NameOf(FK_CustomerAddress_Address_AddressID), Description:="Foreign key constraint referencing Address.AddressID.")>
    Public ReadOnly Property CustomerAddress As DbTable(Of CustomerAddress)
        Get
            Return GetTable(m_CustomerAddress)
        End Get
    End Property

    <_Relationship>
    Private Function FK_CustomerAddress_Customer_CustomerID(x As CustomerAddress) As KeyMapping
        Return x.FK_Customer.Join(Customer.Entity)
    End Function

    <_Relationship>
    Private Function FK_CustomerAddress_Address_AddressID(x As CustomerAddress) As KeyMapping
        Return x.FK_Address.Join(Address.Entity)
    End Function

    Private m_ProductCategory As DbTable(Of ProductCategory)
    <DbTable("[SalesLT].[ProductCategory]", Description:="High-level product categorization.")>
    <Relationship(NameOf(FK_ProductCategory_ProductCategory_ParentProductCategoryID_ProductCategoryID), Description:="Foreign key constraint referencing ProductCategory.ProductCategoryID.")>
    Public ReadOnly Property ProductCategory As DbTable(Of ProductCategory)
        Get
            Return GetTable(m_ProductCategory)
        End Get
    End Property

    <_Relationship>
    Private Function FK_ProductCategory_ProductCategory_ParentProductCategoryID_ProductCategoryID(x As ProductCategory) As KeyMapping
        Return x.FK_ParentProductCategory.Join(x)
    End Function

    Private m_ProductModel As DbTable(Of ProductModel)
    <DbTable("[SalesLT].[ProductModel]")>
    Public ReadOnly Property ProductModel As DbTable(Of ProductModel)
        Get
            Return GetTable(m_ProductModel)
        End Get
    End Property

    Private m_ProductDescription As DbTable(Of ProductDescription)
    <DbTable("[SalesLT].[ProductDescription]", Description:="Product descriptions in several languages.")>
    Public ReadOnly Property ProductDescription As DbTable(Of ProductDescription)
        Get
            Return GetTable(m_ProductDescription)
        End Get
    End Property

    Private m_ProductModelProductDescription As DbTable(Of ProductModelProductDescription)
    <DbTable("[SalesLT].[ProductModelProductDescription]", Description:="Cross-reference table mapping product descriptions and the language the description is written in.")>
    <Relationship(NameOf(FK_ProductModelProductDescription_ProductModel_ProductModelID), Description:="Foreign key constraint referencing ProductModel.ProductModelID.")>
    <Relationship(NameOf(FK_ProductModelProductDescription_ProductDescription_ProductDescriptionID), Description:="Foreign key constraint referencing ProductDescription.ProductDescriptionID.")>
    Public ReadOnly Property ProductModelProductDescription As DbTable(Of ProductModelProductDescription)
        Get
            Return GetTable(m_ProductModelProductDescription)
        End Get
    End Property

    <_Relationship>
    Private Function FK_ProductModelProductDescription_ProductModel_ProductModelID(x As ProductModelProductDescription) As KeyMapping
        Return x.FK_ProductModel.Join(ProductModel.Entity)
    End Function

    <_Relationship>
    Private Function FK_ProductModelProductDescription_ProductDescription_ProductDescriptionID(x As ProductModelProductDescription) As KeyMapping
        Return x.FK_ProductDescription.Join(ProductDescription.Entity)
    End Function

    Private m_Product As DbTable(Of Product)
    <DbTable("[SalesLT].[Product]", Description:="Products sold or used in the manfacturing of sold products.")>
    <Relationship(NameOf(FK_Product_ProductModel_ProductModelID))>
    <Relationship(NameOf(FK_Product_ProductCategory_ProductCategoryID))>
    Public ReadOnly Property Product As DbTable(Of Product)
        Get
            Return GetTable(m_Product)
        End Get
    End Property

    <_Relationship>
    Private Function FK_Product_ProductModel_ProductModelID(x As Product) As KeyMapping
        Return x.FK_ProductModel.Join(ProductModel.Entity)
    End Function

    <_Relationship>
    Private Function FK_Product_ProductCategory_ProductCategoryID(x As Product) As KeyMapping
        Return x.FK_ProductCategory.Join(ProductCategory.Entity)
    End Function

    Private m_SalesOrderHeader As DbTable(Of SalesOrderHeader)
    <DbTable("[SalesLT].[SalesOrderHeader]", Description:="General sales order information.")>
    <Relationship(NameOf(FK_SalesOrderHeader_Customer_CustomerID))>
    <Relationship(NameOf(FK_SalesOrderHeader_Address_BillTo_AddressID))>
    <Relationship(NameOf(FK_SalesOrderHeader_Address_ShipTo_AddressID))>
    Public ReadOnly Property SalesOrderHeader As DbTable(Of SalesOrderHeader)
        Get
            Return GetTable(m_SalesOrderHeader)
        End Get
    End Property

    <_Relationship>
    Private Function FK_SalesOrderHeader_Customer_CustomerID(x As SalesOrderHeader) As KeyMapping
        Return x.FK_Customer.Join(Customer.Entity)
    End Function

    <_Relationship>
    Private Function FK_SalesOrderHeader_Address_BillTo_AddressID(x As SalesOrderHeader) As KeyMapping
        Return x.FK_BillToCustomerAddress.Join(CustomerAddress.Entity)
    End Function

    <_Relationship>
    Private Function FK_SalesOrderHeader_Address_ShipTo_AddressID(x As SalesOrderHeader) As KeyMapping
        Return x.FK_ShipToCustomerAddress.Join(CustomerAddress.Entity)
    End Function

    Private m_SalesOrderDetail As DbTable(Of SalesOrderDetail)
    <DbTable("[SalesLT].[SalesOrderDetail]", Description:="Individual products associated with a specific sales order. See SalesOrderHeader.")>
    <Relationship(NameOf(FK_SalesOrderDetail_SalesOrderHeader))>
    <Relationship(NameOf(FK_SalesOrderDetail_Product))>
    Public ReadOnly Property SalesOrderDetail As DbTable(Of SalesOrderDetail)
        Get
            Return GetTable(m_SalesOrderDetail)
        End Get
    End Property

    <_Relationship>
    Private Function FK_SalesOrderDetail_SalesOrderHeader(x As SalesOrderDetail) As KeyMapping
        Return x.FK_SalesOrderHeader.Join(SalesOrderHeader.Entity)
    End Function

    <_Relationship>
    Private Function FK_SalesOrderDetail_Product(x As SalesOrderDetail) As KeyMapping
        Return x.FK_Product.Join(Product.Entity)
    End Function
End Class

The code of database can be manipulated via Db Visualizer tool window in Visual Studio:

image

Data and Business Layer

  • C#
  • VB.Net

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);
}

Imports System.Data
Imports System.Threading

Partial Class Db
    Private Async Function EnsureConnectionOpenAsync(ct As CancellationToken) As Task
        If Connection.State <> ConnectionState.Open Then
            Await OpenConnectionAsync(ct)
        End If
    End Function

    Public Async Function GetSalesOrderInfoAsync(salesOrderID As _Int32, Optional ct As CancellationToken = Nothing) As Task(Of DataSet(Of SalesOrderInfo))
        Dim result = CreateQuery(
            Sub(builder As DbQueryBuilder, x As SalesOrderInfo)
                Dim o As SalesOrderHeader = Nothing, c As Customer = Nothing, shipTo As Address = Nothing, billTo As Address = Nothing
                builder.From(SalesOrderHeader, o).
                    LeftJoin(Customer, o.FK_Customer, c).
                    LeftJoin(Address, o.FK_ShipToAddress, shipTo).
                    LeftJoin(Address, o.FK_BillToAddress, billTo).
                    AutoSelect().
                    AutoSelect(c, x.Customer).
                    AutoSelect(shipTo, x.ShipToAddress).
                    AutoSelect(billTo, x.BillToAddress).
                    Where(o.SalesOrderID = salesOrderID)
            End Sub)
        Await result.CreateChildAsync(Function(x) x.SalesOrderDetails,
            Sub(builder As DbQueryBuilder, x As SalesOrderInfoDetail)
                Dim d As SalesOrderDetail = Nothing, p As Product = Nothing
                builder.From(SalesOrderDetail, d).LeftJoin(Product, d.FK_Product, p).AutoSelect().AutoSelect(p, x.Product)
            End Sub, ct)
        Return Await result.ToDataSetAsync(ct)
    End Function

    Public Function DeleteSalesOrderAsync(dataSet As DataSet(Of SalesOrderHeader.Key), ct As CancellationToken) As Task(Of Integer)
        Return SalesOrderHeader.DeleteAsync(dataSet, Function(s, x) s.Match(x), ct)
    End Function

    Public Async Function UpdateSalesOrderAsync(salesOrders As DataSet(Of SalesOrderInfo), ct As CancellationToken) As Task
        Await EnsureConnectionOpenAsync(ct)
        Using transaction = BeginTransaction()
            salesOrders.Entity.ResetRowIdentifiers()
            Await SalesOrderHeader.UpdateAsync(salesOrders, ct)
            Await SalesOrderDetail.DeleteAsync(salesOrders, Function(s, x) s.Match(x.FK_SalesOrderHeader), ct)
            Dim salesOrderDetails = salesOrders.GetChild(Function(x) x.SalesOrderDetails)
            salesOrderDetails.Entity.ResetRowIdentifiers()
            Await SalesOrderDetail.InsertAsync(salesOrderDetails, ct)
            Await transaction.CommitAsync(ct)
        End Using
    End Function

    Public Async Function CreateSalesOrderAsync(salesOrders As DataSet(Of SalesOrderInfo), ct As CancellationToken) As Task(Of Integer?)
        Await EnsureConnectionOpenAsync(ct)
        Using transaction = BeginTransaction()

            salesOrders.Entity.ResetRowIdentifiers()
            Await SalesOrderHeader.InsertAsync(salesOrders, True, ct)
            Dim salesOrderDetails = salesOrders.GetChild(Function(x) x.SalesOrderDetails)
            salesOrderDetails.Entity.ResetRowIdentifiers()
            Await SalesOrderDetail.InsertAsync(salesOrderDetails, ct)
            Await transaction.CommitAsync(ct)
            If salesOrders.Count > 0 Then
                Return salesOrders.Entity.SalesOrderID(0)
            Else
                Return Nothing
            End If
        End Using
    End Function
End Class

Data Presentation

  • C#
  • VB.Net
using DevZest.Data.Presenters;
using DevZest.Data.Views;
using DevZest.Data;
using System.Windows;
using System;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Diagnostics;

namespace DevZest.Samples.AdventureWorksLT
{
    partial class SalesOrderWindow
    {
        private class DetailPresenter : DataPresenter<SalesOrderInfoDetail>, ForeignKeyBox.ILookupService, DataView.IPasteAppendService
        {
            public DetailPresenter(Window ownerWindow)
            {
                _ownerWindow = ownerWindow;
            }

            private readonly Window _ownerWindow;

            protected override void BuildTemplate(TemplateBuilder builder)
            {
                var product = _.Product;
                builder.GridRows("Auto", "20")
                    .GridColumns("20", "*", "*", "Auto", "Auto", "Auto", "Auto")
                    .WithFrozenTop(1)
                    .GridLineX(new GridPoint(0, 2), 7)
                    .GridLineY(new GridPoint(2, 1), 1).GridLineY(new GridPoint(3, 1), 1).GridLineY(new GridPoint(4, 1), 1)
                    .GridLineY(new GridPoint(5, 1), 1).GridLineY(new GridPoint(6, 1), 1).GridLineY(new GridPoint(7, 1), 1)
                    .Layout(Orientation.Vertical)
                    .WithVirtualRowPlacement(VirtualRowPlacement.Tail)
                    .AllowDelete()
                    .AddBinding(0, 0, this.BindToGridHeader())
                    .AddBinding(1, 0, product.ProductNumber.BindToColumnHeader("Product No."))
                    .AddBinding(2, 0, product.Name.BindToColumnHeader("Product"))
                    .AddBinding(3, 0, _.UnitPrice.BindToColumnHeader("Unit Price"))
                    .AddBinding(4, 0, _.UnitPriceDiscount.BindToColumnHeader("Discount"))
                    .AddBinding(5, 0, _.OrderQty.BindToColumnHeader("Qty"))
                    .AddBinding(6, 0, _.LineTotal.BindToColumnHeader("Total"))
                    .AddBinding(0, 1, _.BindTo<RowHeader>())
                    .AddBinding(1, 1, _.FK_Product.BindToForeignKeyBox(product, GetProductNumber).MergeIntoGridCell(product.ProductNumber.BindToTextBlock()).WithSerializableColumns(_.ProductID, product.ProductNumber))
                    .AddBinding(2, 1, product.Name.BindToTextBlock().AddToGridCell().WithSerializableColumns(product.Name))
                    .AddBinding(3, 1, _.UnitPrice.BindToTextBox().MergeIntoGridCell())
                    .AddBinding(4, 1, _.UnitPriceDiscount.BindToTextBox(new PercentageConverter()).MergeIntoGridCell(_.UnitPriceDiscount.BindToTextBlock("{0:P}")))
                    .AddBinding(5, 1, _.OrderQty.BindToTextBox().MergeIntoGridCell())
                    .AddBinding(6, 1, _.LineTotal.BindToTextBlock("{0:C}").AddToGridCell().WithSerializableColumns(_.LineTotal));
            }

            private static string GetProductNumber(ColumnValueBag valueBag, Product.PK productKey, Product.Lookup productLookup)
            {
                return valueBag.GetValue(productLookup.ProductNumber);
            }

            bool ForeignKeyBox.ILookupService.CanLookup(CandidateKey foreignKey)
            {
                if (foreignKey == _.FK_Product)
                    return true;
                else
                    return false;
            }

            void ForeignKeyBox.ILookupService.BeginLookup(ForeignKeyBox foreignKeyBox)
            {
                if (foreignKeyBox.ForeignKey == _.FK_Product)
                {
                    var dialogWindow = new ProductLookupWindow();
                    dialogWindow.Show(_ownerWindow, foreignKeyBox, CurrentRow.GetValue(_.ProductID));
                }
                else
                    throw new NotSupportedException();
            }

            protected override bool ConfirmDelete()
            {
                return MessageBox.Show(string.Format("Are you sure you want to delete selected {0} rows?", SelectedRows.Count), "Delete", MessageBoxButton.YesNo) == MessageBoxResult.Yes;
            }

            bool DataView.IPasteAppendService.Verify(IReadOnlyList<ColumnValueBag> data)
            {
                var foreignKeys = DataSet<Product.Ref>.Create();
                for (int i = 0; i < data.Count; i++)
                {
                    var valueBag = data[i];
                    var productId = valueBag.ContainsKey(_.ProductID) ? valueBag[_.ProductID] : null;
                    foreignKeys.AddRow((_, dataRow) =>
                    {
                        _.ProductID.SetValue(dataRow, productId);
                    });
                }

                if (!App.Execute((db, ct) => db.LookupAsync(foreignKeys, ct), Window.GetWindow(View), out var lookup))
                    return false;

                Debug.Assert(lookup.Count == data.Count);
                var product = _.Product;
                for (int i = 0; i < lookup.Count; i++)
                {
                    data[i].SetValue(product.Name, lookup._.Name[i]);
                    data[i].SetValue(product.ProductNumber, lookup._.ProductNumber[i]);
                }
                return true;
            }
        }
    }
}
Imports DevZest.Data
Imports DevZest.Data.Presenters
Imports DevZest.Data.Views

Partial Class SalesOrderWindow
    Private Class DetailPresenter
        Inherits DataPresenter(Of SalesOrderInfoDetail)
        Implements ForeignKeyBox.ILookupService
        Implements DataView.IPasteAppendService
        Public Sub New(ownerWindow As Window)
            _ownerWindow = ownerWindow
        End Sub

        Private ReadOnly _ownerWindow As Window

        Protected Overrides Sub BuildTemplate(builder As TemplateBuilder)
            Dim e = Entity
            Dim product = e.Product
            builder.GridRows("Auto", "20") _
                .GridColumns("20", "*", "*", "Auto", "Auto", "Auto", "Auto") _
                .WithFrozenTop(1) _
                .GridLineX(New GridPoint(0, 2), 7) _
                .GridLineY(New GridPoint(2, 1), 1) _
                .GridLineY(New GridPoint(3, 1), 1) _
                .GridLineY(New GridPoint(4, 1), 1) _
                .GridLineY(New GridPoint(5, 1), 1) _
                .GridLineY(New GridPoint(6, 1), 1) _
                .GridLineY(New GridPoint(7, 1), 1) _
                .Layout(Orientation.Vertical) _
                .WithVirtualRowPlacement(VirtualRowPlacement.Tail) _
                .AllowDelete() _
                .AddBinding(0, 0, Me.BindToGridHeader()) _
                .AddBinding(1, 0, product.ProductNumber.BindToColumnHeader("Product No.")) _
                .AddBinding(2, 0, product.Name.BindToColumnHeader("Product")) _
                .AddBinding(3, 0, e.UnitPrice.BindToColumnHeader("Unit Price")) _
                .AddBinding(4, 0, e.UnitPriceDiscount.BindToColumnHeader("Discount")) _
                .AddBinding(5, 0, e.OrderQty.BindToColumnHeader("Qty")) _
                .AddBinding(6, 0, e.LineTotal.BindToColumnHeader("Total")) _
                .AddBinding(0, 1, e.BindTo(Of RowHeader)()) _
                .AddBinding(1, 1, e.FK_Product.BindToForeignKeyBox(product, AddressOf GetProductNumber).MergeIntoGridCell(product.ProductNumber.BindToTextBlock()).WithSerializableColumns(e.ProductID, product.ProductNumber)) _
                .AddBinding(2, 1, product.Name.BindToTextBlock().AddToGridCell().WithSerializableColumns(product.Name)) _
                .AddBinding(3, 1, e.UnitPrice.BindToTextBox().MergeIntoGridCell()) _
                .AddBinding(4, 1, e.UnitPriceDiscount.BindToTextBox(New PercentageConverter()).MergeIntoGridCell(e.UnitPriceDiscount.BindToTextBlock("{0:P}"))) _
                .AddBinding(5, 1, e.OrderQty.BindToTextBox().MergeIntoGridCell()) _
                .AddBinding(6, 1, e.LineTotal.BindToTextBlock("{0:C}").AddToGridCell().WithSerializableColumns(e.LineTotal))
        End Sub

        Private Shared Function GetProductNumber(valueBag As ColumnValueBag, productKey As Product.PK, productLookup As Product.Lookup) As String
            Return valueBag.GetValue(productLookup.ProductNumber)
        End Function

        Private Function CanLookup(foreignKey As CandidateKey) As Boolean Implements ForeignKeyBox.ILookupService.CanLookup
            If foreignKey Is Entity.FK_Product Then
                Return True
            Else
                Return False
            End If
        End Function

        Private Sub BeginLookup(foreignKeyBox As ForeignKeyBox) Implements ForeignKeyBox.ILookupService.BeginLookup
            If foreignKeyBox.ForeignKey Is Entity.FK_Product Then
                Dim dialogWindow = New ProductLookupWindow()
                dialogWindow.Show(_ownerWindow, foreignKeyBox, CurrentRow.GetValue(Entity.ProductID))
            Else
                Throw New NotSupportedException()
            End If
        End Sub

        Protected Overrides Function ConfirmDelete() As Boolean
            Return MessageBox.Show(String.Format("Are you sure you want to delete selected {0} rows?", SelectedRows.Count), "Delete", MessageBoxButton.YesNo) = MessageBoxResult.Yes
        End Function

        Private Function Verify(data As IReadOnlyList(Of ColumnValueBag)) As Boolean Implements DataView.IPasteAppendService.Verify
            Dim foreignKeys = DevZest.Data.DataSet(Of Product.Ref).Create()
            For i = 0 To data.Count
                Dim valueBag = data(i)
                Dim productId = If(valueBag.ContainsKey(Entity.ProductID), valueBag(Entity.ProductID), Nothing)
                foreignKeys.AddRow(Sub(e, dataRow) e.ProductID.SetValue(dataRow, productId))
            Next

            Dim lookup As DataSet(Of Product.Lookup) = Nothing
            If Not App.Execute(Function(db, ct) db.LookupAsync(foreignKeys, ct), Window.GetWindow(View), lookup) Then
                Return False
            End If

            Debug.Assert(lookup.Count = data.Count)
            Dim product = Entity.Product
            For i = 0 To lookup.Count
                data(i).SetValue(product.Name, lookup.Entity.Name(i))
                data(i).SetValue(product.ProductNumber, lookup.Entity.ProductNumber(i))
            Next
            Return True
        End Function
    End Class
End Class

This will produce the following data grid UI, with foreign key lookup and paste append from clipboard:

image

Mock Database for Testing

  • C#
  • VB.Net
public sealed class MockSalesOrder : DbMock<Db>
{
    public static Task<Db> CreateAsync(Db db, IProgress<DbInitProgress> progress = null, CancellationToken ct = default(CancellationToken))
    {
        return new MockSalesOrder().MockAsync(db, progress, ct);
    }

    // This method is generated by a tool
    private static DataSet<SalesOrderHeader> Headers()
    {
        DataSet<SalesOrderHeader> result = DataSet<SalesOrderHeader>.Create().AddRows(4);
        SalesOrderHeader _ = result._;
        _.SuspendIdentity();
        _.SalesOrderID[0] = 1;
        _.SalesOrderID[1] = 2;
        ...
        _.ResumeIdentity();
        return result;
    }

    // This method is generated by a tool
    private static DataSet<SalesOrderDetail> Details()
    {
        DataSet<SalesOrderDetail> result = DataSet<SalesOrderDetail>.Create().AddRows(32);
        SalesOrderDetail _ = result._;
        _.SuspendIdentity();
        ...
        _.SalesOrderDetailID[0] = 1;
        _.SalesOrderDetailID[1] = 2;
        ...
        _.ResumeIdentity();
        return result;
    }

    protected override void Initialize()
    {
        // The order of mocking table does not matter, the dependencies will be sorted out automatically.
        Mock(Db.SalesOrderDetail, Details);
        Mock(Db.SalesOrderHeader, Headers);
    }
}
Public Class MockSalesOrder
    Inherits DbMock(Of Db)

    Public Shared Function CreateAsync(db As Db, Optional progress As IProgress(Of DbInitProgress) = Nothing, Optional ct As CancellationToken = Nothing) As Task(Of Db)
        Return New MockSalesOrder().MockAsync(db, progress, ct)
    End Function

    ' This method is generated by a tool
    Private Shared Function Headers() As DataSet(Of SalesOrderHeader)
        Dim result As DataSet(Of SalesOrderHeader) = DataSet(Of SalesOrderHeader).Create().AddRows(4)
        Dim x As SalesOrderHeader = result.Entity
        x.SuspendIdentity()
        x.SalesOrderID(0) = 1
        x.SalesOrderID(1) = 2
        ...
        x.ResumeIdentity()
        Return result
    End Function

    ' This method is generated by a tool
    Private Shared Function Details() As DataSet(Of SalesOrderDetail)
        Dim result As DataSet(Of SalesOrderDetail) = DataSet(Of SalesOrderDetail).Create().AddRows(32)
        Dim x As SalesOrderDetail = result.Entity
        x.SuspendIdentity()
        ...
        x.SalesOrderDetailID(0) = 1
        x.SalesOrderDetailID(1) = 2
        x.ResumeIdentity()
        ...
        Return result
    End Function

    Protected Overrides Sub Initialize()
        Mock(Db.SalesOrderHeader, Function() Headers())
        Mock(Db.SalesOrderDetail, Function() Details())
    End Sub
End Class

Testing data code is generated from database:

image

Getting Started

It's highly recommended to start with our step by step tutorial.

  • Improve this Doc
Back to top Copyright © Weifen Luo | DevZest