Unified · Split
| @@ -180,10 +180,9 @@ namespace GSF · 179 unmodified lines | ||
| 180 | 180 | /// <summary> |
| 181 | 181 | /// Converts this string into the specified type. |
| 182 | 182 | /// </summary> |
| 183 | − /// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam> | |
| 184 | 183 | /// <param name="value">Source string to convert to type.</param> |
| 185 | 184 | /// <param name="type"><see cref="Type"/> to convert string to.</param> |
| 186 | − /// <returns><see cref="string"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns> | |
| 185 | + /// <returns><see cref="string"/> converted to specified <see cref="Type"/>; default value of specified type if conversion fails.</returns> | |
| 187 | 186 | /// <remarks> |
| 188 | 187 | /// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="string"/> to the specified <paramref name="type"/>, |
| 189 | 188 | /// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same |
| @@ -198,11 +197,10 @@ namespace GSF · 8 unmodified lines | ||
| 198 | 197 | /// <summary> |
| 199 | 198 | /// Converts this string into the specified type. |
| 200 | 199 | /// </summary> |
| 201 | − /// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam> | |
| 202 | 200 | /// <param name="value">Source string to convert to type.</param> |
| 203 | 201 | /// <param name="type"><see cref="Type"/> to convert string to.</param> |
| 204 | 202 | /// <param name="culture"><see cref="CultureInfo"/> to use for the conversion.</param> |
| 205 | − /// <returns><see cref="string"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns> | |
| 203 | + /// <returns><see cref="string"/> converted to specified <see cref="Type"/>; default value of specified type if conversion fails.</returns> | |
| 206 | 204 | /// <remarks> |
| 207 | 205 | /// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="string"/> to the specified <paramref name="type"/>, |
| 208 | 206 | /// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same |
| @@ -21,6 +21,7 @@ · 20 unmodified lines | ||
| 21 | 21 | // |
| 22 | 22 | //****************************************************************************************************** |
| 23 | 23 | |
| 24 | +using System; | |
| 24 | 25 | using System.ComponentModel.DataAnnotations; |
| 25 | 26 | |
| 26 | 27 | namespace GSF.ComponentModel.DataAnnotations |
| @@ -29,7 +30,8 @@ namespace GSF.ComponentModel.DataAnnotations · 2 unmodified lines | ||
| 29 | 30 | /// Represents a <see cref="RegularExpressionAttribute"/> for validating Acronyms, i.e., expressions |
| 30 | 31 | /// that consist only of upper case letters, numbers, '!', '-', '@', '#', '_' , '.' or '$'. |
| 31 | 32 | /// </summary> |
| 32 | − public class AcronymValidationAttribute : RegularExpressionAttribute | |
| 33 | + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] | |
| 34 | + public sealed class AcronymValidationAttribute : RegularExpressionAttribute | |
| 33 | 35 | { |
| 34 | 36 | /// <summary> |
| 35 | 37 | /// Defines the regular expression pattern used to validate value. |
| @@ -21,6 +21,7 @@ · 20 unmodified lines | ||
| 21 | 21 | // |
| 22 | 22 | //****************************************************************************************************** |
| 23 | 23 | |
| 24 | +using System; | |
| 24 | 25 | using System.ComponentModel.DataAnnotations; |
| 25 | 26 | |
| 26 | 27 | namespace GSF.ComponentModel.DataAnnotations |
| @@ -28,7 +29,8 @@ namespace GSF.ComponentModel.DataAnnotations · 1 unmodified line | ||
| 28 | 29 | /// <summary> |
| 29 | 30 | /// Represents a <see cref="RegularExpressionAttribute"/> for validating URL's. |
| 30 | 31 | /// </summary> |
| 31 | − public class UrlValidationAttribute : RegularExpressionAttribute | |
| 32 | + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] | |
| 33 | + public sealed class UrlValidationAttribute : RegularExpressionAttribute | |
| 32 | 34 | { |
| 33 | 35 | /// <summary> |
| 34 | 36 | /// Defines the regular expression pattern used to validate value. |
| @@ -21,11 +21,14 @@ · 20 unmodified lines | ||
| 21 | 21 | // |
| 22 | 22 | //****************************************************************************************************** |
| 23 | 23 | |
| 24 | +using System; | |
| 25 | + | |
| 24 | 26 | namespace GSF.ComponentModel |
| 25 | 27 | { |
| 26 | 28 | /// <summary> |
| 27 | 29 | /// Defines a C# expression attribute that when evaluated will specify the default value for a property. |
| 28 | 30 | /// </summary> |
| 31 | + [AttributeUsage(AttributeTargets.All)] | |
| 29 | 32 | public sealed class DefaultValueExpressionAttribute : ValueExpressionAttributeBase |
| 30 | 33 | { |
| 31 | 34 | /// <summary> |
| @@ -21,11 +21,14 @@ · 20 unmodified lines | ||
| 21 | 21 | // |
| 22 | 22 | //****************************************************************************************************** |
| 23 | 23 | |
| 24 | +using System; | |
| 25 | + | |
| 24 | 26 | namespace GSF.ComponentModel |
| 25 | 27 | { |
| 26 | 28 | /// <summary> |
| 27 | 29 | /// Defines a C# expression attribute that when evaluated will specify an updated value for a property. |
| 28 | 30 | /// </summary> |
| 31 | + [AttributeUsage(AttributeTargets.All)] | |
| 29 | 32 | public sealed class UpdateValueExpressionAttribute : ValueExpressionAttributeBase |
| 30 | 33 | { |
| 31 | 34 | /// <summary> |
| @@ -225,6 +225,7 @@ namespace GSF.ComponentModel · 224 unmodified lines | ||
| 225 | 225 | /// <returns> |
| 226 | 226 | /// Generated delegate that will create new <typeparamref name="T"/> instances with default values applied. |
| 227 | 227 | /// </returns> |
| 228 | + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] | |
| 228 | 229 | public static Func<T> CreateInstanceForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : ValueExpressionAttributeBase |
| 229 | 230 | { |
| 230 | 231 | return () => CreateInstanceForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry)(new MinimumScope()); |
| @@ -278,6 +279,7 @@ namespace GSF.ComponentModel · 47 unmodified lines | ||
| 278 | 279 | /// Generated delegate that will update <typeparamref name="T"/> instances with update expression values applied. |
| 279 | 280 | /// </returns> |
| 280 | 281 | /// <typeparam name="TValueExpressionAttribute"><see cref="ValueExpressionAttributeBase"/> parameter type.</typeparam> |
| 282 | + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] | |
| 281 | 283 | public static Action<T> UpdateInstanceForType<TValueExpressionAttribute>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : ValueExpressionAttributeBase |
| 282 | 284 | { |
| 283 | 285 | return instance => UpdateInstanceForType<TValueExpressionAttribute, MinimumScope>(properties, typeRegistry)(new MinimumScope { Instance = instance }); |
| @@ -345,6 +347,7 @@ namespace GSF.ComponentModel · 61 unmodified lines | ||
| 345 | 347 | /// </returns> |
| 346 | 348 | /// <typeparam name="TValueExpressionAttribute"><see cref="ValueExpressionAttributeBase"/> parameter type.</typeparam> |
| 347 | 349 | /// <typeparam name="TExpressionScope"><see cref="ValueExpressionScopeBase{T}"/> parameter type.</typeparam> |
| 350 | + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] | |
| 348 | 351 | [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] |
| 349 | 352 | public static Func<TExpressionScope, T> CreateInstanceForType<TValueExpressionAttribute, TExpressionScope>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : ValueExpressionAttributeBase where TExpressionScope : ValueExpressionScopeBase<T> |
| 350 | 353 | { |
| @@ -473,6 +476,7 @@ namespace GSF.ComponentModel · 122 unmodified lines | ||
| 473 | 476 | /// </returns> |
| 474 | 477 | /// <typeparam name="TValueExpressionAttribute"><see cref="ValueExpressionAttributeBase"/> parameter type.</typeparam> |
| 475 | 478 | /// <typeparam name="TExpressionScope"><see cref="ValueExpressionScopeBase{T}"/> parameter type.</typeparam> |
| 479 | + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] | |
| 476 | 480 | [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] |
| 477 | 481 | public static Action<TExpressionScope> UpdateInstanceForType<TValueExpressionAttribute, TExpressionScope>(IEnumerable<PropertyInfo> properties = null, TypeRegistry typeRegistry = null) where TValueExpressionAttribute : ValueExpressionAttributeBase where TExpressionScope : ValueExpressionScopeBase<T> |
| 478 | 482 | { |
| @@ -562,12 +566,14 @@ namespace GSF.ComponentModel · 83 unmodified lines | ||
| 562 | 566 | return Expression.Call(instance, property.SetMethod, getParsedValue); |
| 563 | 567 | } |
| 564 | 568 | |
| 569 | + // Function referenced through reflection - see s_addCachedValueMethod | |
| 565 | 570 | private static void AddCachedValue(PropertyInfo property, object value) |
| 566 | 571 | { |
| 567 | 572 | lock (s_cachedExpressionValues) |
| 568 | 573 | s_cachedExpressionValues.GetOrAdd(property, value); |
| 569 | 574 | } |
| 570 | 575 | |
| 576 | + // Function referenced through reflection - see s_getCachedValueMethod | |
| 571 | 577 | private static Tuple<bool, object> GetCachedValue(PropertyInfo property) |
| 572 | 578 | { |
| 573 | 579 | bool exists; |
| @@ -225,7 +225,9 @@ namespace GSF.Data.Model · 224 unmodified lines | ||
| 225 | 225 | /// <param name="searchText">Text to search.</param> |
| 226 | 226 | /// <returns>An enumerable of modeled table row instances for queried records.</returns> |
| 227 | 227 | /// <para> |
| 228 | − /// This function is used for record paging. Primary keys are cached server-side, typically per user session, to maintain desired per-page sort order. | |
| 228 | + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, | |
| 229 | + /// to maintain desired per-page sort order. Call <see cref="ClearPrimaryKeyCache"/> to manually clear cache | |
| 230 | + /// when table contents are known to have changed. | |
| 229 | 231 | /// </para> |
| 230 | 232 | /// <para> |
| 231 | 233 | /// This is a convenience call to <see cref="QueryRecords(string, bool, int, int, RecordRestriction)"/> where restriction |
| @@ -243,7 +245,9 @@ namespace GSF.Data.Model · 11 unmodified lines | ||
| 243 | 245 | /// <param name="restriction">Record restriction to apply, if any.</param> |
| 244 | 246 | /// <returns>An enumerable of modeled table row instances for queried records.</returns> |
| 245 | 247 | /// <remarks> |
| 246 | − /// This function is used for record paging. Primary keys are cached server-side, typically per user session, to maintain desired per-page sort order. | |
| 248 | + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, | |
| 249 | + /// to maintain desired per-page sort order. Call <see cref="ClearPrimaryKeyCache"/> to manually clear cache | |
| 250 | + /// when table contents are known to have changed. | |
| 247 | 251 | /// </remarks> |
| 248 | 252 | IEnumerable QueryRecords(string sortField, bool ascending, int page, int pageSize, RecordRestriction restriction = null); |
| 249 | 253 | |
| @@ -552,5 +556,27 @@ namespace GSF.Data.Model · 302 unmodified lines | ||
| 552 | 556 | /// <see cref="SearchableAttribute"/> and specified <paramref name="searchText"/>. |
| 553 | 557 | /// </returns> |
| 554 | 558 | RecordRestriction GetSearchRestriction(string searchText); |
| 559 | + | |
| 560 | + /// <summary> | |
| 561 | + /// Calculates the size of the current primary key cache. | |
| 562 | + /// </summary> | |
| 563 | + /// <returns></returns> | |
| 564 | + int GetPrimaryKeyCacheSize(); | |
| 565 | + | |
| 566 | + /// <summary> | |
| 567 | + /// Clears the primary key cache for this <see cref="ITableOperations"/> instance. | |
| 568 | + /// </summary> | |
| 569 | + /// <remarks> | |
| 570 | + /// <para> | |
| 571 | + /// This method is intended to be used in conjunction with calls to the overloads for | |
| 572 | + /// <see cref="QueryRecords(string, bool, int, int, RecordRestriction)"/> which are | |
| 573 | + /// used for record pagination. | |
| 574 | + /// </para> | |
| 575 | + /// <para> | |
| 576 | + /// If record set is known to have changed outside purview of this class, this method | |
| 577 | + /// should be called so that primary key cache can be reloaded. | |
| 578 | + /// </para> | |
| 579 | + /// </remarks> | |
| 580 | + void ClearPrimaryKeyCache(); | |
| 555 | 581 | } |
| 556 | 582 | } |
| @@ -21,17 +21,19 @@ · 20 unmodified lines | ||
| 21 | 21 | // |
| 22 | 22 | //****************************************************************************************************** |
| 23 | 23 | |
| 24 | +using System; | |
| 24 | 25 | using System.Data; |
| 25 | 26 | using System.Diagnostics.CodeAnalysis; |
| 26 | 27 | using System.Linq; |
| 27 | 28 | using System.Runtime.CompilerServices; |
| 29 | +using GSF.Collections; | |
| 28 | 30 | |
| 29 | 31 | namespace GSF.Data.Model |
| 30 | 32 | { |
| 31 | 33 | /// <summary> |
| 32 | 34 | /// Defines a parameterized record restriction that can be applied to queries. |
| 33 | 35 | /// </summary> |
| 34 | − public class RecordRestriction | |
| 36 | + public class RecordRestriction : IEquatable<RecordRestriction> | |
| 35 | 37 | { |
| 36 | 38 | #region [ Members ] |
| 37 | 39 | |
| @@ -53,25 +55,17 @@ namespace GSF.Data.Model · 15 unmodified lines | ||
| 53 | 55 | /// will be updated to reflect what is defined in the user model. |
| 54 | 56 | /// </para> |
| 55 | 57 | /// </remarks> |
| 56 | − public string FilterExpression; | |
| 58 | + public readonly string FilterExpression; | |
| 57 | 59 | |
| 58 | 60 | /// <summary> |
| 59 | 61 | /// Defines restriction parameter values. |
| 60 | 62 | /// </summary> |
| 61 | − public object[] Parameters; | |
| 63 | + public readonly object[] Parameters; | |
| 62 | 64 | |
| 63 | 65 | #endregion |
| 64 | 66 | |
| 65 | 67 | #region [ Constructors ] |
| 66 | 68 | |
| 67 | − /// <summary> | |
| 68 | − /// Creates a new parameterized <see cref="RecordRestriction"/>. | |
| 69 | − /// </summary> | |
| 70 | − public RecordRestriction() | |
| 71 | − { | |
| 72 | − Parameters = new object[0]; | |
| 73 | − } | |
| 74 | − | |
| 75 | 69 | /// <summary> |
| 76 | 70 | /// Creates a new parameterized <see cref="RecordRestriction"/> with the specified SQL filter |
| 77 | 71 | /// expression and parameters. |
| @@ -99,6 +93,45 @@ namespace GSF.Data.Model · 21 unmodified lines | ||
| 99 | 93 | Parameters = parameters; |
| 100 | 94 | } |
| 101 | 95 | |
| 96 | + /// <summary> | |
| 97 | + /// Determines whether the specified object is equal to the current object. | |
| 98 | + /// </summary> | |
| 99 | + /// <param name="obj">An object to compare with this object.</param> | |
| 100 | + /// <returns><c>true</c> if the current object is equal to the <paramref name="obj" /> parameter; otherwise, <c>false</c>.</returns> | |
| 101 | + public override bool Equals(object obj) | |
| 102 | + { | |
| 103 | + RecordRestriction other = obj as RecordRestriction; | |
| 104 | + return (object)other != null && Equals(other); | |
| 105 | + } | |
| 106 | + | |
| 107 | + /// <summary> | |
| 108 | + /// Indicates whether the current object is equal to another object of the same type. | |
| 109 | + /// </summary> | |
| 110 | + /// <param name="other">An object to compare with this object.</param> | |
| 111 | + /// <returns><c>true</c> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <c>false</c>.</returns> | |
| 112 | + public bool Equals(RecordRestriction other) | |
| 113 | + { | |
| 114 | + if (ReferenceEquals(this, other)) | |
| 115 | + return true; | |
| 116 | + | |
| 117 | + if (FilterExpression.Equals(other?.FilterExpression, StringComparison.Ordinal)) | |
| 118 | + return Parameters.CompareTo(other?.Parameters, true) == 0; | |
| 119 | + | |
| 120 | + return false; | |
| 121 | + } | |
| 122 | + | |
| 123 | + /// <summary> | |
| 124 | + /// Serves as the default hash function. | |
| 125 | + /// </summary> | |
| 126 | + /// <returns>A hash code for the current <see cref="RecordRestriction"/>.</returns> | |
| 127 | + public override int GetHashCode() | |
| 128 | + { | |
| 129 | + unchecked | |
| 130 | + { | |
| 131 | + return ((FilterExpression?.GetHashCode() ?? 0) * 397) ^ (Parameters?.GetHashCode() ?? 0); | |
| 132 | + } | |
| 133 | + } | |
| 134 | + | |
| 102 | 135 | #endregion |
| 103 | 136 | |
| 104 | 137 | #region [ Operators ] |
| @@ -110,6 +143,34 @@ namespace GSF.Data.Model · 5 unmodified lines | ||
| 110 | 143 | /// <returns>Record operation representing the specified filter expression.</returns> |
| 111 | 144 | public static implicit operator RecordRestriction(string value) => new RecordRestriction(value); |
| 112 | 145 | |
| 146 | + /// <summary> | |
| 147 | + /// Compares to record restrictions for equality. | |
| 148 | + /// </summary> | |
| 149 | + /// <param name="left"><see cref="RecordRestriction"/> left operand.</param> | |
| 150 | + /// <param name="right"><see cref="RecordRestriction"/> right operand.</param> | |
| 151 | + /// <returns><c>true</c> if record restrictions are equal; otherwise, <c>false</c>.</returns> | |
| 152 | + public static bool operator ==(RecordRestriction left, RecordRestriction right) | |
| 153 | + { | |
| 154 | + if ((object)left == null && (object)right == null) | |
| 155 | + return true; | |
| 156 | + | |
| 157 | + return left?.Equals(right) ?? false; | |
| 158 | + } | |
| 159 | + | |
| 160 | + /// <summary> | |
| 161 | + /// Compares to record restrictions for inequality. | |
| 162 | + /// </summary> | |
| 163 | + /// <param name="left"><see cref="RecordRestriction"/> left operand.</param> | |
| 164 | + /// <param name="right"><see cref="RecordRestriction"/> right operand.</param> | |
| 165 | + /// <returns><c>true</c> if record restrictions are not equal; otherwise, <c>false</c>.</returns> | |
| 166 | + public static bool operator !=(RecordRestriction left, RecordRestriction right) | |
| 167 | + { | |
| 168 | + if ((object)left == null && (object)right == null) | |
| 169 | + return false; | |
| 170 | + | |
| 171 | + return !left?.Equals(right) ?? true; | |
| 172 | + } | |
| 173 | + | |
| 113 | 174 | /// <summary> |
| 114 | 175 | /// Combines two record restrictions with an AND condition. |
| 115 | 176 | /// </summary> |
| @@ -124,7 +185,6 @@ namespace GSF.Data.Model · 8 unmodified lines | ||
| 124 | 185 | /// <c>null</c>, empty or whitespace and the other parameter's filter expression is defined, then |
| 125 | 186 | /// the parameter that has a filter expression will be returned. |
| 126 | 187 | /// </remarks> |
| 127 | − [SuppressMessage("Microsoft.Design", "CA1013:OverloadOperatorEqualsOnOverloadingAddAndSubtract")] | |
| 128 | 188 | public static RecordRestriction operator +(RecordRestriction left, RecordRestriction right) |
| 129 | 189 | { |
| 130 | 190 | return CombineAnd(left, right); |
| @@ -76,6 +76,7 @@ namespace GSF.Data.Model · 75 unmodified lines | ||
| 76 | 76 | private Action<Exception> m_exceptionHandler; |
| 77 | 77 | private IEnumerable<DataRow> m_primaryKeyCache; |
| 78 | 78 | private string m_lastSortField; |
| 79 | + private RecordRestriction m_lastRestriction; | |
| 79 | 80 | private bool m_useCaseSensitiveFieldNames; |
| 80 | 81 | private readonly string m_countSql; |
| 81 | 82 | private readonly string m_orderBySql; |
| @@ -609,7 +610,9 @@ namespace GSF.Data.Model · 527 unmodified lines | ||
| 609 | 610 | /// <returns>An enumerable of modeled table row instances for queried records.</returns> |
| 610 | 611 | /// <remarks> |
| 611 | 612 | /// <para> |
| 612 | − /// This function is used for record paging. Primary keys are cached server-side, typically per user session, to maintain desired per-page sort order. | |
| 613 | + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, | |
| 614 | + /// to maintain desired per-page sort order. Call <see cref="ClearPrimaryKeyCache"/> to manually clear cache | |
| 615 | + /// when table contents are known to have changed. | |
| 613 | 616 | /// </para> |
| 614 | 617 | /// <para> |
| 615 | 618 | /// This is a convenience call to <see cref="QueryRecords(string, bool, int, int, RecordRestriction)"/> where restriction |
| @@ -636,14 +639,18 @@ namespace GSF.Data.Model · 20 unmodified lines | ||
| 636 | 639 | /// <param name="restriction">Record restriction to apply, if any.</param> |
| 637 | 640 | /// <returns>An enumerable of modeled table row instances for queried records.</returns> |
| 638 | 641 | /// <remarks> |
| 639 | − /// This function is used for record paging. Primary keys are cached server-side, typically per user session, to maintain desired per-page sort order. | |
| 642 | + /// <para> | |
| 643 | + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, | |
| 644 | + /// to maintain desired per-page sort order. Call <see cref="ClearPrimaryKeyCache"/> to manually clear cache | |
| 645 | + /// when table contents are known to have changed. | |
| 646 | + /// </para> | |
| 640 | 647 | /// </remarks> |
| 641 | 648 | public IEnumerable<T> QueryRecords(string sortField, bool ascending, int page, int pageSize, RecordRestriction restriction = null) |
| 642 | 649 | { |
| 643 | 650 | if (string.IsNullOrWhiteSpace(sortField)) |
| 644 | 651 | sortField = s_fieldNames[s_primaryKeyProperties[0].Name]; |
| 645 | 652 | |
| 646 | − if ((object)m_primaryKeyCache == null || string.Compare(sortField, m_lastSortField, StringComparison.OrdinalIgnoreCase) != 0) | |
| 653 | + if ((object)m_primaryKeyCache == null || !sortField.Equals(m_lastSortField, StringComparison.OrdinalIgnoreCase) || restriction != m_lastRestriction) | |
| 647 | 654 | { |
| 648 | 655 | string orderByExpression = $"{sortField}{(ascending ? "" : " DESC")}"; |
| 649 | 656 | string sqlExpression = null; |
| @@ -660,7 +667,6 @@ namespace GSF.Data.Model · 10 unmodified lines | ||
| 660 | 667 | sqlExpression = string.Format(m_orderByWhereSql, UpdateFieldNames(restriction.FilterExpression), orderByExpression); |
| 661 | 668 | m_primaryKeyCache = m_connection.RetrieveData(sqlExpression, restriction.Parameters).AsEnumerable(); |
| 662 | 669 | } |
| 663 | − | |
| 664 | 670 | } |
| 665 | 671 | catch (Exception ex) |
| 666 | 672 | { |
| @@ -674,6 +680,7 @@ namespace GSF.Data.Model · 7 unmodified lines | ||
| 674 | 680 | } |
| 675 | 681 | |
| 676 | 682 | m_lastSortField = sortField; |
| 683 | + m_lastRestriction = restriction; | |
| 677 | 684 | } |
| 678 | 685 | |
| 679 | 686 | return m_primaryKeyCache.ToPagedList(page, pageSize).Select(row => LoadRecord(row.ItemArray)).Where(record => record != null); |
| @@ -1479,6 +1486,34 @@ namespace GSF.Data.Model · 799 unmodified lines | ||
| 1479 | 1486 | return new RecordRestriction(multiKeyWordFilter.ToString(), keyWords.SelectMany(keyWord => new object[] { $"%{keyWord}%", keyWord }).ToArray()); |
| 1480 | 1487 | } |
| 1481 | 1488 | |
| 1489 | + /// <summary> | |
| 1490 | + /// Calculates the size of the current primary key cache. | |
| 1491 | + /// </summary> | |
| 1492 | + /// <returns></returns> | |
| 1493 | + public int GetPrimaryKeyCacheSize() | |
| 1494 | + { | |
| 1495 | + return m_primaryKeyCache?.Count() ?? 0; | |
| 1496 | + } | |
| 1497 | + | |
| 1498 | + /// <summary> | |
| 1499 | + /// Clears the primary key cache for this <see cref="TableOperations{T}"/> instance. | |
| 1500 | + /// </summary> | |
| 1501 | + /// <remarks> | |
| 1502 | + /// <para> | |
| 1503 | + /// This method is intended to be used in conjunction with calls to the overloads for | |
| 1504 | + /// <see cref="QueryRecords(string, bool, int, int, RecordRestriction)"/> which are | |
| 1505 | + /// used for record pagination. | |
| 1506 | + /// </para> | |
| 1507 | + /// <para> | |
| 1508 | + /// If record set is known to have changed outside purview of this class, this method | |
| 1509 | + /// should be called so that primary key cache can be reloaded. | |
| 1510 | + /// </para> | |
| 1511 | + /// </remarks> | |
| 1512 | + public void ClearPrimaryKeyCache() | |
| 1513 | + { | |
| 1514 | + m_primaryKeyCache = null; | |
| 1515 | + } | |
| 1516 | + | |
| 1482 | 1517 | // Derive table name, escaping it if requested by model |
| 1483 | 1518 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 1484 | 1519 | private string GetEscapedTableName() |
| @@ -23,6 +23,7 @@ · 22 unmodified lines | ||
| 23 | 23 | |
| 24 | 24 | using System; |
| 25 | 25 | using System.ComponentModel; |
| 26 | +using System.Diagnostics.CodeAnalysis; | |
| 26 | 27 | using System.IO; |
| 27 | 28 | using System.Security; |
| 28 | 29 | |
| @@ -338,6 +339,7 @@ namespace GSF.IO · 309 unmodified lines | ||
| 338 | 339 | /// Releases the unmanaged resources used by the <see cref="SafeFileWatcher"/> object and optionally releases the managed resources. |
| 339 | 340 | /// </summary> |
| 340 | 341 | /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> |
| 342 | + [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "m_fileSystemWatcher")] | |
| 341 | 343 | protected virtual void Dispose(bool disposing) |
| 342 | 344 | { |
| 343 | 345 | if (m_disposed) |
| @@ -156,6 +156,8 @@ · 155 unmodified lines | ||
| 156 | 156 | <Compile Include="Embedded\ISecurityService.cs" /> |
| 157 | 157 | <Compile Include="Hosting\WebPageController.cs" /> |
| 158 | 158 | <Compile Include="Hosting\WebServer.cs" /> |
| 159 | + <Compile Include="Hubs\DataContextHubClient.cs" /> | |
| 160 | + <Compile Include="Hubs\DataContextOperations.cs" /> | |
| 159 | 161 | <Compile Include="Hubs\HubClientBase.cs" /> |
| 160 | 162 | <Compile Include="Hubs\HubClientOperationsBase.cs" /> |
| 161 | 163 | <Compile Include="Hubs\IHubClient.cs" /> |
| @@ -0,0 +1,72 @@ | ||
| 1 | +//****************************************************************************************************** | |
| 2 | +// DataContextHubClient.cs - Gbtc | |
| 3 | +// | |
| 4 | +// Copyright © 2017, Grid Protection Alliance. All Rights Reserved. | |
| 5 | +// | |
| 6 | +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | |
| 7 | +// the NOTICE file distributed with this work for additional information regarding copyright ownership. | |
| 8 | +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | |
| 9 | +// file except in compliance with the License. You may obtain a copy of the License at: | |
| 10 | +// | |
| 11 | +// http://opensource.org/licenses/MIT | |
| 12 | +// | |
| 13 | +// Unless agreed to in writing, the subject software distributed under the License is distributed on an | |
| 14 | +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | |
| 15 | +// License for the specific language governing permissions and limitations. | |
| 16 | +// | |
| 17 | +// Code Modification History: | |
| 18 | +// ---------------------------------------------------------------------------------------------------- | |
| 19 | +// 04/26/2017 - J. Ritchie Carroll | |
| 20 | +// Generated original version of source code. | |
| 21 | +// | |
| 22 | +//****************************************************************************************************** | |
| 23 | + | |
| 24 | +using GSF.Web.Model; | |
| 25 | +using Microsoft.AspNet.SignalR; | |
| 26 | + | |
| 27 | +namespace GSF.Web.Hubs | |
| 28 | +{ | |
| 29 | + /// <summary> | |
| 30 | + /// Represents a SignalR <see cref="Hub"/> client session instance of a data context. | |
| 31 | + /// </summary> | |
| 32 | + public class DataContextHubClient : HubClientBase | |
| 33 | + { | |
| 34 | + #region [ Members ] | |
| 35 | + | |
| 36 | + // Fields | |
| 37 | + private DataContext m_dataContext; | |
| 38 | + private bool m_disposed; | |
| 39 | + | |
| 40 | + #endregion | |
| 41 | + | |
| 42 | + #region [ Methods ] | |
| 43 | + | |
| 44 | + /// <summary> | |
| 45 | + /// Gets <see cref="Model.DataContext"/> instance for the current SignalR hub session, creating it if needed. | |
| 46 | + /// </summary> | |
| 47 | + public DataContext GetDataContext(string settingsCategory) => m_dataContext ?? (m_dataContext = new DataContext(settingsCategory ?? "systemSettings", exceptionHandler: ex => LogException(ex))); | |
| 48 | + | |
| 49 | + /// <summary> | |
| 50 | + /// Releases the unmanaged resources used by the <see cref="DataContextHubClient"/> object and optionally releases the managed resources. | |
| 51 | + /// </summary> | |
| 52 | + /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> | |
| 53 | + protected override void Dispose(bool disposing) | |
| 54 | + { | |
| 55 | + if (!m_disposed) | |
| 56 | + { | |
| 57 | + try | |
| 58 | + { | |
| 59 | + if (disposing) | |
| 60 | + m_dataContext?.Dispose(); | |
| 61 | + } | |
| 62 | + finally | |
| 63 | + { | |
| 64 | + m_disposed = true; // Prevent duplicate dispose. | |
| 65 | + base.Dispose(disposing); // Call base class Dispose(). | |
| 66 | + } | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + #endregion | |
| 71 | + } | |
| 72 | +} | |
| @@ -0,0 +1,54 @@ | ||
| 1 | +//****************************************************************************************************** | |
| 2 | +// DataContextOperations.cs - Gbtc | |
| 3 | +// | |
| 4 | +// Copyright © 2017, Grid Protection Alliance. All Rights Reserved. | |
| 5 | +// | |
| 6 | +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | |
| 7 | +// the NOTICE file distributed with this work for additional information regarding copyright ownership. | |
| 8 | +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | |
| 9 | +// file except in compliance with the License. You may obtain a copy of the License at: | |
| 10 | +// | |
| 11 | +// http://opensource.org/licenses/MIT | |
| 12 | +// | |
| 13 | +// Unless agreed to in writing, the subject software distributed under the License is distributed on an | |
| 14 | +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | |
| 15 | +// License for the specific language governing permissions and limitations. | |
| 16 | +// | |
| 17 | +// Code Modification History: | |
| 18 | +// ---------------------------------------------------------------------------------------------------- | |
| 19 | +// 04/26/2017 - J. Ritchie Carroll | |
| 20 | +// Generated original version of source code. | |
| 21 | +// | |
| 22 | +//****************************************************************************************************** | |
| 23 | + | |
| 24 | +using System; | |
| 25 | +using GSF.Web.Model; | |
| 26 | +using Microsoft.AspNet.SignalR.Hubs; | |
| 27 | + | |
| 28 | +namespace GSF.Web.Hubs | |
| 29 | +{ | |
| 30 | + /// <summary> | |
| 31 | + /// Represents hub operations for using <see cref="DataContextHubClient"/> instances. | |
| 32 | + /// </summary> | |
| 33 | + public class DataContextOperations : HubClientOperationsBase<DataContextHubClient> | |
| 34 | + { | |
| 35 | + private readonly string m_settingsCategory; | |
| 36 | + | |
| 37 | + /// <summary> | |
| 38 | + /// Creates a new <see cref="DataContextOperations"/> instance. | |
| 39 | + /// </summary> | |
| 40 | + /// <param name="hub">Parent hub.</param> | |
| 41 | + /// <param name="settingsCategory">Setting category that contains the connection settings.</param> | |
| 42 | + /// <param name="logStatusMessageFunction">Delegate to use to log status messages, if any.</param> | |
| 43 | + /// <param name="logExceptionFunction">Delegate to use to log exceptions, if any.</param> | |
| 44 | + public DataContextOperations(IHub hub, string settingsCategory, Action<string, UpdateType> logStatusMessageFunction = null, Action<Exception> logExceptionFunction = null) : base(hub, logStatusMessageFunction, logExceptionFunction) | |
| 45 | + { | |
| 46 | + m_settingsCategory = settingsCategory; | |
| 47 | + } | |
| 48 | + | |
| 49 | + /// <summary> | |
| 50 | + /// Gets <see cref="Model.DataContext"/> instance for the current SignalR hub session, creating it if needed. | |
| 51 | + /// </summary> | |
| 52 | + public DataContext DataContext => HubClient.GetDataContext(m_settingsCategory); | |
| 53 | + } | |
| 54 | +} | |
| @@ -40,7 +40,7 @@ namespace GSF.Web.Hubs · 39 unmodified lines | ||
| 40 | 40 | #region [ Members ] |
| 41 | 41 | |
| 42 | 42 | // Fields |
| 43 | − private readonly DataContext m_dataContext; | |
| 43 | + private readonly DataContextOperations m_dataContextOperations; | |
| 44 | 44 | private readonly Action<string, UpdateType> m_logStatusMessageFunction; |
| 45 | 45 | private readonly Action<Exception> m_logExceptionFunction; |
| 46 | 46 | private dynamic m_clientScript; |
| @@ -72,12 +72,12 @@ namespace GSF.Web.Hubs · 25 unmodified lines | ||
| 72 | 72 | /// <summary> |
| 73 | 73 | /// Creates a new <see cref="RecordOperationsHub{T}"/> with the specified <see cref="DataContext"/> and logging functions. |
| 74 | 74 | /// </summary> |
| 75 | − /// <param name="dataContext">Data context to use for this hub; set to <c>null</c> for default data context.</param> | |
| 75 | + /// <param name="settingsCategory">Setting category that contains the connection settings.</param> | |
| 76 | 76 | /// <param name="logStatusMessageFunction">Delegate to use to log status messages, if any.</param> |
| 77 | 77 | /// <param name="logExceptionFunction">Delegate to use to log exceptions, if any.</param> |
| 78 | − protected RecordOperationsHub(DataContext dataContext, Action<string, UpdateType> logStatusMessageFunction, Action<Exception> logExceptionFunction) | |
| 78 | + protected RecordOperationsHub(string settingsCategory, Action<string, UpdateType> logStatusMessageFunction, Action<Exception> logExceptionFunction) | |
| 79 | 79 | { |
| 80 | − m_dataContext = dataContext ?? new DataContext(exceptionHandler: ex => LogException(ex)); | |
| 80 | + m_dataContextOperations = new DataContextOperations(this, settingsCategory, logStatusMessageFunction, logExceptionFunction); | |
| 81 | 81 | m_logStatusMessageFunction = logStatusMessageFunction; |
| 82 | 82 | m_logExceptionFunction = logExceptionFunction; |
| 83 | 83 | } |
| @@ -100,9 +100,9 @@ namespace GSF.Web.Hubs · 16 unmodified lines | ||
| 100 | 100 | public dynamic ClientScript => m_clientScript ?? (m_clientScript = Clients?.Client(ConnectionID)); |
| 101 | 101 | |
| 102 | 102 | /// <summary> |
| 103 | − /// Gets <see cref="Model.DataContext"/> created for this <see cref="RecordOperationsHub{T}"/> instance. | |
| 103 | + /// Gets <see cref="Model.DataContext"/> instance for the current SignalR hub session, creating it if needed. | |
| 104 | 104 | /// </summary> |
| 105 | − public DataContext DataContext => m_dataContext; | |
| 105 | + public DataContext DataContext => m_dataContextOperations.DataContext; | |
| 106 | 106 | |
| 107 | 107 | /// <summary> |
| 108 | 108 | /// Gets active connection ID from current hub context or assigns one to use. |
| @@ -137,7 +137,9 @@ namespace GSF.Web.Hubs · 28 unmodified lines | ||
| 137 | 137 | try |
| 138 | 138 | { |
| 139 | 139 | if (disposing) |
| 140 | − m_dataContext?.Dispose(); | |
| 140 | + { | |
| 141 | + m_clientScript = null; | |
| 142 | + } | |
| 141 | 143 | } |
| 142 | 144 | finally |
| 143 | 145 | { |
| @@ -171,7 +173,10 @@ namespace GSF.Web.Hubs · 27 unmodified lines | ||
| 171 | 173 | public override Task OnDisconnected(bool stopCalled) |
| 172 | 174 | { |
| 173 | 175 | if (stopCalled) |
| 176 | + { | |
| 177 | + m_dataContextOperations?.EndSession(); | |
| 174 | 178 | s_connectCount--; |
| 179 | + } | |
| 175 | 180 | |
| 176 | 181 | return base.OnDisconnected(stopCalled); |
| 177 | 182 | } |
| @@ -65,11 +65,11 @@ namespace GSF.Web.Security · 64 unmodified lines | ||
| 65 | 65 | /// <summary> |
| 66 | 66 | /// Creates a new <see cref="SecurityHub"/> with the specified <see cref="DataContext"/> and logging functions. |
| 67 | 67 | /// </summary> |
| 68 | − /// <param name="dataContext">Data context to use for this hub; set to <c>null</c> for default security provider context.</param> | |
| 68 | + /// <param name="settingsCategory">Setting category that contains the connection settings. Defaults to "securityProvider".</param> | |
| 69 | 69 | /// <param name="logStatusMessageFunction">Delegate to use to log status messages, if any.</param> |
| 70 | 70 | /// <param name="logExceptionFunction">Delegate to use to log exceptions, if any.</param> |
| 71 | − public SecurityHub(DataContext dataContext, Action<string, UpdateType> logStatusMessageFunction, Action<Exception> logExceptionFunction) : | |
| 72 | − this(dataContext, logStatusMessageFunction, logExceptionFunction, true) | |
| 71 | + public SecurityHub(string settingsCategory, Action<string, UpdateType> logStatusMessageFunction, Action<Exception> logExceptionFunction) : | |
| 72 | + this(settingsCategory, logStatusMessageFunction, logExceptionFunction, true) | |
| 73 | 73 | { |
| 74 | 74 | // Capture initial defaults |
| 75 | 75 | if ((object)logStatusMessageFunction != null && (object)s_logStatusMessageFunction == null) |
| @@ -79,8 +79,8 @@ namespace GSF.Web.Security · 3 unmodified lines | ||
| 79 | 79 | s_logExceptionFunction = logExceptionFunction; |
| 80 | 80 | } |
| 81 | 81 | |
| 82 | − private SecurityHub(DataContext dataContext, Action<string, UpdateType> logStatusMessageFunction, Action<Exception> logExceptionFunction, bool overload) : | |
| 83 | − base(dataContext ?? new DataContext("securityProvider"), logStatusMessageFunction ?? s_logStatusMessageFunction, logExceptionFunction ?? s_logExceptionFunction) | |
| 82 | + private SecurityHub(string settingsCategory, Action<string, UpdateType> logStatusMessageFunction, Action<Exception> logExceptionFunction, bool overload) : | |
| 83 | + base(settingsCategory ?? "securityProvider", logStatusMessageFunction ?? s_logStatusMessageFunction, logExceptionFunction ?? s_logExceptionFunction) | |
| 84 | 84 | { |
| 85 | 85 | } |
| 86 | 86 | |