Added List<> method

Added AOrigin parameter for all methods
Started documentation
This commit is contained in:
Mark van Renswoude 2020-09-13 22:53:17 +02:00
parent 2395700495
commit 66943d975f
14 changed files with 610 additions and 25 deletions

4
.gitignore vendored
View File

@ -1,6 +1,8 @@
__history
Win32
Docs/_build
*.local
*.identcache
*.res
*.res
*.sublime-workspace

View File

@ -0,0 +1,10 @@
{
"folders":
[
{
"path": ".",
"file_exclude_patterns": ["*.sublime-project"],
"folder_exclude_patterns": ["_build"]
}
]
}

20
Docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Dainty
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

14
Docs/README.md Normal file
View File

@ -0,0 +1,14 @@
The documentation can be built locally using Sphinx. Install Python 3 (choco install python on Windows),
then install sphinx and the ReadTheDocs theme:
```pip install sphinx sphinx_rtd_theme```
To build the HTML output, run:
```.\make.bat html```
To use the auto reloading server (rundev.bat), install the sphinx-autobuild package:
```pip install sphinx-autobuild```

158
Docs/conf.py Normal file
View File

@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
#
# Dainty documentation build configuration file, created by
# sphinx-quickstart on Sat Feb 11 13:02:50 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Dainty'
author = u'Mark van Renswoude'
html_show_copyright = False
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u''
# The full version, including alpha/beta/rc tags.
release = u''
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Daintydoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Dainty.tex', u'Dainty Documentation',
u'Mark van Renswoude', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'dainty', u'Dainty Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Dainty', u'Dainty Documentation',
author, 'Dainty', 'Simple object mapper for Delphi.',
'Miscellaneous'),
]
highlight_language = 'delphi'

150
Docs/gettingstarted.rst Normal file
View File

@ -0,0 +1,150 @@
Getting started
===============
Including the source
--------------------
Dainty comes with runtime packages for a few Delphi versions which you can build to use the dcu's in your project. There is no designtime package or components, so you can also include the source directly if desired in which case you need both Dainty.pas and Dainty.Converter.Default.pas.
Dainty provides two ways to use it. The easiest way is to use the included class helpers and directly call Dainty's methods on any TDataSet or TParams instance. This is the method all the examples below will use. If however you have a conflict in class helpers and can't use them, you can instead call the same methods directly using the TDainty class instead and pass the dataset or params as the first parameter.
Reading a dataset
-----------------
Use Rows to iterate through a dataset and map each row to an object.
::
uses
Dainty;
type
TCustomerRow = class
public
CustomerID: Integer;
FullName: string;
Active: Boolean;
end;
var
query: TIBQuery;
customer: TCustomerRow;
begin
query := TIBQuery.Create(nil);
try
query.Database := MyDatabase;
query.Transaction := MyTransaction;
query.SQL.Text := 'select CustomerID, FullName, Active from Customer';
query.Open;
for customer in query.Rows<TCustomerRow> do
begin
{ You can read the customer properties here. Note that you do not have ownership
of the objects when using Rows<> and must not keep a reference, as they are
destroyed during and after the loop.
The author is of the opinion that 'database' objects should not be used as business
objects but instead mapped to and from, to provide separation of the domains.
However, Dainty does not enforce this and you can use query.List<> instead to
get ownership of the list. }
end;
finally
FreeAndNil(query);
end;
end;
Note that the customer object is destroyed right before the next iteration or after the loop is finished. If you want to keep a reference to the customer object, use List<> instead to get a list of objects which you have to Free yourself.
If you only need one row there are a few helpers you can use:
GetFirst will retrieve one row from the dataset and expects at least one row to be present. GetSingle is similar, but in addition it verifies that there is exactly one row and not more. Both have an OrDefault version which will not throw an exception but return nil instead if the requirements are not met. An example:
::
var
query: TIBQuery;
oldestCustomer: TCustomerRow;
begin
query := TIBQuery.Create(nil);
try
query.Database := MyDatabase;
query.Transaction := MyTransaction;
query.SQL.Text := 'select CustomerID, FullName, Active from Customer order by Age desc rows 1';
query.Open;
oldestCustomer := query.GetFirstOrDefault<TCustomerRow>;
try
{ ... }
finally
FreeAndNil(oldestCustomer);
end;
finally
FreeAndNil(query);
end;
end;
Note that you must destroy the resulting object yourself in this scenario.
For full control over the dataset position and the resulting object, GetRowReader<> is available.
Writing parameters
------------------
::
uses
Dainty;
type
TCustomerParams = class
public
FullName: string;
Active: Boolean;
end;
var
query: TIBQuery;
customParams: TCustomerParams;
begin
query := TIBQuery.Create(nil);
try
query.Database := MyDatabase;
query.Transaction := MyTransaction;
query.SQL.Text := 'select CustomerID, FullName, Active from Customer where FullName = :FullName and Active = :Active';
customerParams := TCustomerParams.Create;
try
customerParams.FullName := 'John Doe';
customerParams.Active := True;
query.Params.Apply(customerParams);
query.Open;
{ ... }
finally
FreeAndNil(customerParams);
end;
finally
FreeAndNil(query);
end;
end;
FieldName and ParamName attributes
----------------------------------
TODO: explain how these are used
Note that ParamName and FieldName are aliases for the same attribute and can be used interchangeably. Pick whichever one makes the most sense for the object in question.

