808 lines
30 KiB
C#
808 lines
30 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Npgsql.TypeMapping;
|
|
using NpgsqlTypes;
|
|
|
|
namespace Npgsql;
|
|
|
|
/// <summary>
|
|
/// Represents a collection of parameters relevant to a <see cref="NpgsqlCommand"/> as well as their respective mappings to columns in
|
|
/// a <see cref="DataSet"/>.
|
|
/// </summary>
|
|
public sealed class NpgsqlParameterCollection : DbParameterCollection, IList<NpgsqlParameter>
|
|
{
|
|
internal const int LookupThreshold = 5;
|
|
|
|
internal List<NpgsqlParameter> InternalList { get; } = new(5);
|
|
#if DEBUG
|
|
internal static bool TwoPassCompatMode;
|
|
#else
|
|
internal static readonly bool TwoPassCompatMode;
|
|
#endif
|
|
|
|
static NpgsqlParameterCollection()
|
|
=> TwoPassCompatMode = AppContext.TryGetSwitch("Npgsql.EnableLegacyCaseInsensitiveDbParameters", out var enabled)
|
|
&& enabled;
|
|
|
|
// Dictionary lookups for GetValue to improve performance. _caseSensitiveLookup is only ever used in legacy two-pass mode.
|
|
Dictionary<string, int>? _caseInsensitiveLookup;
|
|
Dictionary<string, int>? _caseSensitiveLookup;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the NpgsqlParameterCollection class.
|
|
/// </summary>
|
|
internal NpgsqlParameterCollection()
|
|
{
|
|
}
|
|
|
|
bool LookupEnabled => InternalList.Count >= LookupThreshold;
|
|
|
|
void LookupClear()
|
|
{
|
|
_caseInsensitiveLookup?.Clear();
|
|
_caseSensitiveLookup?.Clear();
|
|
}
|
|
|
|
void LookupAdd(string name, int index)
|
|
{
|
|
if (_caseInsensitiveLookup is null)
|
|
return;
|
|
|
|
if (TwoPassCompatMode && !_caseSensitiveLookup!.ContainsKey(name))
|
|
_caseSensitiveLookup[name] = index;
|
|
|
|
if (!_caseInsensitiveLookup.ContainsKey(name))
|
|
_caseInsensitiveLookup[name] = index;
|
|
}
|
|
|
|
void LookupInsert(string name, int index)
|
|
{
|
|
if (_caseInsensitiveLookup is null)
|
|
return;
|
|
|
|
if (TwoPassCompatMode &&
|
|
(!_caseSensitiveLookup!.TryGetValue(name, out var indexCs) || index < indexCs))
|
|
{
|
|
for (var i = index + 1; i < InternalList.Count; i++)
|
|
{
|
|
var parameterName = InternalList[i].TrimmedName;
|
|
if (_caseSensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI + 1 == i)
|
|
_caseSensitiveLookup[parameterName] = i;
|
|
}
|
|
|
|
_caseSensitiveLookup[name] = index;
|
|
}
|
|
|
|
if (!_caseInsensitiveLookup.TryGetValue(name, out var indexCi) || index < indexCi)
|
|
{
|
|
for (var i = index + 1; i < InternalList.Count; i++)
|
|
{
|
|
var parameterName = InternalList[i].TrimmedName;
|
|
if (_caseInsensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI + 1 == i)
|
|
_caseInsensitiveLookup[parameterName] = i;
|
|
}
|
|
|
|
_caseInsensitiveLookup[name] = index;
|
|
}
|
|
}
|
|
|
|
void LookupRemove(string name, int index)
|
|
{
|
|
if (_caseInsensitiveLookup is null)
|
|
return;
|
|
|
|
if (TwoPassCompatMode && _caseSensitiveLookup!.Remove(name))
|
|
{
|
|
for (var i = index; i < InternalList.Count; i++)
|
|
{
|
|
var parameterName = InternalList[i].TrimmedName;
|
|
if (_caseSensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI - 1 == i)
|
|
_caseSensitiveLookup[parameterName] = i;
|
|
}
|
|
}
|
|
|
|
if (_caseInsensitiveLookup.Remove(name))
|
|
{
|
|
for (var i = index; i < InternalList.Count; i++)
|
|
{
|
|
var parameterName = InternalList[i].TrimmedName;
|
|
if (_caseInsensitiveLookup.TryGetValue(parameterName, out var currentI) && currentI - 1 == i)
|
|
_caseInsensitiveLookup[parameterName] = i;
|
|
}
|
|
|
|
// Fix-up the case-insensitive lookup to point to the next match, if any.
|
|
for (var i = 0; i < InternalList.Count; i++)
|
|
{
|
|
var value = InternalList[i];
|
|
if (string.Equals(name, value.TrimmedName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_caseInsensitiveLookup[value.TrimmedName] = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void LookupChangeName(NpgsqlParameter parameter, string oldName, string oldTrimmedName, int index)
|
|
{
|
|
if (string.Equals(oldTrimmedName, parameter.TrimmedName, StringComparison.OrdinalIgnoreCase))
|
|
return;
|
|
|
|
if (oldName.Length != 0)
|
|
LookupRemove(oldTrimmedName, index);
|
|
if (!parameter.IsPositional)
|
|
LookupAdd(parameter.TrimmedName, index);
|
|
}
|
|
|
|
internal void ChangeParameterName(NpgsqlParameter parameter, string? value)
|
|
{
|
|
var oldName = parameter.ParameterName;
|
|
var oldTrimmedName = parameter.TrimmedName;
|
|
parameter.ChangeParameterName(value);
|
|
|
|
if (_caseInsensitiveLookup is null || _caseInsensitiveLookup.Count == 0)
|
|
return;
|
|
|
|
var index = IndexOf(parameter);
|
|
if (index == -1) // This would be weird.
|
|
return;
|
|
|
|
LookupChangeName(parameter, oldName, oldTrimmedName, index);
|
|
}
|
|
|
|
#region NpgsqlParameterCollection Member
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="NpgsqlParameter"/> with the specified name.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the <see cref="NpgsqlParameter"/> to retrieve.</param>
|
|
/// <value>
|
|
/// The <see cref="NpgsqlParameter"/> with the specified name, or a <see langword="null"/> reference if the parameter is not found.
|
|
/// </value>
|
|
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");
|
|
|
|
if (!string.Equals(parameterName, value.TrimmedName, StringComparison.OrdinalIgnoreCase))
|
|
throw new ArgumentException(
|
|
"Parameter name must be a case-insensitive match with the property 'ParameterName' on the given NpgsqlParameter",
|
|
nameof(parameterName));
|
|
|
|
var oldValue = InternalList[index];
|
|
LookupChangeName(value, oldValue.ParameterName, oldValue.TrimmedName, index);
|
|
|
|
InternalList[index] = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="NpgsqlParameter"/> at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The zero-based index of the <see cref="NpgsqlParameter"/> to retrieve.</param>
|
|
/// <value>The <see cref="NpgsqlParameter"/> at the specified index.</value>
|
|
public new NpgsqlParameter this[int index]
|
|
{
|
|
get => InternalList[index];
|
|
set
|
|
{
|
|
if (value is null)
|
|
throw new ArgumentNullException(nameof(value));
|
|
if (value.Collection is not null)
|
|
throw new InvalidOperationException("The parameter already belongs to a collection");
|
|
|
|
var oldValue = InternalList[index];
|
|
|
|
if (ReferenceEquals(oldValue, value))
|
|
return;
|
|
|
|
LookupChangeName(value, oldValue.ParameterName, oldValue.TrimmedName, index);
|
|
|
|
InternalList[index] = value;
|
|
value.Collection = this;
|
|
oldValue.Collection = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified <see cref="NpgsqlParameter"/> object to the <see cref="NpgsqlParameterCollection"/>.
|
|
/// </summary>
|
|
/// <param name="value">The <see cref="NpgsqlParameter"/> to add to the collection.</param>
|
|
/// <returns>The index of the new <see cref="NpgsqlParameter"/> object.</returns>
|
|
public NpgsqlParameter Add(NpgsqlParameter value)
|
|
{
|
|
if (value is null)
|
|
throw new ArgumentNullException(nameof(value));
|
|
if (value.Collection is not null)
|
|
throw new InvalidOperationException("The parameter already belongs to a collection");
|
|
|
|
InternalList.Add(value);
|
|
value.Collection = this;
|
|
if (!value.IsPositional)
|
|
LookupAdd(value.TrimmedName, InternalList.Count - 1);
|
|
return value;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
void ICollection<NpgsqlParameter>.Add(NpgsqlParameter item)
|
|
=> Add(item);
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> given the specified parameter name and
|
|
/// value.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the <see cref="NpgsqlParameter"/>.</param>
|
|
/// <param name="value">The value of the <see cref="NpgsqlParameter"/> to add to the collection.</param>
|
|
/// <returns>The parameter that was added.</returns>
|
|
public NpgsqlParameter AddWithValue(string parameterName, object value)
|
|
=> Add(new NpgsqlParameter(parameterName, value));
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> given the specified parameter name,
|
|
/// data type and value.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the <see cref="NpgsqlParameter"/>.</param>
|
|
/// <param name="parameterType">One of the NpgsqlDbType values.</param>
|
|
/// <param name="value">The value of the <see cref="NpgsqlParameter"/> to add to the collection.</param>
|
|
/// <returns>The parameter that was added.</returns>
|
|
public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, object value)
|
|
=> Add(new NpgsqlParameter(parameterName, parameterType) { Value = value });
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> given the specified parameter name and
|
|
/// value.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the <see cref="NpgsqlParameter"/>.</param>
|
|
/// <param name="value">The value of the <see cref="NpgsqlParameter"/> to add to the collection.</param>
|
|
/// <param name="parameterType">One of the <see cref="NpgsqlDbType"/> values.</param>
|
|
/// <param name="size">The length of the column.</param>
|
|
/// <returns>The parameter that was added.</returns>
|
|
public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, object value)
|
|
=> Add(new NpgsqlParameter(parameterName, parameterType, size) { Value = value });
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> given the specified parameter name and
|
|
/// value.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the <see cref="NpgsqlParameter"/>.</param>
|
|
/// <param name="value">The value of the <see cref="NpgsqlParameter"/> to add to the collection.</param>
|
|
/// <param name="parameterType">One of the <see cref="NpgsqlDbType"/> values.</param>
|
|
/// <param name="size">The length of the column.</param>
|
|
/// <param name="sourceColumn">The name of the source column.</param>
|
|
/// <returns>The parameter that was added.</returns>
|
|
public NpgsqlParameter AddWithValue(string parameterName, NpgsqlDbType parameterType, int size, string? sourceColumn, object value)
|
|
=> Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn) { Value = value });
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> given the specified value.
|
|
/// </summary>
|
|
/// <param name="value">The value of the <see cref="NpgsqlParameter"/> to add to the collection.</param>
|
|
/// <returns>The parameter that was added.</returns>
|
|
public NpgsqlParameter AddWithValue(object value)
|
|
=> Add(new NpgsqlParameter { Value = value });
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> given the specified data type and value.
|
|
/// </summary>
|
|
/// <param name="parameterType">One of the <see cref="NpgsqlDbType"/> values.</param>
|
|
/// <param name="value">The value of the <see cref="NpgsqlParameter"/> to add to the collection.</param>
|
|
/// <returns>The parameter that was added.</returns>
|
|
public NpgsqlParameter AddWithValue(NpgsqlDbType parameterType, object value)
|
|
=> Add(new NpgsqlParameter { NpgsqlDbType = parameterType, Value = value });
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> given the parameter name and the data type.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the parameter.</param>
|
|
/// <param name="parameterType">One of the <see cref="DbType"/> values.</param>
|
|
/// <returns>The index of the new <see cref="NpgsqlParameter"/> object.</returns>
|
|
public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType)
|
|
=> Add(new NpgsqlParameter(parameterName, parameterType));
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> with the parameter name, the data type,
|
|
/// and the column length.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the parameter.</param>
|
|
/// <param name="parameterType">One of the <see cref="DbType"/> values.</param>
|
|
/// <param name="size">The length of the column.</param>
|
|
/// <returns>The index of the new <see cref="NpgsqlParameter"/> object.</returns>
|
|
public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size)
|
|
=> Add(new NpgsqlParameter(parameterName, parameterType, size));
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="NpgsqlParameter"/> to the <see cref="NpgsqlParameterCollection"/> with the parameter name, the data type, the
|
|
/// column length, and the source column name.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the parameter.</param>
|
|
/// <param name="parameterType">One of the <see cref="DbType"/> values.</param>
|
|
/// <param name="size">The length of the column.</param>
|
|
/// <param name="sourceColumn">The name of the source column.</param>
|
|
/// <returns>The index of the new <see cref="NpgsqlParameter"/> object.</returns>
|
|
public NpgsqlParameter Add(string parameterName, NpgsqlDbType parameterType, int size, string sourceColumn)
|
|
=> Add(new NpgsqlParameter(parameterName, parameterType, size, sourceColumn));
|
|
|
|
#endregion
|
|
|
|
#region IDataParameterCollection Member
|
|
|
|
/// <inheritdoc />
|
|
// ReSharper disable once ImplicitNotNullOverridesUnknownExternalMember
|
|
public override void RemoveAt(string parameterName)
|
|
=> RemoveAt(IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName))));
|
|
|
|
/// <inheritdoc />
|
|
public override bool Contains(string parameterName)
|
|
=> IndexOf(parameterName ?? throw new ArgumentNullException(nameof(parameterName))) != -1;
|
|
|
|
/// <inheritdoc />
|
|
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 always faster after around 10 items when matched against reference equality.
|
|
// For string equality this is the case after ~3 items so we take a decent compromise going with 5.
|
|
if (LookupEnabled && parameterName.Length != 0)
|
|
{
|
|
if (_caseInsensitiveLookup is null)
|
|
BuildLookup();
|
|
|
|
if (TwoPassCompatMode && _caseSensitiveLookup!.TryGetValue(parameterName, out var indexCs))
|
|
return indexCs;
|
|
|
|
if (_caseInsensitiveLookup!.TryGetValue(parameterName, out var indexCi))
|
|
return indexCi;
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Start with case-sensitive search in two pass mode.
|
|
if (TwoPassCompatMode)
|
|
{
|
|
for (var i = 0; i < InternalList.Count; i++)
|
|
{
|
|
var name = InternalList[i].TrimmedName;
|
|
if (string.Equals(parameterName, InternalList[i].TrimmedName))
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// Then do case-insensitive search.
|
|
for (var i = 0; i < InternalList.Count; i++)
|
|
{
|
|
var name = InternalList[i].TrimmedName;
|
|
if (ReferenceEquals(parameterName, name) || string.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
|
|
void BuildLookup()
|
|
{
|
|
if (TwoPassCompatMode)
|
|
_caseSensitiveLookup = new Dictionary<string, int>(InternalList.Count, StringComparer.Ordinal);
|
|
|
|
_caseInsensitiveLookup = new Dictionary<string, int>(InternalList.Count, StringComparer.OrdinalIgnoreCase);
|
|
|
|
for (var i = 0; i < InternalList.Count; i++)
|
|
{
|
|
var item = InternalList[i];
|
|
if (!item.IsPositional)
|
|
LookupAdd(item.TrimmedName, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IList Member
|
|
|
|
/// <inheritdoc />
|
|
public override bool IsReadOnly => false;
|
|
|
|
/// <summary>
|
|
/// Removes the specified <see cref="NpgsqlParameter"/> from the collection using a specific index.
|
|
/// </summary>
|
|
/// <param name="index">The zero-based index of the parameter.</param>
|
|
public override void RemoveAt(int index)
|
|
{
|
|
if (InternalList.Count - 1 < index)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
|
|
Remove(InternalList[index]);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Insert(int index, object value)
|
|
=> Insert(index, Cast(value));
|
|
|
|
/// <summary>
|
|
/// Removes the specified <see cref="NpgsqlParameter"/> from the collection.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the <see cref="NpgsqlParameter"/> to remove from the collection.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the specified <see cref="NpgsqlParameter"/> from the collection.
|
|
/// </summary>
|
|
/// <param name="value">The <see cref="NpgsqlParameter"/> to remove from the collection.</param>
|
|
public override void Remove(object value)
|
|
=> Remove(Cast(value));
|
|
|
|
/// <inheritdoc />
|
|
public override bool Contains(object value)
|
|
=> value is NpgsqlParameter param && InternalList.Contains(param);
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether a <see cref="NpgsqlParameter"/> with the specified parameter name exists in the collection.
|
|
/// </summary>
|
|
/// <param name="parameterName">The name of the <see cref="NpgsqlParameter"/> object to find.</param>
|
|
/// <param name="parameter">
|
|
/// A reference to the requested parameter is returned in this out param if it is found in the list.
|
|
/// This value is <see langword="null"/> if the parameter is not found.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <see langword="true"/> if the collection contains the parameter and param will contain the parameter;
|
|
/// otherwise, <see langword="false"/>.
|
|
/// </returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all items from the collection.
|
|
/// </summary>
|
|
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();
|
|
LookupClear();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int IndexOf(object value)
|
|
=> IndexOf(Cast(value));
|
|
|
|
/// <inheritdoc />
|
|
public override int Add(object value)
|
|
{
|
|
Add(Cast(value));
|
|
return Count - 1;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool IsFixedSize => false;
|
|
|
|
#endregion
|
|
|
|
#region ICollection Member
|
|
|
|
/// <inheritdoc />
|
|
public override bool IsSynchronized => (InternalList as ICollection).IsSynchronized;
|
|
|
|
/// <summary>
|
|
/// Gets the number of <see cref="NpgsqlParameter"/> objects in the collection.
|
|
/// </summary>
|
|
/// <value>The number of <see cref="NpgsqlParameter"/> objects in the collection.</value>
|
|
public override int Count => InternalList.Count;
|
|
|
|
/// <inheritdoc />
|
|
public override void CopyTo(Array array, int index)
|
|
=> ((ICollection)InternalList).CopyTo(array, index);
|
|
|
|
/// <inheritdoc />
|
|
bool ICollection<NpgsqlParameter>.IsReadOnly => false;
|
|
|
|
/// <inheritdoc />
|
|
public override object SyncRoot => ((ICollection)InternalList).SyncRoot;
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable Member
|
|
|
|
IEnumerator<NpgsqlParameter> IEnumerable<NpgsqlParameter>.GetEnumerator()
|
|
=> InternalList.GetEnumerator();
|
|
|
|
/// <inheritdoc />
|
|
public override IEnumerator GetEnumerator() => InternalList.GetEnumerator();
|
|
|
|
#endregion
|
|
|
|
/// <inheritdoc />
|
|
public override void AddRange(Array values)
|
|
{
|
|
if (values is null)
|
|
throw new ArgumentNullException(nameof(values));
|
|
|
|
foreach (var parameter in values)
|
|
Add(Cast(parameter) ?? throw new ArgumentException("Collection contains a null value.", nameof(values)));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override DbParameter GetParameter(string parameterName)
|
|
=> this[parameterName];
|
|
|
|
/// <inheritdoc />
|
|
protected override DbParameter GetParameter(int index)
|
|
=> this[index];
|
|
|
|
/// <inheritdoc />
|
|
protected override void SetParameter(string parameterName, DbParameter value)
|
|
=> this[parameterName] = Cast(value);
|
|
|
|
/// <inheritdoc />
|
|
protected override void SetParameter(int index, DbParameter value)
|
|
=> this[index] = Cast(value);
|
|
|
|
/// <summary>
|
|
/// Report the offset within the collection of the given parameter.
|
|
/// </summary>
|
|
/// <param name="item">Parameter to find.</param>
|
|
/// <returns>Index of the parameter, or -1 if the parameter is not present.</returns>
|
|
public int IndexOf(NpgsqlParameter item)
|
|
=> InternalList.IndexOf(item);
|
|
|
|
/// <summary>
|
|
/// Insert the specified parameter into the collection.
|
|
/// </summary>
|
|
/// <param name="index">Index of the existing parameter before which to insert the new one.</param>
|
|
/// <param name="item">Parameter to insert.</param>
|
|
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;
|
|
if (!item.IsPositional)
|
|
LookupInsert(item.TrimmedName, index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Report whether the specified parameter is present in the collection.
|
|
/// </summary>
|
|
/// <param name="item">Parameter to find.</param>
|
|
/// <returns>True if the parameter was found, otherwise false.</returns>
|
|
public bool Contains(NpgsqlParameter item) => InternalList.Contains(item);
|
|
|
|
/// <summary>
|
|
/// Remove the specified parameter from the collection.
|
|
/// </summary>
|
|
/// <param name="item">Parameter to remove.</param>
|
|
/// <returns>True if the parameter was found and removed, otherwise false.</returns>
|
|
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");
|
|
|
|
var index = IndexOf(item);
|
|
if (index >= 0)
|
|
{
|
|
InternalList.RemoveAt(index);
|
|
if (!LookupEnabled)
|
|
LookupClear();
|
|
if (!item.IsPositional)
|
|
LookupRemove(item.TrimmedName, index);
|
|
item.Collection = null;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert collection to a System.Array.
|
|
/// </summary>
|
|
/// <param name="array">Destination array.</param>
|
|
/// <param name="arrayIndex">Starting index in destination array.</param>
|
|
public void CopyTo(NpgsqlParameter[] array, int arrayIndex)
|
|
=> InternalList.CopyTo(array, arrayIndex);
|
|
|
|
/// <summary>
|
|
/// Convert collection to a System.Array.
|
|
/// </summary>
|
|
/// <returns>NpgsqlParameter[]</returns>
|
|
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);
|
|
}
|
|
|
|
if (LookupEnabled && _caseInsensitiveLookup is not null)
|
|
{
|
|
other._caseInsensitiveLookup = new Dictionary<string, int>(_caseInsensitiveLookup, StringComparer.OrdinalIgnoreCase);
|
|
if (TwoPassCompatMode)
|
|
{
|
|
Debug.Assert(_caseSensitiveLookup is not null);
|
|
other._caseSensitiveLookup = new Dictionary<string, int>(_caseSensitiveLookup, StringComparer.Ordinal);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void ValidateAndBind(ConnectorTypeMapper typeMapper)
|
|
{
|
|
HasOutputParameters = false;
|
|
PlaceholderType = PlaceholderType.NoParameters;
|
|
|
|
for (var i = 0; i < InternalList.Count; i++)
|
|
{
|
|
var p = InternalList[i];
|
|
|
|
CalculatePlaceholderType(p);
|
|
|
|
switch (p.Direction)
|
|
{
|
|
case ParameterDirection.Input:
|
|
break;
|
|
|
|
case ParameterDirection.InputOutput:
|
|
if (PlaceholderType == PlaceholderType.Positional)
|
|
throw new NotSupportedException("Output parameters are not supported in positional mode");
|
|
HasOutputParameters = true;
|
|
break;
|
|
|
|
case ParameterDirection.Output:
|
|
if (PlaceholderType == PlaceholderType.Positional)
|
|
throw new NotSupportedException("Output parameters are not supported in positional mode");
|
|
HasOutputParameters = true;
|
|
continue;
|
|
|
|
case ParameterDirection.ReturnValue:
|
|
// Simply ignored
|
|
continue;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(ParameterDirection),
|
|
$"Unhandled {nameof(ParameterDirection)} value: {p.Direction}");
|
|
}
|
|
|
|
p.Bind(typeMapper);
|
|
p.LengthCache?.Clear();
|
|
p.ValidateAndGetLength();
|
|
}
|
|
}
|
|
|
|
internal void CalculatePlaceholderType(NpgsqlParameter p)
|
|
{
|
|
if (p.IsPositional)
|
|
{
|
|
switch (PlaceholderType)
|
|
{
|
|
case PlaceholderType.NoParameters:
|
|
PlaceholderType = PlaceholderType.Positional;
|
|
break;
|
|
|
|
case PlaceholderType.Named:
|
|
PlaceholderType = PlaceholderType.Mixed;
|
|
break;
|
|
|
|
case PlaceholderType.Positional:
|
|
case PlaceholderType.Mixed:
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(PlaceholderType), $"Unknown {nameof(PlaceholderType)} value: {PlaceholderType}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (PlaceholderType)
|
|
{
|
|
case PlaceholderType.NoParameters:
|
|
PlaceholderType = PlaceholderType.Named;
|
|
break;
|
|
|
|
case PlaceholderType.Positional:
|
|
PlaceholderType = PlaceholderType.Mixed;
|
|
break;
|
|
|
|
case PlaceholderType.Named:
|
|
case PlaceholderType.Mixed:
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(PlaceholderType), $"Unknown {nameof(PlaceholderType)} value: {PlaceholderType}");
|
|
}
|
|
}
|
|
}
|
|
|
|
internal bool HasOutputParameters { get; set; }
|
|
internal PlaceholderType PlaceholderType { get; set; }
|
|
|
|
static NpgsqlParameter Cast(object? value)
|
|
=> value is NpgsqlParameter p
|
|
? p
|
|
: throw new InvalidCastException(
|
|
$"The value \"{value}\" is not of type \"{nameof(NpgsqlParameter)}\" and cannot be used in this parameter collection.");
|
|
}
|
|
|
|
enum PlaceholderType
|
|
{
|
|
/// <summary>
|
|
/// The parameter collection includes no parameters.
|
|
/// </summary>
|
|
NoParameters,
|
|
|
|
/// <summary>
|
|
/// The parameter collection includes only named parameters.
|
|
/// </summary>
|
|
Named,
|
|
|
|
/// <summary>
|
|
/// The parameter collection includes only positional parameters.
|
|
/// </summary>
|
|
Positional,
|
|
|
|
/// <summary>
|
|
/// The parameter collection includes both named and positional parameters.
|
|
/// This is only supported when <see cref="NpgsqlCommand.CommandType" /> is set to <see cref="CommandType.StoredProcedure" />.
|
|
/// </summary>
|
|
Mixed
|
|
} |