diff --git a/src/Gemstone.Data/Model/SecureTableOperations.cs b/src/Gemstone.Data/Model/SecureTableOperations.cs index d5010ba6e..14ae9b7d0 100644 --- a/src/Gemstone.Data/Model/SecureTableOperations.cs +++ b/src/Gemstone.Data/Model/SecureTableOperations.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Linq; using System.Reflection; @@ -40,10 +41,6 @@ namespace Gemstone.Data.Model; /// Modeled table. public class SecureTableOperations where T : class, new() { - /// - /// which performs DB operations. - /// - public TableOperations BaseOperations { get; } /// /// Creates a new @@ -63,8 +60,157 @@ public SecureTableOperations(AdoDataConnection connection) BaseOperations = new(connection); } + #region [ Properties ] + + /// + /// which performs DB operations. + /// + public TableOperations BaseOperations { get; } + + /// + /// Gets instance associated with this used for database operations. + /// + public AdoDataConnection Connection => BaseOperations.Connection; + + /// + /// Gets the table name defined for the modeled table, includes any escaping as defined in model. + /// + public string TableName => BaseOperations.TableName; + + /// + /// Gets the table name defined for the modeled table without any escape characters. + /// + /// + /// A table name will only be escaped if the model requested escaping with the . + /// + public string UnescapedTableName => BaseOperations.UnescapedTableName; + + /// + /// Gets the wildcard character used for pattern matching within queries. + /// + public string WildcardChar => BaseOperations.WildcardChar; + + /// + /// Gets flag that determines if modeled table has a primary key that is an identity field. + /// + public bool HasPrimaryKeyIdentityField => BaseOperations.HasPrimaryKeyIdentityField; + + /// + /// Gets or sets delegate used to handle table operation exceptions. + /// + /// + /// When exception handler is provided, table operations will not throw exceptions for database calls, any + /// encountered exceptions will be passed to handler for processing. Otherwise, exceptions will be thrown + /// on the call stack. + /// + public Action? ExceptionHandler => BaseOperations.ExceptionHandler; + + /// + /// Gets or sets flag that determines if field names should be treated as case-sensitive. Defaults to false. + /// + /// + /// In cases where modeled table fields have applied , this flag will be used + /// to properly update escaped field names that may be case-sensitive. For example, escaped field names in Oracle + /// are case-sensitive. This value is typically false. + /// + public bool UseCaseSensitiveFieldNames => BaseOperations.UseCaseSensitiveFieldNames; + + /// + /// Gets or sets primary key cache. + /// + /// + /// + /// The overloads that include paging parameters + /// cache the sorted and filtered primary keys of queried records between calls so that paging is fast and + /// efficient. Since the primary keys are cached, an instance of the should + /// exist per user session when using query functions that support pagination. In web based implementations, + /// the primary cache should be stored with user session state data and then restored between instances of + /// the that are created along with a connection that is opened per page. + /// + /// + /// The function should be called to manually clear cache when table + /// contents are known to have changed. Note that calls to any overload + /// will automatically clear any existing primary key cache. + /// + /// + /// Primary keys values are stored in data table without interpretation, i.e., in their raw form as queried + /// from the database. Primary key data in cache will be encrypted for models with primary key fields that + /// are marked with the + /// + /// + public DataTable? PrimaryKeyCache => BaseOperations.PrimaryKeyCache; + + /// + /// Gets or sets root record restriction that applies to query table operations. + /// + /// + /// + /// Defining a root query restriction creates a base query filter that gets applied to all query operations, + /// even when another restriction is applied - in this case the root restriction will be pre-pended to the + /// specified query, e.g.: + /// + /// restriction = RootQueryRestriction + restriction; + /// + /// A root query restriction is useful to apply a common state to the query operations, e.g., always + /// filtering records for a specific user or context. + /// + /// + /// A root query restriction can be manually assigned to a instance or + /// automatically assigned by marking a model with the . + /// + /// + /// If any of the reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public RecordRestriction? RootQueryRestriction => BaseOperations.RootQueryRestriction; + + /// + /// Gets or sets flag that determines if should be applied to update operations. + /// + /// + /// + /// If only references primary key fields, then this property value should be set + /// to false since default update operations for a modeled record already work against primary key fields. + /// + /// + /// This flag can be manually set per instance or handled automatically by marking + /// a model with the and assigning a value to the attribute property + /// . + /// + /// + public bool ApplyRootQueryRestrictionToUpdates => BaseOperations.ApplyRootQueryRestrictionToUpdates; + + /// + /// Gets or sets flag that determines if should be applied to delete operations. + /// + /// + /// + /// If only references primary key fields, then this property value should be set + /// to false since default delete operations for a modeled record already work against primary key fields. + /// + /// + /// This flag can be manually set per instance or handled automatically by marking + /// a model with the and assigning a value to the attribute property + /// . + /// + /// + public bool ApplyRootQueryRestrictionToDeletes => BaseOperations.ApplyRootQueryRestrictionToDeletes; + + #endregion + #region [ Methods ] + /// + /// Creates a new modeled record instance, applying any modeled default values as specified by a + /// or on the + /// model properties. + /// + /// New modeled record instance with any defined default values applied. + public T? NewRecord() => BaseOperations.NewRecord(); + /// /// Transforms a into an equivalent , as defined by the model's . /// @@ -793,6 +939,43 @@ public int DeleteRecord(ClaimsPrincipal principal, RecordRestriction? restrictio public Task DeleteRecordAsync(ClaimsPrincipal principal, RecordRestriction? restriction, CancellationToken cancellationToken, bool? applyRootQueryRestriction = null) => BaseOperations.DeleteRecordAsync(restriction + GetClaimRecordRestriction(principal), cancellationToken, applyRootQueryRestriction); + /// + /// Deletes the specified modeled table from the database. + /// + /// Claims principal which is making the request. + /// Record to delete. + /// Number of rows affected. + public int DeleteRecord(ClaimsPrincipal principal, T record) + { + IEnumerable whereElements = BaseOperations + .GetPrimaryKeyFieldNames(true) + .Select((primaryKeyField, index) => $"{primaryKeyField} = {index}"); + RecordRestriction recordRestrict = new RecordRestriction( + string.Join(" AND ", whereElements), + BaseOperations.GetPrimaryKeys(record) + ); + return DeleteRecord(principal, recordRestrict); + } + + /// + /// Deletes the specified modeled table from the database. + /// + /// Claims principal which is making the request. + /// Record to delete. + /// Propagates notification that operations should be canceled. + /// Number of rows affected. + public Task DeleteRecordAsync(ClaimsPrincipal principal, T record, CancellationToken cancellationToken) + { + IEnumerable whereElements = BaseOperations + .GetPrimaryKeyFieldNames(true) + .Select((primaryKeyField, index) => $"{primaryKeyField} = {index}"); + RecordRestriction recordRestrict = new RecordRestriction( + string.Join(" AND ", whereElements), + BaseOperations.GetPrimaryKeys(record) + ); + return DeleteRecordAsync(principal, recordRestrict, cancellationToken); + } + /// /// Deletes the records referenced by the specified SQL filter expression and parameters. ///