9
Docs/index.rst Normal file
View File

@ -0,0 +1,9 @@
Dainty documentation
====================
.. toctree::
:maxdepth: 2
:caption: Table of contents
introduction
gettingstarted

17
Docs/introduction.rst Normal file
View File

@ -0,0 +1,17 @@
Introduction
============
Dainty is a simple object mapper for Delphi.
Heavily inspired by `Dapper <https://github.com/StackExchange/Dapper>`_, Dainty aims to provide a lightweight layer to map objects from a TDataSet descendant or to TParams. It is intentionally not a fully fledged ORM framework.
Dainty has been written and tested primarily in Delphi XE2 and 10.2.
Key features
------------
* Read rows from a DataSet into objects
* Fill TParams using an object
* Helper methods for single row results
* Object mapping cache to reduce runtime RTTI overhead

36
Docs/make.bat Normal file
View File

@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=Dainty
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

3
Docs/rundev.bat Normal file
View File

@ -0,0 +1,3 @@
@Echo Off
start "" "http://localhost:8000/"
sphinx-autobuild . .\_build\html -N

View File

@ -7,12 +7,17 @@
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
<TargetedPlatforms>1</TargetedPlatforms>
<TargetedPlatforms>3</TargetedPlatforms>
<AppType>Package</AppType>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
<Base_Win64>true</Base_Win64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
@ -23,6 +28,12 @@
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win64)'!=''">
<Cfg_1_Win64>true</Cfg_1_Win64>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
<Cfg_1_Win32>true</Cfg_1_Win32>
<CfgParent>Cfg_1</CfgParent>
@ -34,7 +45,20 @@
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win64)'!=''">
<Cfg_2_Win64>true</Cfg_2_Win64>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
<Cfg_2_Win32>true</Cfg_2_Win32>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_OutputXMLDocumentation>true</DCC_OutputXMLDocumentation>
<RuntimeOnlyPackage>true</RuntimeOnlyPackage>
<VerInfo_Locale>1043</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
@ -47,11 +71,18 @@
<DCC_DcuOutput>$(DELPHILIB)</DCC_DcuOutput>
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win64)'!=''">
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace>
<DCC_BplOutput>$(DELPHIBIN64)</DCC_BplOutput>
<DCC_DcpOutput>$(DELPHIBIN64)</DCC_DcpOutput>
<DCC_DcuOutput>$(DELPHILIB64)</DCC_DcuOutput>
<VerInfo_Locale>1033</VerInfo_Locale>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
@ -60,6 +91,10 @@
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
<DCC_RemoteDebug>true</DCC_RemoteDebug>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
@ -71,6 +106,14 @@
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
<DCC_DebugInformation>false</DCC_DebugInformation>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win64)'!=''">
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
<MainSource>MainSource</MainSource>
@ -129,7 +172,7 @@
</Delphi.Personality>
<Deployment/>
<Platforms>
<Platform value="Win64">False</Platform>
<Platform value="Win64">True</Platform>
<Platform value="Win32">True</Platform>
</Platforms>
</BorlandProject>

View File

