/// <summary>
        /// Returns the value from a field in a row of the specified table.
        /// The row is identified by the primary key field name and value that is passed along.
        /// The table is identified by the provided table name.
        /// This method can be used to retrieve information from 1:1 related secondary tables
        /// in scenarios where child items have related table that extend the child row's schema.
        /// In most scenarios, this overload is NOT needed. Use the simpler overloads instead!
        /// </summary>
        /// <typeparam name="TField">The type of the field.</typeparam>
        /// <param name="fieldName">Name of the field that contains the value.</param>
        /// <param name="ignoreNulls">Should nulls be returned (true) or should default values be provided for nulls (false)?</param>
        /// <param name="tableName">Name of the table that contains the field</param>
        /// <param name="searchExpression">Search (filter) expression used to identify the record in the secondary table</param>
        /// <returns>Value object</returns>
        /// <example>GetFieldValue("CustomerStatus", true, "ExtendedCustomerInformationTable", "cust_id = 'x'");</example>
        /// <remarks>May throw ArgumentException and RowNotInTableException.</remarks>
        protected virtual TField ReadFieldValue <TField>(string fieldName, bool ignoreNulls, string tableName, string searchExpression)
        {
            // TODO: We may have to do something with the search expression, which should also support maps

            var entity = ParentEntity as BusinessEntity;

            if (entity == null)
            {
                throw new NullReferenceException("Parent entity is not a business entity.");
            }

            fieldName = ((BusinessEntity)ParentEntity).GetInternalFieldName(fieldName, tableName);

            var secondaryTable = CurrentRow.Table.DataSet.Tables[tableName];

            // Now that we have the table, we can try to find the desired row
            var foundRows = secondaryTable.Select(searchExpression);

            // We expect to find exactly one row. If fewer or more rows get returned, we throw an error
            if (foundRows.Length != 1)
            {
                if (foundRows.Length > 1)
                {
                    // The record was not uniquely identified
                    throw new ArgumentException("Unable to find unique record: " + foundRows.Length.ToString(NumberFormatInfo.InvariantInfo) + " records returned by search expression '@searchExpression'", "searchExpression");
                }
                throw new RowNotInTableException("Search record not found by expression '@searchExpression'");
            }

            // We did find the row. We can now make sure it has the field we desire
            CheckColumn(fieldName, secondaryTable);

            // Finally, we are ready to retrieve the desired field
            return(BusinessEntityHelper.GetFieldValue <TField>(entity, foundRows[0].Table.DataSet, tableName, fieldName, foundRows[0], ignoreNulls));
        }
 /// <summary>
 /// Returns the value of the specified field in the database
 /// </summary>
 /// <typeparam name="TField">The type of the field.</typeparam>
 /// <param name="fieldName">Field name</param>
 /// <param name="ignoreNulls">Should nulls be ignored and returned as such (true) or should the be turned into default values (false)?</param>
 /// <param name="currentRow">Current data row, which contains the value we are interested in</param>
 /// <returns>Value object</returns>
 protected virtual TField ReadFieldValue <TField>(string fieldName, bool ignoreNulls, DataRow currentRow)
 {
     if (!(ParentEntity is BusinessEntity entity))
     {
         throw new NullReferenceException("Parent entity is not a business entity.");
     }
     return(BusinessEntityHelper.GetFieldValue <TField>(entity, currentRow.Table.DataSet, TableName, fieldName, currentRow, ignoreNulls));
 }
 /// <summary>
 /// Returns the value of the specified field in the database
 /// </summary>
 /// <param name="fieldName">Field name</param>
 /// <param name="ignoreNulls">Should nulls be ignored and returned as such (true) or should the be turned into default values (false)?</param>
 /// <returns>Value object</returns>
 protected virtual object GetFieldValue(string fieldName, bool ignoreNulls = false)
 {
     if (!(ParentEntity is BusinessEntity entity))
     {
         throw new NullReferenceException("Parent entity is not a business entity.");
     }
     return(BusinessEntityHelper.GetFieldValue <object>(entity, CurrentRow.Table.DataSet, TableName, fieldName, CurrentRow, ignoreNulls));
 }
        /// <summary>
        ///     Returns the value of the specified field in the database
        /// </summary>
        /// <typeparam name="TField">The type of the field.</typeparam>
        /// <param name="fieldName">Field name</param>
        /// <param name="mode">Accessing a field in the parent (link) or target (child/foreign) table?</param>
        /// <param name="ignoreNulls">
        ///     Should nulls be ignored and returned as such (true) or should the be turned into default
        ///     values (false)?
        /// </param>
        /// <returns>Value object</returns>
        protected virtual TField ReadFieldValue <TField>(string fieldName, XLinkItemAccessMode mode, bool ignoreNulls = false)
        {
            if (mode == XLinkItemAccessMode.CurrentTable)
            {
                return(ReadFieldValue <TField>(fieldName));
            }

            if (!(ParentEntity is BusinessEntity entity))
            {
                throw new NullReferenceException("Parent entity is not a business entity.");
            }
            return(BusinessEntityHelper.GetFieldValue <TField>(entity, CurrentTargetRow.Table.DataSet, CurrentTargetRow.Table.TableName, fieldName, CurrentTargetRow, ignoreNulls));
        }
        /// <summary>Sets the value of a field in the database. This overload allows to specify whether the value is set in the master table, or the target table.</summary>
        /// <typeparam name="TField">The type of the field.</typeparam>
        /// <param name="fieldName">Field name</param>
        /// <param name="value">New value</param>
        /// <param name="mode">Specifies whether we want to access the current (link) table ("Current") or the target table.</param>
        /// <param name="forceUpdate">
        ///     Should the value be set, even if it is the same as before (and therefore set the dirty flag
        ///     despite that there were no changes)?
        /// </param>
        /// <returns>True or False</returns>
        protected virtual bool WriteFieldValue <TField>(string fieldName, TField value, XLinkItemAccessMode mode, bool forceUpdate = false)
        {
            if (mode == XLinkItemAccessMode.CurrentTable)
            {
                return(SetFieldValue(fieldName, value, forceUpdate));
            }

            BusinessEntityHelper.CheckColumn(CurrentTargetRow.Table, fieldName);
            if (!(ParentEntity is BusinessEntity entity))
            {
                throw new NullReferenceException("Parent entity is not a business entity.");
            }
            return(BusinessEntityHelper.SetFieldValue(entity, fieldName, value, CurrentTargetRow.Table.TableName, CurrentRow.Table.DataSet, CurrentTargetRow, forceUpdate));
        }
        /// <summary>
        /// Sets the value of a field in the database
        /// </summary>
        /// <typeparam name="TField">The type of the field.</typeparam>
        /// <param name="fieldName">Field name</param>
        /// <param name="value">New value</param>
        /// <param name="forceUpdate">Should the value be set, even if it is the same as before (and therefore set the dirty flag despite that there were no changes)?</param>
        /// <param name="currentRow">Row that has the field that needs to be updated</param>
        /// <returns>True or False</returns>
        protected virtual bool WriteFieldValue <TField>(string fieldName, TField value, bool forceUpdate, DataRow currentRow)
        {
            if (!(ParentEntity is BusinessEntity entity))
            {
                throw new NullReferenceException("Parent entity is not a business entity.");
            }
            var retVal = BusinessEntityHelper.SetFieldValue(entity, fieldName, value, TableName, currentRow.Table.DataSet, currentRow, forceUpdate);

            entity.DataUpdated(fieldName, currentRow.Table.TableName);
            if (ParentCollection is EntitySubItemCollection collection)
            {
                collection.DataUpdated(fieldName, currentRow);
            }
            return(retVal);
        }
        /// <summary>
        ///     Returns the value of the specified field in the database
        /// </summary>
        /// <param name="fieldName">Field name</param>
        /// <param name="mode">Accessing a field in the parent (link) or target (child/foreign) table?</param>
        /// <param name="ignoreNulls">
        ///     Should nulls be ignored and returned as such (true) or should the be turned into default
        ///     values (false)?
        /// </param>
        /// <returns>Value object</returns>
        protected virtual object GetFieldValue(string fieldName, XLinkItemAccessMode mode, bool ignoreNulls = false)
        {
            if (mode == XLinkItemAccessMode.CurrentTable)
            {
                return(GetFieldValue(fieldName));
            }

            var entity = ParentEntity as BusinessEntity;

            if (entity == null)
            {
                throw new NullReferenceException("Parent entity is not a business entity.");
            }
            return(BusinessEntityHelper.GetFieldValue <object>(entity, CurrentTargetRow.Table.DataSet, CurrentTargetRow.Table.TableName, fieldName, CurrentTargetRow, ignoreNulls));
        }
        /// <summary>
        /// Sets the value of a field in the database in the table specified.
        /// The record of the table is identified by the search expression
        /// </summary>
        /// <param name="fieldName">Field name</param>
        /// <param name="value">New value</param>
        /// <param name="forceUpdate">Should the value be set, even if it is the same as before (and therefore set the dirty flag despite that there were no changes)?</param>
        /// <param name="tableName">Name of the table that holds the row that needs to be updated</param>
        /// <param name="searchExpression">Search expression used to identify the record that needs to be updated</param>
        /// <returns>True or False</returns>
        /// <example>SetFieldValue("MyField","xxx value",true,"MySecondaryTable","id = 'x'");</example>
        protected virtual bool SetFieldValue(string fieldName, object value, bool forceUpdate, string tableName, string searchExpression)
        {
            if (!(ParentEntity is BusinessEntity entity))
            {
                throw new NullReferenceException("Parent entity is not a business entity.");
            }

            // TODO: We should probably do something with the search expression, so that can work too with maps
            fieldName = entity.GetInternalFieldName(fieldName, tableName);
            tableName = entity.GetInternalTableName(tableName);

            // Before we can do anything else, we have to retrieve the appropriate table
            if (!CurrentRow.Table.DataSet.Tables.Contains(tableName))
            {
                // The specified table name does not exist. We need to throw an error!
                throw new ArgumentException("Table '@tableName' not in DataSet", "tableName");
            }
            var secondaryTable = CurrentRow.Table.DataSet.Tables[tableName];

            // Now that we have the table, we can try to find the desired row
            var matchingRows = secondaryTable.Select(searchExpression);

            // We expect to find exactly one row. If fewer or more rows get returned, we throw an error
            if (matchingRows.Length != 1)
            {
                if (matchingRows.Length > 1)
                {
                    // The record was not uniquely identified
                    throw new ArgumentException("Unable to find unique record: " + matchingRows.Length.ToString(NumberFormatInfo.InvariantInfo) + " records returned by search expression '@searchExpression'", "searchExpression");
                }
                throw new RowNotInTableException("Search record not found by expression '@searchExpression'");
            }

            // We did find the row. We can now make sure it has the field we desire
            CheckColumn(fieldName, secondaryTable);

            var retVal = BusinessEntityHelper.SetFieldValue(entity, fieldName, value, tableName, matchingRows[0].Table.DataSet, matchingRows[0], forceUpdate);

            entity.DataUpdated(fieldName, CurrentRow.Table.TableName);

            if (ParentCollection is EntitySubItemCollection collection)
            {
                collection.DataUpdated(fieldName, matchingRows[0]);
            }
            return(retVal);
        }
 /// <summary>
 /// This method can be used to make sure the default table in the internal DataSet has all the required fields.
 /// If the field (column) doesn't exist, it will be added.
 /// </summary>
 /// <param name="fieldName">Field name to check for.</param>
 /// <param name="tableToCheck">Table that is supposed to have this column.</param>
 /// <returns>true or false</returns>
 protected virtual bool CheckColumn(string fieldName, DataTable tableToCheck) => BusinessEntityHelper.CheckColumn(tableToCheck, fieldName);
 /// <summary>
 ///     Method used to check whether the current data row has a certain field
 ///     This overload allows passing a table object.
 /// </summary>
 /// <param name="fieldName">Field Name</param>
 /// <param name="tableRow">DataRow the field is a member of</param>
 /// <returns>True (if the column existed or has been added successfully) or False</returns>
 protected virtual bool CheckColumn(string fieldName, DataRow tableRow) => BusinessEntityHelper.CheckColumn(tableRow.Table, fieldName);