From 66943d975ff8a60974c38a85cd18bba5c18b03e6 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 13 Sep 2020 22:53:17 +0200 Subject: [PATCH] Added List<> method Added AOrigin parameter for all methods Started documentation --- .gitignore | 4 +- Docs/Dainty.Docs.sublime-project | 10 ++ Docs/Makefile | 20 ++++ Docs/README.md | 14 +++ Docs/conf.py | 158 +++++++++++++++++++++++++++++++ Docs/gettingstarted.rst | 150 +++++++++++++++++++++++++++++ Docs/index.rst | 9 ++ Docs/introduction.rst | 17 ++++ Docs/make.bat | 36 +++++++ Docs/rundev.bat | 3 + Packages/DaintyDXE2.dproj | 49 +++++++++- README.md | 57 ++++++++++- Source/Dainty.pas | 105 ++++++++++++++++---- UnitTests/DaintyFieldsTests.pas | 3 + 14 files changed, 610 insertions(+), 25 deletions(-) create mode 100644 Docs/Dainty.Docs.sublime-project create mode 100644 Docs/Makefile create mode 100644 Docs/README.md create mode 100644 Docs/conf.py create mode 100644 Docs/gettingstarted.rst create mode 100644 Docs/index.rst create mode 100644 Docs/introduction.rst create mode 100644 Docs/make.bat create mode 100644 Docs/rundev.bat diff --git a/.gitignore b/.gitignore index f5d342c..2ed224c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ __history Win32 +Docs/_build *.local *.identcache -*.res \ No newline at end of file +*.res +*.sublime-workspace \ No newline at end of file diff --git a/Docs/Dainty.Docs.sublime-project b/Docs/Dainty.Docs.sublime-project new file mode 100644 index 0000000..80af61d --- /dev/null +++ b/Docs/Dainty.Docs.sublime-project @@ -0,0 +1,10 @@ +{ + "folders": + [ + { + "path": ".", + "file_exclude_patterns": ["*.sublime-project"], + "folder_exclude_patterns": ["_build"] + } + ] +} diff --git a/Docs/Makefile b/Docs/Makefile new file mode 100644 index 0000000..fa170f5 --- /dev/null +++ b/Docs/Makefile @@ -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) \ No newline at end of file diff --git a/Docs/README.md b/Docs/README.md new file mode 100644 index 0000000..ccea1cc --- /dev/null +++ b/Docs/README.md @@ -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``` \ No newline at end of file diff --git a/Docs/conf.py b/Docs/conf.py new file mode 100644 index 0000000..e7039e4 --- /dev/null +++ b/Docs/conf.py @@ -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' \ No newline at end of file diff --git a/Docs/gettingstarted.rst b/Docs/gettingstarted.rst new file mode 100644 index 0000000..061c1ba --- /dev/null +++ b/Docs/gettingstarted.rst @@ -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 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; + 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. \ No newline at end of file diff --git a/Docs/index.rst b/Docs/index.rst new file mode 100644 index 0000000..ad8339b --- /dev/null +++ b/Docs/index.rst @@ -0,0 +1,9 @@ +Dainty documentation +==================== + +.. toctree:: + :maxdepth: 2 + :caption: Table of contents + + introduction + gettingstarted diff --git a/Docs/introduction.rst b/Docs/introduction.rst new file mode 100644 index 0000000..b8f9b11 --- /dev/null +++ b/Docs/introduction.rst @@ -0,0 +1,17 @@ +Introduction +============ + +Dainty is a simple object mapper for Delphi. + +Heavily inspired by `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 \ No newline at end of file diff --git a/Docs/make.bat b/Docs/make.bat new file mode 100644 index 0000000..3fb24c0 --- /dev/null +++ b/Docs/make.bat @@ -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 diff --git a/Docs/rundev.bat b/Docs/rundev.bat new file mode 100644 index 0000000..1410909 --- /dev/null +++ b/Docs/rundev.bat @@ -0,0 +1,3 @@ +@Echo Off +start "" "http://localhost:8000/" +sphinx-autobuild . .\_build\html -N \ No newline at end of file diff --git a/Packages/DaintyDXE2.dproj b/Packages/DaintyDXE2.dproj index 6f0baa5..93311e4 100644 --- a/Packages/DaintyDXE2.dproj +++ b/Packages/DaintyDXE2.dproj @@ -7,12 +7,17 @@ True Debug Win32 - 1 + 3 Package true + + true + Base + true + true Base @@ -23,6 +28,12 @@ Base true + + true + Cfg_1 + true + true + true Cfg_1 @@ -34,7 +45,20 @@ Base true + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + true true 1043 CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= @@ -47,11 +71,18 @@ $(DELPHILIB) .\$(Platform)\$(Config) + + true + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + $(DELPHIBIN64) + $(DELPHIBIN64) + $(DELPHILIB64) + 1033 + true Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 1033 - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= DEBUG;$(DCC_Define) @@ -60,6 +91,10 @@ true true + + true + 1033 + true 1033 @@ -71,6 +106,14 @@ 0 false + + true + 1033 + + + true + 1033 + MainSource @@ -129,7 +172,7 @@ - False + True True diff --git a/README.md b/README.md index cefae97..7400bbe 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file +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 do + SendEmail(customer.FullName, customer.Email); +``` +Getting a list of objects: +```pascal +customers := query.List; +try + { ... } +finally + FreeAndNil(customers); +end; +``` +Getting a single row from a dataset: +```pascal +customer := query.GetFirst; +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; +``` \ No newline at end of file diff --git a/Source/Dainty.pas b/Source/Dainty.pas index f471c12..ca7e30a 100644 --- a/Source/Dainty.pas +++ b/Source/Dainty.pas @@ -69,6 +69,9 @@ type ParamName = FieldName; + TDaintyOrigin = (doFirst, doCurrent); + + /// /// 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. /// /// + /// The objects returns are owned by the enumerable, and destroyed during and after the loop. + /// + function Rows(AOrigin: TDaintyOrigin = doFirst): IEnumerable; + + /// + /// Returns the DataSet rows as a list of objects. + /// + /// + /// 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. /// - function Rows: IEnumerable; + function List(AOrigin: TDaintyOrigin = doFirst): TList; + /// /// Provides access to the reader which allows control over the DataSet loop. @@ -101,7 +114,7 @@ type /// /// The caller must Free the returned object. /// - function GetFirst: T; + function GetFirst(AOrigin: TDaintyOrigin = doFirst): T; /// /// Returns the current row mapped to the specified class. Returns nil if no row is active. @@ -109,7 +122,7 @@ type /// /// The caller must Free the returned object. /// - function GetFirstOrDefault: T; + function GetFirstOrDefault(AOrigin: TDaintyOrigin = doFirst): T; /// @@ -119,7 +132,7 @@ type /// /// The caller must Free the returned object. /// - function GetSingle: T; + function GetSingle(AOrigin: TDaintyOrigin = doFirst): T; /// /// Returns the current row mapped to the specified class. Returns nil if no row is active @@ -128,7 +141,7 @@ type /// /// The caller must Free the returned object. /// - function GetSingleOrDefault: T; + function GetSingleOrDefault(AOrigin: TDaintyOrigin = doFirst): T; end; @@ -179,15 +192,28 @@ type /// Provides row to object mapping functionality. Usually accessed using the TDaintyDataSetHelper. /// TDainty = class + protected + class procedure GotoOrigin(ADataSet: TDataSet; AOrigin: TDaintyOrigin); public /// /// Returns a typed enumerable which iterates the DataSet and returns the mapped /// object for each row. /// /// + /// 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. /// - class function Rows(ADataSet: TDataSet): IEnumerable; + class function Rows(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): IEnumerable; + + /// + /// Returns the DataSet rows as a list of objects. + /// + /// + /// 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. + /// + class function List(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): TList; + /// /// Provides access to the mapper which allows control over the DataSet loop. @@ -202,7 +228,7 @@ type /// /// The caller must Free the returned object. /// - class function GetFirst(ADataSet: TDataSet): T; + class function GetFirst(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T; /// /// Returns the current row mapped to the specified class. Returns nil if no row is active. @@ -210,7 +236,7 @@ type /// /// The caller must Free the returned object. /// - class function GetFirstOrDefault(ADataSet: TDataSet): T; + class function GetFirstOrDefault(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T; /// @@ -220,7 +246,7 @@ type /// /// The caller must Free the returned object. /// - class function GetSingle(ADataSet: TDataSet): T; + class function GetSingle(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T; /// /// Returns the current row mapped to the specified class. Returns nil if no row is active @@ -229,7 +255,7 @@ type /// /// The caller must Free the returned object. /// - class function GetSingleOrDefault(ADataSet: TDataSet): T; + class function GetSingleOrDefault(ADataSet: TDataSet; AOrigin: TDaintyOrigin = doFirst): T; /// @@ -468,37 +494,43 @@ uses { TDaintyDataSetHelper } -function TDaintyDataSetHelper.Rows: IEnumerable; +function TDaintyDataSetHelper.Rows(AOrigin: TDaintyOrigin): IEnumerable; begin Result := TDainty.Rows(Self); end; +function TDaintyDataSetHelper.List(AOrigin: TDaintyOrigin): TList; +begin + Result := TDainty.List(Self, AOrigin); +end; + + function TDaintyDataSetHelper.GetRowReader: TDaintyReader; begin Result := TDainty.GetRowReader(Self); end; -function TDaintyDataSetHelper.GetFirst: T; +function TDaintyDataSetHelper.GetFirst(AOrigin: TDaintyOrigin): T; begin Result := TDainty.GetFirst(Self); end; -function TDaintyDataSetHelper.GetFirstOrDefault: T; +function TDaintyDataSetHelper.GetFirstOrDefault(AOrigin: TDaintyOrigin): T; begin Result := TDainty.GetFirstOrDefault(Self); end; -function TDaintyDataSetHelper.GetSingle: T; +function TDaintyDataSetHelper.GetSingle(AOrigin: TDaintyOrigin): T; begin Result := TDainty.GetSingle(Self); end; -function TDaintyDataSetHelper.GetSingleOrDefault: T; +function TDaintyDataSetHelper.GetSingleOrDefault(AOrigin: TDaintyOrigin): T; begin Result := TDainty.GetSingleOrDefault(Self); end; @@ -513,27 +545,51 @@ end; { TDainty } -class function TDainty.Rows(ADataSet: TDataSet): IEnumerable; +class function TDainty.Rows(ADataSet: TDataSet; AOrigin: TDaintyOrigin): IEnumerable; var reader: TDaintyReader; begin + GotoOrigin(ADataSet, AOrigin); reader := GetRowReader(ADataSet); Result := TDaintyEnumerable.Create(reader, ADataSet); end; +class function TDainty.List(ADataSet: TDataSet; AOrigin: TDaintyOrigin): TList; +var + reader: TDaintyReader; + +begin + GotoOrigin(ADataSet, AOrigin); + reader := GetRowReader(ADataSet); + + Result := TObjectList.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(ADataSet: TDataSet): TDaintyReader; begin Result := TDaintyRttiMapperFactory.ConstructReader(ADataSet); end; -class function TDainty.GetFirst(ADataSet: TDataSet): T; +class function TDainty.GetFirst(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T; var enumerator: IEnumerator; begin + GotoOrigin(ADataSet, AOrigin); enumerator := Rows(ADataSet).GetEnumerator; if not enumerator.MoveNext then @@ -543,7 +599,7 @@ begin end; -class function TDainty.GetFirstOrDefault(ADataSet: TDataSet): T; +class function TDainty.GetFirstOrDefault(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T; var enumerator: IEnumerator; @@ -557,11 +613,12 @@ begin end; -class function TDainty.GetSingle(ADataSet: TDataSet): T; +class function TDainty.GetSingle(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T; var enumerator: IEnumerator; begin + GotoOrigin(ADataSet, AOrigin); enumerator := Rows(ADataSet).GetEnumerator; if not enumerator.MoveNext then @@ -577,11 +634,12 @@ begin end; -class function TDainty.GetSingleOrDefault(ADataSet: TDataSet): T; +class function TDainty.GetSingleOrDefault(ADataSet: TDataSet; AOrigin: TDaintyOrigin): T; var enumerator: IEnumerator; begin + GotoOrigin(ADataSet, AOrigin); enumerator := Rows(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 } constructor TDaintyEnumerable.Create(AReader: TDaintyReader; ADataSet: TDataSet); diff --git a/UnitTests/DaintyFieldsTests.pas b/UnitTests/DaintyFieldsTests.pas index d29764d..3d9f969 100644 --- a/UnitTests/DaintyFieldsTests.pas +++ b/UnitTests/DaintyFieldsTests.pas @@ -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;