@ -1,4 +1,59 @@
# Dainty
*Simple object mapper for Delphi*
Heavily inspired by [Dapper](https://github.com/StackExchange/Dapper), Dainty aims to provide a lightweight layer to map objects from a TDataSet descendant or to TParams. It is intentionally not a fully fledged ORM framework.
Heavily inspired by [Dapper](https://github.com/StackExchange/Dapper), Dainty aims to provide a lightweight layer to map objects from a TDataSet descendant or to TParams. It is intentionally not a fully fledged ORM framework.
Documentation is available on [ReadTheDocs](https://dainty.readthedocs.io/).
## Quick examples
Iterating through a dataset:
```pascal
for customer in query.Rows<TCustomerRow> do
SendEmail(customer.FullName, customer.Email);
```
Getting a list of objects:
```pascal
customers := query.List<TCustomerRow>;
try
{ ... }
finally
FreeAndNil(customers);
end;
```
Getting a single row from a dataset:
```pascal
customer := query.GetFirst<TCustomerRow>;
try
{ Alternatively: GetFirstOrDefault, GetSingle, GetSingleOrDefault }
finally
FreeAndNil(customer);
end;
```
Assigning parameter values:
```pascal
type
TCustomerParams = class
public
FullName: string;
Active: Boolean;
end;
...
query.SQL.Text := 'select CustomerID, FullName, Active from Customer ' +
'where FullName = :FullName and Active = :Active';
customerParams := TCustomerParams.Create;
try
customerParams.FullName := 'John Doe';
customerParams.Active := True;
query.Params.Apply(customerParams);
query.Open;
{ ... }
finally
FreeAndNil(customerParams);
end;
```

View File

@ -69,6 +69,9 @@ type
ParamName = FieldName;
TDaintyOrigin = (doFirst, doCurrent);
/// <summary>
/// Allows for direct calls to TDainty methods from any DataSet instance, for example
/// DataSet.Rows<> or DataSet.GetFirstOrDefault<>.
@ -84,9 +87,19 @@ type
/// object for each row.
/// </summary>
/// <remarks>
/// The objects returns are owned by the enumerable, and destroyed during and after the loop.
/// </remarks>
function Rows<T: class>(AOrigin: TDaintyOrigin = doFirst): IEnumerable<T>;
/// <summary>
/// Returns the DataSet rows as a list of objects.
/// </summary>
/// <remarks>
/// The caller is responsible for freeing the list.
/// Note that the DataSet is not reset to First and will instead start at the current record.
/// </remarks>
function Rows<T: class>: IEnumerable<T>;
function List<T: class>(AOrigin: TDaintyOrigin = doFirst): TList<T>;
/// <summary>
/// Provides access to the reader which allows control over the DataSet loop.
@ -101,7 +114,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
function GetFirst<T: class>: T;
function GetFirst<T: class>(AOrigin: TDaintyOrigin = doFirst): T;
/// <summary>
/// Returns the current row mapped to the specified class. Returns nil if no row is active.
@ -109,7 +122,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
function GetFirstOrDefault<T: class>: T;
function GetFirstOrDefault<T: class>(AOrigin: TDaintyOrigin = doFirst): T;
/// <summary>
@ -119,7 +132,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
function GetSingle<T: class>: T;
function GetSingle<T: class>(AOrigin: TDaintyOrigin = doFirst): T;
/// <summary>
/// Returns the current row mapped to the specified class. Returns nil if no row is active
@ -128,7 +141,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
function GetSingleOrDefault<T: class>: T;
function GetSingleOrDefault<T: class>(AOrigin: TDaintyOrigin = doFirst): T;
end;
@ -179,15 +192,28 @@ type
/// Provides row to object mapping functionality. Usually accessed using the TDaintyDataSetHelper.
/// </summary>
TDainty = class
protected
class procedure GotoOrigin(ADataSet: TDataSet; AOrigin: TDaintyOrigin);
public
/// <summary>
/// Returns a typed enumerable which iterates the DataSet and returns the mapped
/// object for each row.
/// </summary>
/// <remarks>
/// The objects returns are owned by the enumerable, and destroyed during and after the loop.
/// Note that the DataSet is not reset to First and will instead start at the current record.
/// </remarks>
class function Rows<T: class>(ADataSet: TDataSet): IEnumerable<T>;
class function Rows<T: class>(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): IEnumerable<T>;
/// <summary>
/// Returns the DataSet rows as a list of objects.
/// </summary>
/// <remarks>
/// The caller is responsible for freeing the list.
/// Note that the DataSet is not reset to First and will instead start at the current record.
/// </remarks>
class function List<T: class>(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): TList<T>;
/// <summary>
/// Provides access to the mapper which allows control over the DataSet loop.
@ -202,7 +228,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
class function GetFirst<T: class>(ADataSet: TDataSet): T;
class function GetFirst<T: class>(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T;
/// <summary>
/// Returns the current row mapped to the specified class. Returns nil if no row is active.
@ -210,7 +236,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
class function GetFirstOrDefault<T: class>(ADataSet: TDataSet): T;
class function GetFirstOrDefault<T: class>(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T;
/// <summary>
@ -220,7 +246,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
class function GetSingle<T: class>(ADataSet: TDataSet): T;
class function GetSingle<T: class>(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T;
/// <summary>
/// Returns the current row mapped to the specified class. Returns nil if no row is active
@ -229,7 +255,7 @@ type
/// <remarks>
/// The caller must Free the returned object.
/// </remarks>
class function GetSingleOrDefault<T: class>(ADataSet: TDataSet): T;
class function GetSingleOrDefault<T: class>(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T;
/// <summary>
@ -468,37 +494,43 @@ uses
{ TDaintyDataSetHelper }
function TDaintyDataSetHelper.Rows<T>: IEnumerable<T>;
function TDaintyDataSetHelper.Rows<T>(AOrigin: TDaintyOrigin): IEnumerable<T>;
begin
Result := TDainty.Rows<T>(Self);
end;
function TDaintyDataSetHelper.List<T>(AOrigin: TDaintyOrigin): TList<T>;
begin
Result := TDainty.List<T>(Self, AOrigin);
end;
function TDaintyDataSetHelper.GetRowReader<T>: TDaintyReader<T>;
begin
Result := TDainty.GetRowReader<T>(Self);
end;
function TDaintyDataSetHelper.GetFirst<T>: T;
function TDaintyDataSetHelper.GetFirst<T>(AOrigin: TDaintyOrigin): T;
begin
Result := TDainty.GetFirst<T>(Self);
end;
function TDaintyDataSetHelper.GetFirstOrDefault<T>: T;
function TDaintyDataSetHelper.GetFirstOrDefault<T>(AOrigin: TDaintyOrigin): T;
begin
Result := TDainty.GetFirstOrDefault<T>(Self);
end;
function TDaintyDataSetHelper.GetSingle<T>: T;
function TDaintyDataSetHelper.GetSingle<T>(AOrigin: TDaintyOrigin): T;
begin
Result := TDainty.GetSingle<T>(Self);
end;
function TDaintyDataSetHelper.GetSingleOrDefault<T>: T;
function TDaintyDataSetHelper.GetSingleOrDefault<T>(AOrigin: TDaintyOrigin): T;
begin
Result := TDainty.GetSingleOrDefault<T>(Self);
end;
@ -513,27 +545,51 @@ end;
{ TDainty }
class function TDainty.Rows<T>(ADataSet: TDataSet): IEnumerable<T>;
class function TDainty.Rows<T>(ADataSet: TDataSet; AOrigin: TDaintyOrigin): IEnumerable<T>;
var
reader: TDaintyReader<T>;
begin
GotoOrigin(ADataSet, AOrigin);
reader := GetRowReader<T>(ADataSet);
Result := TDaintyEnumerable<T>.Create(reader, ADataSet);
end;
class function TDainty.List<T>(ADataSet: TDataSet; AOrigin: TDaintyOrigin): TList<T>;
var
reader: TDaintyReader<T>;
begin
GotoOrigin(ADataSet, AOrigin);
reader := GetRowReader<T>(ADataSet);
Result := TObjectList<T>.Create;
try
while not ADataSet.Eof do
begin
Result.Add(reader.MapRow);
ADataSet.Next;
end;
except
FreeAndNil(Result);
raise;
end;
end;
class function TDainty.GetRowReader<T>(ADataSet: TDataSet): TDaintyReader<T>;
begin
Result := TDaintyRttiMapperFactory.ConstructReader<T>(ADataSet);
end;
class function TDainty.GetFirst<T>(ADataSet: TDataSet): T;
class function TDainty.GetFirst<T>(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T;
var
enumerator: IEnumerator<T>;
begin
GotoOrigin(ADataSet, AOrigin);
enumerator := Rows<T>(ADataSet).GetEnumerator;
if not enumerator.MoveNext then
@ -543,7 +599,7 @@ begin
end;
class function TDainty.GetFirstOrDefault<T>(ADataSet: TDataSet): T;
class function TDainty.GetFirstOrDefault<T>(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T;
var
enumerator: IEnumerator<T>;
@ -557,11 +613,12 @@ begin
end;
class function TDainty.GetSingle<T>(ADataSet: TDataSet): T;
class function TDainty.GetSingle<T>(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T;
var
enumerator: IEnumerator<T>;
begin
GotoOrigin(ADataSet, AOrigin);
enumerator := Rows<T>(ADataSet).GetEnumerator;
if not enumerator.MoveNext then
@ -577,11 +634,12 @@ begin
end;
class function TDainty.GetSingleOrDefault<T>(ADataSet: TDataSet): T;
class function TDainty.GetSingleOrDefault<T>(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T;
var
enumerator: IEnumerator<T>;
begin
GotoOrigin(ADataSet, AOrigin);
enumerator := Rows<T>(ADataSet).GetEnumerator;
if not enumerator.MoveNext then
@ -608,6 +666,13 @@ begin
end;
class procedure TDainty.GotoOrigin(ADataSet: TDataSet; AOrigin: TDaintyOrigin);
begin
if AOrigin = doFirst then
ADataSet.First;
end;
{ TDaintyEnumerable<T> }
constructor TDaintyEnumerable<T>.Create(AReader: TDaintyReader<T>; ADataSet: TDataSet);

View File

@ -24,6 +24,9 @@ type
published
procedure SimpleTypes;
// #ToDo1 -oMvR: 11-9-2020: List test
// #ToDo1 -oMvR: 11-9-2020: origin test
procedure GetFirst;
procedure GetFirstMultipleRows;
procedure GetFirstNoData;