X Tutup
using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Npgsql.Util; using NpgsqlTypes; namespace Npgsql { /// /// Represents a collection of parameters relevant to a NpgsqlCommand /// as well as their respective mappings to columns in a DataSet. /// This class cannot be inherited. /// public sealed class NpgsqlParameterCollection : DbParameterCollection, IList { readonly List _internalList = new List(5); // Dictionary lookups for GetValue to improve performance Dictionary? _lookup; Dictionary? _lookupIgnoreCase; /// /// Initializes a new instance of the NpgsqlParameterCollection class. /// internal NpgsqlParameterCollection() => InvalidateHashLookups(); /// /// Invalidate the hash lookup tables. This should be done any time a change /// may throw the lookups out of sync with the list. /// internal void InvalidateHashLookups() { _lookup = null; _lookupIgnoreCase = null; } #region NpgsqlParameterCollection Member /// /// Gets the NpgsqlParameter with the specified name. /// /// The name of the NpgsqlParameter to retrieve. /// The NpgsqlParameter with the specified name, or a null reference if the parameter is not found. [PublicAPI] public new NpgsqlParameter this[string parameterName] { get { if (parameterName is null) throw new ArgumentNullException(nameof(parameterName)); var index = IndexOf(parameterName); if (index == -1) throw new ArgumentException("Parameter not found"); return _internalList[index]; } set { if (parameterName is null) throw new ArgumentNullException(nameof(parameterName)); if (value is null) throw new ArgumentNullException(nameof(value)); var index = IndexOf(parameterName); if (index == -1) throw new ArgumentException("Parameter not found"); var oldValue = _internalList[index]; if (oldValue.ParameterName != value.ParameterName) InvalidateHashLookups(); _internalList[index] = value; } } /// /// Gets the NpgsqlParameter at the specified index. /// /// The zero-based index of the NpgsqlParameter to retrieve. /// The NpgsqlParameter at the specified index. [PublicAPI] public new NpgsqlParameter this[int index] { get => _internalList[index]; set { if (value is null) throw new ArgumentNullException(nameof(value)); if (value.Collection != null) throw new InvalidOperationException("The parameter already belongs to a collection"); var oldValue = _internalList[index]; if (oldValue == value) return; if (value.ParameterName != oldValue.ParameterName) InvalidateHashLookups(); _internalList[index] = value; value.Collection = this; oldValue.Collection = null; } } /// /// Adds the specified NpgsqlParameter object to the NpgsqlParameterCollection. /// /// The NpgsqlParameter to add to the collection. /// The index of the new NpgsqlParameter object. public NpgsqlParameter Add(NpgsqlParameter value) { if (value is null) throw new ArgumentNullException(nameof(value)); if (value.Collection != null) throw new InvalidOperationException("The parameter already belongs to a collection"); _internalList.Add(value); value.Collection = this; InvalidateHashLookups(); return value; } /// void ICollection.Add(NpgsqlParameter item) => Add(item); #nullable disable /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name and value. /// /// The name of the NpgsqlParameter. /// The Value of the NpgsqlParameter to add to the collection. /// The paramater that was added. [PublicAPI] public NpgsqlParameter AddWithValue(string parameterName, object value) => Add(new NpgsqlParameter(parameterName, value)); /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name, data type and value. /// /// The name of the NpgsqlParameter. /// One of the NpgsqlDbType values. /// The Value of the NpgsqlParameter to add to the collection. /// The paramater that was added. [PublicAPI] public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, object value) => Add(new NpgsqlParameter(parameterName, parameterType) { Value = value }); /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name and value. /// /// The name of the NpgsqlParameter. /// The Value of the NpgsqlParameter to add to the collection. /// One of the NpgsqlDbType values. /// The length of the column. /// The paramater that was added. [PublicAPI] public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, object value) => Add(new NpgsqlParameter(parameterName, parameterType, size) { Value = value }); /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified parameter name and value. /// /// The name of the NpgsqlParameter. /// The Value of the NpgsqlParameter to add to the collection. /// One of the NpgsqlDbType values. /// The length of the column. /// The name of the source column. /// The paramater that was added. [PublicAPI] public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn, object value) => Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn) { Value = value }); /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified value. /// /// The Value of the NpgsqlParameter to add to the collection. /// The paramater that was added. [PublicAPI] public NpgsqlParameter AddWithValue(object value) => Add(new NpgsqlParameter() { Value = value }); /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the specified data type and value. /// /// One of the NpgsqlDbType values. /// The Value of the NpgsqlParameter to add to the collection. /// The paramater that was added. [PublicAPI] public NpgsqlParameter AddWithValue(NpgsqlDbType parameterType, object value) => Add(new NpgsqlParameter { NpgsqlDbType = parameterType, Value = value }); #nullable restore /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection given the parameter name and the data type. /// /// The name of the parameter. /// One of the DbType values. /// The index of the new NpgsqlParameter object. [PublicAPI] public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType) => Add(new NpgsqlParameter(parameterName, parameterType)); /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection with the parameter name, the data type, and the column length. /// /// The name of the parameter. /// One of the DbType values. /// The length of the column. /// The index of the new NpgsqlParameter object. [PublicAPI] public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size) => Add(new NpgsqlParameter(parameterName, parameterType, size)); /// /// Adds a NpgsqlParameter to the NpgsqlParameterCollection with the parameter name, the data type, the column length, and the source column name. /// /// The name of the parameter. /// One of the DbType values. /// The length of the column. /// The name of the source column. /// The index of the new NpgsqlParameter object. [PublicAPI] public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn) => Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn)); #endregion #region IDataParameterCollection Member /// [PublicAPI] // ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember public override void RemoveAt(string parameterName) => RemoveAt(IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName)))); /// public override bool Contains(string parameterName) => IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName))) != -1; /// public override int IndexOf(string parameterName) { if (parameterName is null) return -1; if (parameterName.Length > 0 && (parameterName[0] == ':' || parameterName[0] == '@')) parameterName = parameterName.Remove(0, 1); // Using a dictionary is much faster for 5 or more items if (_internalList.Count >= 5) { if (_lookup == null) { _lookup = new Dictionary(); for (var i = 0 ; i < _internalList.Count ; i++) { var item = _internalList[i]; // Store only the first of each distinct value if (!_lookup.ContainsKey(item.TrimmedName)) _lookup.Add(item.TrimmedName, i); } } // Try to access the case sensitive parameter name first if (_lookup.TryGetValue(parameterName, out var retIndex)) return retIndex; // Case sensitive lookup failed, generate a case insensitive lookup if (_lookupIgnoreCase == null) { _lookupIgnoreCase = new Dictionary(PGUtil.InvariantCaseIgnoringStringComparer); for (var i = 0 ; i < _internalList.Count ; i++) { var item = _internalList[i]; // Store only the first of each distinct value if (!_lookupIgnoreCase.ContainsKey(item.TrimmedName)) _lookupIgnoreCase.Add(item.TrimmedName, i); } } // Then try to access the case insensitive parameter name if (_lookupIgnoreCase.TryGetValue(parameterName, out retIndex)) return retIndex; return -1; } // First try a case-sensitive match for (var i = 0; i < _internalList.Count; i++) if (parameterName == _internalList[i].TrimmedName) return i; // If not fond, try a case-insensitive match for (var i = 0; i < _internalList.Count; i++) if (string.Equals(parameterName, _internalList[i].TrimmedName, StringComparison.OrdinalIgnoreCase)) return i; return -1; } #endregion #region IList Member /// public override bool IsReadOnly => false; /// /// Removes the specified NpgsqlParameter from the collection using a specific index. /// /// The zero-based index of the parameter. public override void RemoveAt(int index) { if (_internalList.Count - 1 < index) throw new ArgumentOutOfRangeException(nameof(index)); Remove(_internalList[index]); } /// public override void Insert(int index, object value) => Insert(index, Cast(value)); /// /// Removes the specified NpgsqlParameter from the collection. /// /// The name of the NpgsqlParameter to remove from the collection. [PublicAPI] public void Remove(string parameterName) { if (parameterName is null) throw new ArgumentNullException(nameof(parameterName)); var index = IndexOf(parameterName); if (index < 0) throw new InvalidOperationException("No parameter with the specified name exists in the collection"); RemoveAt(index); } /// /// Removes the specified NpgsqlParameter from the collection. /// /// The NpgsqlParameter to remove from the collection. public override void Remove(object value) => Remove(Cast(value)); /// public override bool Contains(object value) => value is NpgsqlParameter param && _internalList.Contains(param); /// /// Gets a value indicating whether a NpgsqlParameter with the specified parameter name exists in the collection. /// /// The name of the NpgsqlParameter object to find. /// A reference to the requested parameter is returned in this out param if it is found in the list. This value is null if the parameter is not found. /// true if the collection contains the parameter and param will contain the parameter; otherwise, false. public bool TryGetValue(string parameterName, [NotNullWhen(true)] out NpgsqlParameter? parameter) { if (parameterName is null) throw new ArgumentNullException(nameof(parameterName)); var index = IndexOf(parameterName); if (index != -1) { parameter = _internalList[index]; return true; } parameter = null; return false; } /// /// Removes all items from the collection. /// public override void Clear() { // clean up parameters so they can be added to another command if required. foreach (var toRemove in _internalList) toRemove.Collection = null; _internalList.Clear(); InvalidateHashLookups(); } /// public override int IndexOf(object value) => IndexOf(Cast(value)); /// public override int Add(object value) { Add(Cast(value)); return Count - 1; } /// public override bool IsFixedSize => false; #endregion #region ICollection Member /// public override bool IsSynchronized => (_internalList as ICollection).IsSynchronized; /// /// Gets the number of NpgsqlParameter objects in the collection. /// /// The number of NpgsqlParameter objects in the collection. public override int Count => _internalList.Count; /// public override void CopyTo(Array array, int index) => ((ICollection)_internalList).CopyTo(array, index); /// bool ICollection.IsReadOnly => false; /// public override object SyncRoot => ((ICollection)_internalList).SyncRoot; #endregion #region IEnumerable Member IEnumerator IEnumerable.GetEnumerator() => _internalList.GetEnumerator(); /// public override IEnumerator GetEnumerator() => _internalList.GetEnumerator(); #endregion /// public override void AddRange(Array values) { if (values is null) throw new ArgumentNullException(nameof(values)); foreach (object? parameter in values) Add(Cast(parameter) ?? throw new ArgumentException("Collection contains a null value.", nameof(values))); } /// protected override DbParameter GetParameter(string parameterName) => this[parameterName]; /// protected override DbParameter GetParameter(int index) => this[index]; /// protected override void SetParameter(string parameterName, DbParameter value) => this[parameterName] = Cast(value); /// protected override void SetParameter(int index, DbParameter value) => this[index] = Cast(value); /// /// Report the offset within the collection of the given parameter. /// /// Parameter to find. /// Index of the parameter, or -1 if the parameter is not present. [PublicAPI] public int IndexOf(NpgsqlParameter item) => _internalList.IndexOf(item); /// /// Insert the specified parameter into the collection. /// /// Index of the existing parameter before which to insert the new one. /// Parameter to insert. [PublicAPI] public void Insert(int index, NpgsqlParameter item) { if (item is null) throw new ArgumentNullException(nameof(item)); if (item.Collection != null) throw new Exception("The parameter already belongs to a collection"); _internalList.Insert(index, item); item.Collection = this; InvalidateHashLookups(); } /// /// Report whether the specified parameter is present in the collection. /// /// Parameter to find. /// True if the parameter was found, otherwise false. [PublicAPI] public bool Contains(NpgsqlParameter item) => _internalList.Contains(item); /// /// Remove the specified parameter from the collection. /// /// Parameter to remove. /// True if the parameter was found and removed, otherwise false. [PublicAPI] public bool Remove(NpgsqlParameter item) { if (item == null) throw new ArgumentNullException(nameof(item)); if (item.Collection != this) throw new InvalidOperationException("The item does not belong to this collection"); if (_internalList.Remove(item)) { item.Collection = null; InvalidateHashLookups(); return true; } return false; } /// /// Convert collection to a System.Array. /// /// Destination array. /// Starting index in destination array. [PublicAPI] public void CopyTo(NpgsqlParameter[] array, int arrayIndex) => _internalList.CopyTo(array, arrayIndex); /// /// Convert collection to a System.Array. /// /// NpgsqlParameter[] [PublicAPI] public NpgsqlParameter[] ToArray() => _internalList.ToArray(); internal void CloneTo(NpgsqlParameterCollection other) { other._internalList.Clear(); foreach (var param in _internalList) { var newParam = param.Clone(); newParam.Collection = this; other._internalList.Add(newParam); } other._lookup = _lookup; other._lookupIgnoreCase = _lookupIgnoreCase; } internal bool HasOutputParameters { get { foreach (var p in _internalList) if (p.IsOutputDirection) return true; return false; } } static NpgsqlParameter Cast(object? value) { try { return (NpgsqlParameter)value!; } catch (Exception) { throw new InvalidCastException($"The value \"{value}\" is not of type \"{nameof(NpgsqlParameter)}\" and cannot be used in this parameter collection."); } } } }
X Tutup