diff --git a/Source/Core/Core/Transformer.cs b/Source/Core/Core/Transformer.cs
index b367183..3d59107 100644
--- a/Source/Core/Core/Transformer.cs
+++ b/Source/Core/Core/Transformer.cs
@@ -13,6 +13,8 @@ namespace Open.Database.Extensions.Core;
/// The type of the model to create from the data.
public class Transformer
{
+ const string ResultsObsolete = "Use table.CreateDataReader().Map(map).ToList() instead, where map is the RowMap you gave Transformer.Create. It returns a List, not a lazy enumerable, and has no clearTable argument: clear the table yourself after reading it. Removed in 12.";
+
///
/// Maximum number of arrays to hold in the local array pool per bucket.
/// May also define how many records are pre buffered before transforming.
@@ -162,11 +164,14 @@ static IEnumerable Drain(Queue
public static partial class CommandExtensions
{
+ const string ToListAsyncObsolete = "Use await command.Map(transform).ToListAsync(cancellationToken) instead. Map also reads the later result sets, so a failure in one is thrown; add .SingleResult() to read only the first, as this method does. Map executes asynchronously and then reads the rows with Read(), as useReadAsync: false did. It has no CommandBehavior argument: if you passed one, use await command.ExecuteReaderAsync(r => r.Map(transform).ToList(), behavior | CommandBehavior.SingleResult, cancellationToken). Removed in 12.";
+
#region Connection.EnsureOpen Shortcuts.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ConnectionState EnsureOpen(this IDbCommand command)
@@ -162,8 +164,10 @@ public static ValueTask> ToListAsync(this DbCommand command,
=> ToListAsync(command, behavior, transform, cancellationToken);
///
+ /// Obsolete: use await command.Map(transform).ToListAsync(cancellationToken).
/// Asynchronously iterates all records using an and returns the desired results as a list.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The return type of the transform function.
/// The to generate a reader from.
/// The behavior to use with the data reader.
@@ -171,6 +175,7 @@ public static ValueTask> ToListAsync(this DbCommand command,
/// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation.
/// The cancellation token.
/// A task containing a list of all results.
+ [Obsolete(ToListAsyncObsolete)]
public static async ValueTask> ToListAsync(this DbCommand command,
CommandBehavior behavior,
Func transform,
@@ -187,26 +192,31 @@ public static async ValueTask> ToListAsync(this DbCommand command,
#endif
using DbDataReader reader = await command.OpenAndExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false);
- if (useReadAsync) return await reader.ToListAsync(transform, cancellationToken).ConfigureAwait(false);
+ if (useReadAsync) return await DataReaderExtensions.ToListAsyncCore(reader, transform, cancellationToken).ConfigureAwait(false);
- var r = reader.ToList(transform, cancellationToken);
- return r;
+ return reader.Select(transform, cancellationToken).ToList();
}
///
+ [Obsolete(ToListAsyncObsolete)]
public static ValueTask> ToListAsync(this DbCommand command,
CommandBehavior behavior,
Func transform,
CancellationToken cancellationToken = default)
+#pragma warning disable CS0618 // One obsolete overload forwards to another.
=> ToListAsync(command, behavior, transform, true, cancellationToken);
+#pragma warning restore CS0618
///
+ [Obsolete(ToListAsyncObsolete)]
public static ValueTask> ToListAsync(this DbCommand command,
Func transform,
CommandBehavior behavior = CommandBehavior.Default,
bool useReadAsync = true,
CancellationToken cancellationToken = default)
+#pragma warning disable CS0618 // One obsolete overload forwards to another.
=> ToListAsync(command, behavior, transform, useReadAsync, cancellationToken);
+#pragma warning restore CS0618
///
/// Iterates all records using an and returns the desired results as an array.
diff --git a/Source/Core/Extensions/DataReader.cs b/Source/Core/Extensions/DataReader.cs
index 521777b..7b84c2a 100644
--- a/Source/Core/Extensions/DataReader.cs
+++ b/Source/Core/Extensions/DataReader.cs
@@ -11,6 +11,11 @@ namespace Open.Database.Extensions;
#pragma warning restore IDE0079 // Remove unnecessary suppression
public static class DataReaderExtensions
{
+ const string ReaderToListObsolete = "Use reader.Map(transform).ToList() instead. It reads the current result set, as this method does, and takes no CancellationToken: if you passed one, use reader.Select(transform, cancellationToken).ToList(). Removed in 12.";
+ const string ReaderToArrayObsolete = "Use reader.Map(transform).ToArray() instead. It reads the current result set, as this method does. Removed in 12.";
+ const string ReaderToImmutableArrayObsolete = "Use reader.Map(transform).ToImmutableArray() instead. It reads the current result set, as this method does. Removed in 12.";
+ const string ReaderToListAsyncObsolete = "Use await reader.Map(transform).ToListAsync(cancellationToken) instead. It reads the current result set, as this method does, but with Read() where this method used ReadAsync. Removed in 12.";
+
///
/// Iterates all records from an .
///
@@ -663,27 +668,37 @@ public static IAsyncEnumerable SelectAsync(this IDataReader reader,
=> SelectAsync(reader, transform, false, cancellationToken);
///
+ /// Obsolete: use reader.Map(transform).ToList().
/// Shortcut for .Iterate(transform).ToList();
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The return type of the transform function.
/// The IDataReader to iterate.
/// The transform function to process each .
/// Optional cancellation token.
/// A list of the transformed results.
+ [Obsolete(ReaderToListObsolete)]
public static List ToList(this IDataReader reader,
Func transform, CancellationToken cancellationToken = default)
=> reader.Select(transform, cancellationToken).ToList();
///
+ /// Obsolete: use await reader.Map(transform).ToListAsync(cancellationToken).
/// Asynchronously iterates all records using the data reader and returns the desired results as a list.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The return type of the transform function.
/// The SqlDataReader to read from.
/// The transform function to process each .
/// Optional cancellation token.
/// A task containing a list of all results.
- public static async ValueTask> ToListAsync(this DbDataReader reader,
+ [Obsolete(ReaderToListAsyncObsolete)]
+ public static ValueTask> ToListAsync(this DbDataReader reader,
Func transform, CancellationToken cancellationToken = default)
+ => ToListAsyncCore(reader, transform, cancellationToken);
+
+ internal static async ValueTask> ToListAsyncCore(DbDataReader reader,
+ Func transform, CancellationToken cancellationToken)
{
if (reader is null) throw new ArgumentNullException(nameof(reader));
if (transform is null) throw new ArgumentNullException(nameof(transform));
@@ -708,22 +723,28 @@ public static async ValueTask> ToListAsync(this DbDataReader reader,
}
///
+ /// Obsolete: use reader.Map(transform).ToArray().
/// Shortcut for .Select(transform).ToArray();
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The return type of the transform function.
/// The IDataReader to iterate.
/// The transform function to process each .
/// An array of the transformed results.
+ [Obsolete(ReaderToArrayObsolete)]
public static T[] ToArray(this IDataReader reader, Func transform)
=> reader.Select(transform).ToArray();
///
+ /// Obsolete: use reader.Map(transform).ToImmutableArray().
/// Shortcut for .Select(transform).ToImmutableArray();
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The return type of the transform function.
/// The IDataReader to iterate.
/// The transform function to process each .
/// An immutable array of the transformed results.
+ [Obsolete(ReaderToImmutableArrayObsolete)]
public static ImmutableArray ToImmutableArray(this IDataReader reader, Func transform)
=> reader.Select(transform).ToImmutableArray();
diff --git a/Source/Core/Extensions/IExecuteReader.cs b/Source/Core/Extensions/IExecuteReader.cs
index 85b6ca5..dbcdf2a 100644
--- a/Source/Core/Extensions/IExecuteReader.cs
+++ b/Source/Core/Extensions/IExecuteReader.cs
@@ -13,6 +13,10 @@ public static class IExecuteReaderExtensions
const string TakeObsolete = "Use .Map(selector).Take(count).ToList() instead. " + LaterSets + " Take(0) now runs the command; this method did not. Removed in 12.";
const string SkipObsolete = "Use .Map(selector).Skip(count).ToList() instead. " + LaterSets + " " + SkipDifference + " Removed in 12.";
const string SkipThenTakeObsolete = "Use .Map(selector).Skip(skip).Take(take).ToList() instead. " + LaterSets + " " + SkipDifference + " Take(0) now runs the command; this method did not. Removed in 12.";
+ const string ToListObsolete = "Use .Map(transform).ToList() instead. " + LaterSets + " Map has no CommandBehavior argument: if you passed one, use cmd.ExecuteReader(r => r.Map(transform).ToList(), behavior | CommandBehavior.SingleResult). Removed in 12.";
+ const string ToArrayObsolete = "Use .Map(transform).ToArray() instead. " + LaterSets + " Map has no CommandBehavior argument: if you passed one, use cmd.ExecuteReader(r => r.Map(transform).ToArray(), behavior | CommandBehavior.SingleResult). Removed in 12.";
+ const string ToImmutableArrayObsolete = "Use .Map(transform).ToImmutableArray() instead. " + LaterSets + " Map has no CommandBehavior argument: if you passed one, use cmd.ExecuteReader(r => r.Map(transform).ToImmutableArray(), behavior | CommandBehavior.SingleResult). Removed in 12.";
+ const string IterateReaderObsolete = "Use .Map(transform).Read(selector) instead. " + LaterSets + " Map has no CommandBehavior argument: if you passed one, use cmd.ExecuteReader(r => selector(r.Select(transform)), behavior | CommandBehavior.SingleResult). Removed in 12.";
///
/// Iterates a reader on a command with a handler function.
@@ -194,8 +198,10 @@ await reader
}
///
+ /// Obsolete: use .Map(transform).Read(selector).
/// Executes a reader on a command with a transform function.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The return type of the transform function applied to each record.
/// The type returned by the selector.
/// The IExecuteReader to iterate.
@@ -203,6 +209,7 @@ await reader
/// Provides an IEnumerable<TEntity> to select individual results by.
/// The behavior to use with the data reader.
/// The result of the transform.
+ [Obsolete(IterateReaderObsolete)]
public static TResult IterateReader(
this IExecuteReader command,
Func transform,
@@ -342,7 +349,7 @@ public static List Skip(this IExecuteReader command, int count, Func count == 0
- ? reader.ToList(transform)
+ ? reader.Select(transform).ToList()
: reader.Select(transform).Skip(count).ToList(),
CommandBehavior.SingleResult);
}
@@ -375,13 +382,16 @@ public static List SkipThenTake(this IExecuteReader command, int skip, int
}
///
+ /// Obsolete: use .Map(transform).ToList().
/// Converts all IDataRecords into a list using a transform function.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The expected return type.
/// The IExecuteReader to iterate.
/// The transform function.
/// The command behavior for once the command the reader is complete.
/// The list of transformed records.
+ [Obsolete(ToListObsolete)]
public static List ToList(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default)
{
if (command is null) throw new ArgumentNullException(nameof(command));
@@ -394,13 +404,16 @@ public static List ToList(this IExecuteReader command, Func
+ /// Obsolete: use .Map(transform).ToArray().
/// Converts all IDataRecords into an array using a transform function.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The expected return type.
/// The IExecuteReader to iterate.
/// The transform function.
/// The command behavior for once the command the reader is complete.
/// The array of transformed records.
+ [Obsolete(ToArrayObsolete)]
public static T[] ToArray(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default)
{
if (command is null) throw new ArgumentNullException(nameof(command));
@@ -413,13 +426,16 @@ public static T[] ToArray(this IExecuteReader command, Func t
}
///
+ /// Obsolete: use .Map(transform).ToImmutableArray().
/// Converts all IDataRecords into an immutable array using a transform function.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The expected return type.
/// The IExecuteReader to iterate.
/// The transform function.
/// The command behavior for once the command the reader is complete.
/// The array of transformed records.
+ [Obsolete(ToImmutableArrayObsolete)]
public static ImmutableArray ToImmutableArray(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default)
{
if (command is null) throw new ArgumentNullException(nameof(command));
diff --git a/Source/Core/Extensions/MappedQueryExtensions.cs b/Source/Core/Extensions/MappedQueryExtensions.cs
index 36621fb..6bcfc76 100644
--- a/Source/Core/Extensions/MappedQueryExtensions.cs
+++ b/Source/Core/Extensions/MappedQueryExtensions.cs
@@ -29,6 +29,8 @@ namespace Open.Database.Extensions;
[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Convenience and style.")]
public static class MappedQueryExtensions
{
+ const string ReadAsyncTokenFirstObsolete = "Pass the token last: query.ReadAsync(handler, cancellationToken). Removed in 12.";
+
///
/// Asynchronously executes the query and passes the mapped rows to as they are read. Use this overload when the handler does not need the cancellation token.
///
@@ -68,22 +70,30 @@ public static ValueTask ReadAsync(this IMappedQuery quer
}
///
+ /// Obsolete: use query.ReadAsync(handler, cancellationToken).
/// Asynchronously executes the query and passes the mapped rows to . The cancellation token comes first so the handler can be written as a trailing lambda.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
///
+ [Obsolete(ReadAsyncTokenFirstObsolete)]
public static ValueTask ReadAsync(this IMappedQuery query, CancellationToken cancellationToken, Func, ValueTask> handler)
=> query.ReadAsync(handler, cancellationToken);
///
+ /// Obsolete: use query.ReadAsync(handler, cancellationToken).
/// Asynchronously executes the query, passes the mapped rows to , and returns the handler's result. The cancellation token comes first so the handler can be written as a trailing lambda.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
///
+ [Obsolete(ReadAsyncTokenFirstObsolete)]
public static ValueTask ReadAsync(this IMappedQuery query, CancellationToken cancellationToken, Func, ValueTask> handler)
=> query.ReadAsync(handler, cancellationToken);
///
+ /// Obsolete: use query.ReadAsync(handler, cancellationToken).
/// Asynchronously executes the query and passes the mapped rows to together with the cancellation token in effect. The cancellation token comes first so the handler can be written as a trailing lambda.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The type each row is mapped to.
/// The query to execute.
/// Cancels the call, together with the cancellation token set on the underlying command: either one cancels it, whether it is opening the connection, looking up stored-procedure parameter names, executing or reading.
@@ -91,12 +101,15 @@ public static ValueTask ReadAsync(this IMappedQuery quer
/// A task that completes after the handler has completed and the connection has been released.
/// The operation was canceled.
///
+ [Obsolete(ReadAsyncTokenFirstObsolete)]
public static ValueTask ReadAsync(this IMappedQuery query, CancellationToken cancellationToken, Func, CancellationToken, ValueTask> handler)
=> query.ReadAsync(handler, cancellationToken);
///
+ /// Obsolete: use query.ReadAsync(handler, cancellationToken).
/// Asynchronously executes the query, passes the mapped rows to together with the cancellation token in effect, and returns the handler's result. The cancellation token comes first so the handler can be written as a trailing lambda.
///
+ /// Obsolete in 11.0, removed in 12. See Deprecated in 11.0 for the difference and a before/after example.
/// The type each row is mapped to.
/// The type returned by the handler.
/// The query to execute.
@@ -105,6 +118,7 @@ public static ValueTask ReadAsync(this IMappedQuery query, CancellationTok
/// A task that produces the value returned by after the connection has been released.
/// The operation was canceled.
///
+ [Obsolete(ReadAsyncTokenFirstObsolete)]
public static ValueTask ReadAsync(this IMappedQuery query, CancellationToken cancellationToken, Func, CancellationToken, ValueTask> handler)
=> query.ReadAsync(handler, cancellationToken);
@@ -136,8 +150,9 @@ public static List ToList(this IMappedQuery query)
///
///
public static ValueTask> ToListAsync(this IMappedQuery query, CancellationToken cancellationToken = default)
- => query.ReadAsync(cancellationToken,
- async (items, token) => await items.ToListAsync(token).ConfigureAwait(false));
+ => query.ReadAsync(
+ async (items, token) => await items.ToListAsync(token).ConfigureAwait(false),
+ cancellationToken);
///
/// Reads every mapped row into a new array.
@@ -167,8 +182,9 @@ public static T[] ToArray(this IMappedQuery query)
///
///
public static ValueTask ToArrayAsync(this IMappedQuery query, CancellationToken cancellationToken = default)
- => query.ReadAsync(cancellationToken,
- async (items, token) => await items.ToArrayAsync(token).ConfigureAwait(false));
+ => query.ReadAsync(
+ async (items, token) => await items.ToArrayAsync(token).ConfigureAwait(false),
+ cancellationToken);
///
/// Reads every mapped row into a new .
@@ -212,8 +228,9 @@ internal static async ValueTask> ToImmutableArrayAsync(this
///
///
///
- public static ValueTask> ToImmutableArrayAsync(this IMappedQuery query, CancellationToken cancellationToken = default) => query.ReadAsync(cancellationToken,
- async (items, token) => await items.ToImmutableArrayAsync(token).ConfigureAwait(false));
+ public static ValueTask> ToImmutableArrayAsync(this IMappedQuery query, CancellationToken cancellationToken = default) => query.ReadAsync(
+ async (items, token) => await items.ToImmutableArrayAsync(token).ConfigureAwait(false),
+ cancellationToken);
///
/// Reads every mapped row into a new , keyed by .
@@ -264,8 +281,9 @@ public static ValueTask> ToDictionaryAsync(
if (query is null) throw new ArgumentNullException(nameof(query));
if (keySelector is null) throw new ArgumentNullException(nameof(keySelector));
Contract.EndContractBlock();
- return query.ReadAsync(cancellationToken,
- async (items, token) => await items.ToDictionaryAsync(keySelector, comparer, token).ConfigureAwait(false));
+ return query.ReadAsync(
+ async (items, token) => await items.ToDictionaryAsync(keySelector, comparer, token).ConfigureAwait(false),
+ cancellationToken);
}
///
@@ -316,17 +334,21 @@ public static ValueTask> ToHashSetAsync(this IMappedQuery query
{
#if NETSTANDARD2_0
var result = new HashSet(comparer);
- return query.ReadAsync(cancellationToken, async (items, token) =>
- {
- await foreach (var item in items.WithCancellation(token).ConfigureAwait(false))
+ return query.ReadAsync(
+ async (items, token) =>
{
- result.Add(item);
- }
+ await foreach (var item in items.WithCancellation(token).ConfigureAwait(false))
+ {
+ result.Add(item);
+ }
- return result;
- });
+ return result;
+ },
+ cancellationToken);
#else
- return query.ReadAsync(cancellationToken, (items, token) => items.ToHashSetAsync(comparer, token));
+ return query.ReadAsync(
+ (items, token) => items.ToHashSetAsync(comparer, token),
+ cancellationToken);
#endif
}
@@ -361,8 +383,9 @@ public static ValueTask> ToHashSetAsync(this IMappedQuery query
///
///
public static ValueTask FirstOrDefaultAsync(this IMappedQuery query, CancellationToken cancellationToken = default)
- => query.ReadAsync(cancellationToken,
- async (items, token) => await items.FirstOrDefaultAsync(token).ConfigureAwait(false));
+ => query.ReadAsync(
+ async (items, token) => await items.FirstOrDefaultAsync(token).ConfigureAwait(false),
+ cancellationToken);
///
/// Reads the only mapped row.
@@ -386,6 +409,7 @@ public static ValueTask> ToHashSetAsync(this IMappedQuery query
/// The query returns more than one row.
///
public static ValueTask SingleOrDefaultAsync(this IMappedQuery query, CancellationToken cancellationToken = default)
- => query.ReadAsync(cancellationToken,
- async (items, token) => await items.SingleOrDefaultAsync(token).ConfigureAwait(false));
+ => query.ReadAsync(
+ async (items, token) => await items.SingleOrDefaultAsync(token).ConfigureAwait(false),
+ cancellationToken);
}
diff --git a/Source/Core/Extensions/Results.cs b/Source/Core/Extensions/Results.cs
index 4f4d904..d3b543e 100644
--- a/Source/Core/Extensions/Results.cs
+++ b/Source/Core/Extensions/Results.cs
@@ -193,10 +193,10 @@ public static ValueTask> ResultsAsync(
CancellationToken cancellationToken)
=> ResultsAsync(command, null, true, cancellationToken);
- const string ResultsFromDataTableObsolete = "Use .ToDataTable() then .To(map, true) instead, for example: using var table = command.ToDataTable(); var rows = table.To(map, true).ToList(). This method threw for a type that cannot be mapped before it ran; To still throws, but after the data is loaded: read RowMap.Default (or build your RowMap) first to keep the early failure. Removed in 12.";
+ const string ResultsFromDataTableObsolete = "Use .ToDataTable() then table.CreateDataReader().Map(map).ToList() instead, for example: using var table = command.ToDataTable(); using var tableReader = table.CreateDataReader(); var rows = tableReader.Map(map).ToList(). This method cleared the table as the rows were read; Map does not, so dispose or clear the table yourself. This method threw for a type that cannot be mapped before it ran; Map still throws, but after the data is loaded: read RowMap.Default (or build your RowMap) first to keep the early failure. Removed in 12.";
///
- /// Obsolete: use then .
+ /// Obsolete: use then table.CreateDataReader().Map(map).ToList().
/// Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T.
/// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration.
///
@@ -218,7 +218,7 @@ public static IEnumerable ResultsFromDataTable(this IDataReader reader, Ro
}
///
- /// Obsolete: use then .
+ /// Obsolete: use then table.CreateDataReader().Map(map).ToList().
/// Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T.
/// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration.
///
diff --git a/Source/Core/Extensions/_.cs b/Source/Core/Extensions/_.cs
index 7e74f03..412107d 100644
--- a/Source/Core/Extensions/_.cs
+++ b/Source/Core/Extensions/_.cs
@@ -5,6 +5,8 @@
///
public static partial class CoreExtensions
{
+ const string ToDataTableObsolete = "Use table.CreateDataReader().Map(map).ToList() instead. It returns a List, not a lazy enumerable, and has no clearSourceTable argument: clear the table yourself after reading it. Removed in 12.";
+
internal static IEnumerable Concat(T first, ICollection remaining)
=> (remaining == null || remaining.Count == 0) ? [first] : remaining.Prepend(first);
@@ -198,15 +200,24 @@ static IEnumerable AsEnumerableCore(DataRowCollection rows)
}
///
+ /// Obsolete: use table.CreateDataReader().Map(map).ToList().
/// Loads all data into a queue before iterating (dequeuing) the results as type .
///
- /// Declare the once and pass it: a map built in the call (a new RowMap<T>(...) or a collection expression in the argument) is rebuilt, and its row reader compiled, on every call.
+ ///
+ /// Obsolete in 11.0, removed in 12.
+ /// See Deprecated in 11.0
+ /// for the difference and a before/after example.
+ /// Declare the once and pass it:
+ /// a map built in the call (a new RowMap<T>(...) or a collection expression in the argument)
+ /// is rebuilt, and its row reader compiled, on every call.
+ ///
/// The model type to map the values to (using reflection).
/// The to read from.
/// An optional prebuilt ; reads by column name.
/// Clears the source table before providing the enumeration.
/// An enumerable used to iterate the results.
///
+ [Obsolete(ToDataTableObsolete)]
public static IEnumerable To(this DataTable table, RowMap? map = null, bool clearSourceTable = false)
=> Transformer
.Create(map)
diff --git a/Source/MSSqlClient/Extensions/Command.AddParameter.cs b/Source/MSSqlClient/Extensions/Command.AddParameter.cs
index 814ff35..fbf5310 100644
--- a/Source/MSSqlClient/Extensions/Command.AddParameter.cs
+++ b/Source/MSSqlClient/Extensions/Command.AddParameter.cs
@@ -69,6 +69,7 @@ public static SqlParameter AddParameterType(
/// is not a .
/// is .
/// is empty or white space.
+ [Obsolete("Call AddParameterType on the SqlCommand: this overload casts any IDbCommand to SqlCommand and throws InvalidCastException for any other command. Removed in 12.")]
public static SqlParameter AddParameterType(this IDbCommand target,
string name, SqlDbType type)
=> AddParameterType((SqlCommand)target, name, type);
diff --git a/Source/SqlClient/ExpressiveSqlCommand.cs b/Source/SqlClient/ExpressiveSqlCommand.cs
index 10d7b35..6bf3fef 100644
--- a/Source/SqlClient/ExpressiveSqlCommand.cs
+++ b/Source/SqlClient/ExpressiveSqlCommand.cs
@@ -15,6 +15,7 @@ namespace Open.Database.Extensions;
///
///
///
+[Obsolete(SqlClientObsolete._message)]
public class ExpressiveSqlCommand : ExpressiveDbCommandBase
{
///
diff --git a/Source/SqlClient/Extensions/Command.AddParameter.cs b/Source/SqlClient/Extensions/Command.AddParameter.cs
index 814ff35..fbf5310 100644
--- a/Source/SqlClient/Extensions/Command.AddParameter.cs
+++ b/Source/SqlClient/Extensions/Command.AddParameter.cs
@@ -69,6 +69,7 @@ public static SqlParameter AddParameterType(
/// is not a .
/// is .
/// is empty or white space.
+ [Obsolete("Call AddParameterType on the SqlCommand: this overload casts any IDbCommand to SqlCommand and throws InvalidCastException for any other command. Removed in 12.")]
public static SqlParameter AddParameterType(this IDbCommand target,
string name, SqlDbType type)
=> AddParameterType((SqlCommand)target, name, type);
diff --git a/Source/SqlClient/Extensions/Command.ExecuteReader.cs b/Source/SqlClient/Extensions/Command.ExecuteReader.cs
index 1ef0987..b5f78cc 100644
--- a/Source/SqlClient/Extensions/Command.ExecuteReader.cs
+++ b/Source/SqlClient/Extensions/Command.ExecuteReader.cs
@@ -3,6 +3,7 @@
///
/// SqlClient extensions for building a command and retrieving data using best practices.
///
+[Obsolete(SqlClientObsolete._message)]
public static partial class SqlCommandExtensions
{
#region Open and execute the reader
diff --git a/Source/SqlClient/Extensions/ConnectionProvider.Command.cs b/Source/SqlClient/Extensions/ConnectionProvider.Command.cs
index 47c1d69..0ca4b74 100644
--- a/Source/SqlClient/Extensions/ConnectionProvider.Command.cs
+++ b/Source/SqlClient/Extensions/ConnectionProvider.Command.cs
@@ -3,6 +3,7 @@
///
/// Core non-DB-specific extensions for acquiring and operating on different connection factories.
///
+[Obsolete(SqlClientObsolete._message)]
public static partial class SqlConnectionExtensions
{
///
diff --git a/Source/SqlClient/Extensions/Transaction.cs b/Source/SqlClient/Extensions/Transaction.cs
index 5e9c9fc..b9f6b21 100644
--- a/Source/SqlClient/Extensions/Transaction.cs
+++ b/Source/SqlClient/Extensions/Transaction.cs
@@ -8,6 +8,7 @@
/// System.Data.SqlClient specific extensions for database transactions.
///
[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")]
+[Obsolete(SqlClientObsolete._message)]
public static partial class SqlTransactionExtensions
{
///
diff --git a/Source/SqlClient/Obsolete.cs b/Source/SqlClient/Obsolete.cs
new file mode 100644
index 0000000..8df8fb1
--- /dev/null
+++ b/Source/SqlClient/Obsolete.cs
@@ -0,0 +1,9 @@
+namespace Open.Database.Extensions;
+
+///
+/// The message every public type of this package carries.
+///
+static class SqlClientObsolete
+{
+ internal const string _message = "Open.Database.Extensions.SqlClient (System.Data.SqlClient) is deprecated: use Open.Database.Extensions.MSSqlClient (Microsoft.Data.SqlClient), which has the same API. Removed in 12.";
+}
diff --git a/Source/SqlClient/SqlConnectionFactory.cs b/Source/SqlClient/SqlConnectionFactory.cs
index 553eb9c..433ed66 100644
--- a/Source/SqlClient/SqlConnectionFactory.cs
+++ b/Source/SqlClient/SqlConnectionFactory.cs
@@ -7,6 +7,7 @@
/// The factory holds no connections. The caller of Create owns the connection it receives and disposes it; the expressive commands do this automatically.
///
///
+[Obsolete(SqlClientObsolete._message)]
public class SqlConnectionFactory : DbConnectionFactory
{
///
diff --git a/Tests/Open.Database.Extensions.Core.Tests/CommandExecuteFailureTests.cs b/Tests/Open.Database.Extensions.Core.Tests/CommandExecuteFailureTests.cs
index 0f08bec..777bb5f 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/CommandExecuteFailureTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/CommandExecuteFailureTests.cs
@@ -63,8 +63,12 @@ sealed class OnlyDbCommand(IDbCommand inner) : IDbCommand
static readonly Dictionary> AsyncCalls = new()
{
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
["ToListAsync(transform)"] = (c, t) => c.ToListAsync(r => r.GetValue(0), cancellationToken: t).AsTask(),
+#pragma warning restore CS0618
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
["ToListAsync(behavior, transform)"] = (c, t) => c.ToListAsync(CommandBehavior.Default, r => r.GetValue(0), true, t).AsTask(),
+#pragma warning restore CS0618
["ToListAsync(async transform)"] = (c, t) => c.ToListAsync(CommandBehavior.Default, r => new ValueTask(r.GetValue(0)), t).AsTask(),
["ExecuteReaderAsync(Action)"] = (c, t) => c.ExecuteReaderAsync(_ => { }, CommandBehavior.Default, t).AsTask(),
["ExecuteReaderAsync(Func)"] = (c, t) => c.ExecuteReaderAsync(_ => default, CommandBehavior.Default, t).AsTask(),
diff --git a/Tests/Open.Database.Extensions.Core.Tests/CommandExtensionsTests.cs b/Tests/Open.Database.Extensions.Core.Tests/CommandExtensionsTests.cs
index dca50d9..cf543d5 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/CommandExtensionsTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/CommandExtensionsTests.cs
@@ -188,7 +188,9 @@ public static async Task ToListAsync_ClosedConnection_OpensAsyncAndRequestsClose
var probe = new ConnectionProbe(ConnectionState.Closed, dbConnection: true);
FakeDbCommand command = NewDbCommand(probe, 1, 2);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
List results = await command.ToListAsync(r => (int)r.GetValue(0), cancellationToken: TestContext.Current.CancellationToken);
+#pragma warning restore CS0618
Assert.Equal([1, 2], results);
Assert.Equal("OpenAsync", probe.Log);
@@ -201,7 +203,9 @@ public static async Task ToListAsync_OpenConnection_IsNotReopenedOrClosed()
var probe = new ConnectionProbe(ConnectionState.Open, dbConnection: true);
FakeDbCommand command = NewDbCommand(probe, 1, 2);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
List results = await command.ToListAsync(r => (int)r.GetValue(0), cancellationToken: TestContext.Current.CancellationToken);
+#pragma warning restore CS0618
Assert.Equal([1, 2], results);
Assert.Equal(string.Empty, probe.Log);
@@ -214,7 +218,9 @@ public static async Task ToListAsync_WithoutReadAsync_ReadsTheSameRows()
var probe = new ConnectionProbe(ConnectionState.Open, dbConnection: true);
FakeDbCommand command = NewDbCommand(probe, 1, 2, 3);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
List results = await command.ToListAsync(r => (int)r.GetValue(0), useReadAsync: false, cancellationToken: TestContext.Current.CancellationToken);
+#pragma warning restore CS0618
Assert.Equal([1, 2, 3], results);
}
diff --git a/Tests/Open.Database.Extensions.Core.Tests/ConstructorBindingTests.cs b/Tests/Open.Database.Extensions.Core.Tests/ConstructorBindingTests.cs
index dd7f65b..3e9154f 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/ConstructorBindingTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/ConstructorBindingTests.cs
@@ -147,7 +147,9 @@ public static void ADataTable_IsMappedToPositionalRecords()
table.Rows.Add(1, "one");
table.Rows.Add(2, DBNull.Value);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal([new(1, "one"), new(2, null!)], table.To()); // a NULL into a non-nullable reference parameter gets its fallback: no declared default, so null (NullRuleMatrixTests covers one with a default)
+#pragma warning restore CS0618
}
[Fact]
diff --git a/Tests/Open.Database.Extensions.Core.Tests/DataReaderExtensionsTests.cs b/Tests/Open.Database.Extensions.Core.Tests/DataReaderExtensionsTests.cs
index cba42bd..cac6e04 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/DataReaderExtensionsTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/DataReaderExtensionsTests.cs
@@ -68,10 +68,16 @@ public static void ToList_ToArray_ToImmutableArray_ProduceSameSequence()
{
static int First(IDataRecord r) => (int)r.GetValue(0);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal([1, 2], FakeReader.Create(["Id"], [1], [2]).ToList(First, TestContext.Current.CancellationToken));
+#pragma warning restore CS0618
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal([1, 2], FakeReader.Create(["Id"], [1], [2]).ToArray(First));
+#pragma warning restore CS0618
// ImmutableArray implements IEquatable (reference equality), so compare as a sequence.
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal([1, 2], FakeReader.Create(["Id"], [1], [2]).ToImmutableArray(First).ToArray());
+#pragma warning restore CS0618
}
[Fact]
diff --git a/Tests/Open.Database.Extensions.Core.Tests/DeprecationTests.cs b/Tests/Open.Database.Extensions.Core.Tests/DeprecationTests.cs
index 3b663bb..bd912ef 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/DeprecationTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/DeprecationTests.cs
@@ -1,4 +1,7 @@
+extern alias mssql;
+
using Open.Database.Extensions.Core;
+using System.Data.Common;
using System.Reflection;
#nullable enable
@@ -37,6 +40,14 @@ static IEnumerable OnReader(string name, bool selector)
&& p.Any(x => IsFunc(x.ParameterType)) == selector;
});
+ static readonly string[] ReaderTerminals = ["ToList", "ToArray", "ToImmutableArray"];
+
+ static bool IsRecordSelector(ParameterInfo p)
+ => IsFunc(p.ParameterType)
+ && p.ParameterType.GetGenericArguments() is [var input, var output]
+ && input == typeof(IDataRecord)
+ && output.IsGenericParameter;
+
// (group, members, text every message must contain)
static IEnumerable<(string Group, MemberInfo[] Members, string[] Contains)> Table()
{
@@ -44,7 +55,7 @@ static IEnumerable OnReader(string name, bool selector)
yield return ("1a Results(IExecuteReader)", [.. OnReader("Results", false)], [".Map().ToList()", ".Map(map).ToList()", ".SingleResult()"]);
yield return ("1b/1c ResultsAsync", [.. Of(command, "ResultsAsync")], [".Map().ToListAsync()", ".Map(map).ToListAsync()", ".SingleResult()"]);
- yield return ("2 ResultsFromDataTable", [.. Of(typeof(CoreExtensions), "ResultsFromDataTable")], [".ToDataTable()", ".To(map, true)", "RowMap.Default"]);
+ yield return ("2 ResultsFromDataTable", [.. Of(typeof(CoreExtensions), "ResultsFromDataTable")], [".ToDataTable()", ".CreateDataReader().Map(map).ToList()", "RowMap.Default"]);
yield return ("3a FirstOrDefault(selector)", [.. OnReader("FirstOrDefault", true)], [".Map(selector).FirstOrDefault()"]);
yield return ("3b SingleOrDefault(selector)", [.. OnReader("SingleOrDefault", true)], [".Map(selector).SingleOrDefault()"]);
yield return ("3c Take(selector)", [.. OnReader("Take", true)], [".Map(selector).Take(count).ToList()", "Take(0) now runs the command"]);
@@ -59,6 +70,21 @@ static IEnumerable OnReader(string name, bool selector)
yield return ("6b/6c AsMappedDictionaries (async)", [.. Of(typeof(QueryResultExtensions), "AsMappedDictionaries", m => m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() != typeof(QueryResult<>))], ["(await cmd.RetrieveAsync()).DequeueAsMappedDictionaries()"]);
yield return ("7a DequeueAs", [.. Of(typeof(QueryResultExtensions), "DequeueAs", m => m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(QueryResult<>))], ["cmd.Map().ToList()", "command.Results()", "reader.ResultsBuffered()"]);
yield return ("7b-7e DequeueAs (async)", [.. Of(typeof(QueryResultExtensions), "DequeueAs", m => m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() != typeof(QueryResult<>))], ["await cmd.Map().ToListAsync()", "(await cmd.RetrieveAsync()).DequeueAs()", "await command.ResultsAsync()"]);
+
+ yield return ("8a reader ToList/ToArray/ToImmutableArray(transform)", [.. ReaderTerminals.SelectMany(name => Of(typeof(DataReaderExtensions), name, m => m.GetParameters() is [{ ParameterType: var first }, var second, ..] && first == typeof(IDataReader) && IsRecordSelector(second)))], ["reader.Map(transform)"]);
+ yield return ("8b reader ToListAsync(transform)", [.. Of(typeof(DataReaderExtensions), "ToListAsync", m => m.GetParameters() is [_, var second, ..] && IsRecordSelector(second))], ["await reader.Map(transform).ToListAsync(cancellationToken)"]);
+ yield return ("9a IExecuteReader ToList/ToArray/ToImmutableArray(transform)", [.. ReaderTerminals.SelectMany(name => OnReader(name, true))], [".Map(transform)", "CommandBehavior.SingleResult", ".SingleResult()"]);
+ yield return ("9b IExecuteReader IterateReader(transform, selector)", [.. OnReader("IterateReader", true).Where(m => m.GetGenericArguments().Length == 2)], [".Map(transform).Read(selector)", "CommandBehavior.SingleResult", ".SingleResult()"]);
+ yield return ("10 DbCommand ToListAsync(transform)", [.. Of(typeof(CommandExtensions), "ToListAsync", m => m.GetParameters() is [{ ParameterType: var first }, ..] && first == typeof(DbCommand) && m.GetParameters().Any(IsRecordSelector))], ["await command.Map(transform).ToListAsync(cancellationToken)", ".SingleResult()"]);
+ yield return ("11a To(DataTable)", [.. Of(typeof(CoreExtensions), "To", m => m.GetParameters()[0].ParameterType == typeof(DataTable))], ["table.CreateDataReader().Map(map).ToList()", "clear the table yourself"]);
+ yield return ("11b Transformer.Results(DataTable)", [.. Of(typeof(Transformer<>), "Results", m => m.GetParameters() is [{ ParameterType: var first }, ..] && first == typeof(DataTable))], ["table.CreateDataReader().Map(map).ToList()", "clear the table yourself"]);
+ yield return ("12 MappedQueryExtensions.ReadAsync(token first)", [.. Of(typeof(MappedQueryExtensions), "ReadAsync", m => m.GetParameters()[1].ParameterType == typeof(CancellationToken))], ["query.ReadAsync(handler, cancellationToken)"]);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
+ yield return ("13 SqlClient types", [typeof(ExpressiveSqlCommand), typeof(SqlConnectionFactory), typeof(SqlCommandExtensions), typeof(SqlConnectionExtensions), typeof(SqlTransactionExtensions)], ["Open.Database.Extensions.MSSqlClient"]);
+ yield return ("14a AddParameterType(IDbCommand, SqlDbType) in SqlClient", [.. Of(typeof(SqlCommandExtensions), "AddParameterType", m => m.GetParameters()[0].ParameterType == typeof(IDbCommand))], ["Call AddParameterType on the SqlCommand"]);
+#pragma warning restore CS0618
+ yield return ("14b AddParameterType(IDbCommand, SqlDbType) in MSSqlClient", [.. Of(typeof(mssql::Open.Database.Extensions.SqlCommandExtensions), "AddParameterType", m => m.GetParameters()[0].ParameterType == typeof(IDbCommand))], ["Call AddParameterType on the SqlCommand"]);
+ yield return ("15 ExpressiveCommandBase.Concat", [typeof(ExpressiveCommandBase<,,,,>).GetMethod("Concat", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)!], ["remaining.Prepend(first)"]);
}
[Fact]
@@ -81,7 +107,7 @@ public static void EveryObsoleteMember_IsAWarning_NamesItsReplacement_AndSaysWhe
}
}
- Assert.Equal(22, members); // 3 + 2 + 5 + 2 + 2 + 3 + 5
+ Assert.Equal(47, members); // 22 before 11.0.0-rc.4 + 3 + 1 + 3 + 1 + 3 + 2 + 4 + 5 + 2 + 1
}
// ---- group 1 ----
@@ -126,7 +152,9 @@ public static void ResultsFromDataTable_AndToDataTableThenTo_GiveTheSameRows()
string old = Show(source.CreateDataReader().ResultsFromDataTable());
#pragma warning restore CS0618
using var table = source.CreateDataReader().ToDataTable();
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
string replacement = Show(table.To(null, true).ToList());
+#pragma warning restore CS0618
Assert.Equal("1:n1,2:n2", old);
Assert.Equal(old, replacement);
diff --git a/Tests/Open.Database.Extensions.Core.Tests/ExecuteReaderEdgeTests.cs b/Tests/Open.Database.Extensions.Core.Tests/ExecuteReaderEdgeTests.cs
index db121e7..16b797c 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/ExecuteReaderEdgeTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/ExecuteReaderEdgeTests.cs
@@ -144,7 +144,9 @@ public static void TheCallersBehavior_IsKept_AndSingleResultIsAdded()
{
FakeExecuteReader command = Command(3, out _);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
command.ToImmutableArray(Id, CommandBehavior.SequentialAccess);
+#pragma warning restore CS0618
CommandBehavior behavior = Assert.Single(command.Behaviors);
Assert.True(behavior.HasFlag(CommandBehavior.SequentialAccess));
@@ -202,7 +204,9 @@ public static void ANullArgument_IsRefusedBeforeTheCommandRuns(string call, stri
{
case "First transform": command.First((Func)null!); break;
case "Retrieve columnNames": command.Retrieve((IEnumerable)null!); break;
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
default: ((IExecuteReader)null!).ToList(Id); break;
+#pragma warning restore CS0618
}
});
diff --git a/Tests/Open.Database.Extensions.Core.Tests/NullGuardDefectTests.cs b/Tests/Open.Database.Extensions.Core.Tests/NullGuardDefectTests.cs
index b526299..63c15f3 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/NullGuardDefectTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/NullGuardDefectTests.cs
@@ -68,7 +68,9 @@ public static void IterateReader_NullSelector_IsRefusedBeforeTheCommandRuns()
var tally = new DbTally();
ExpressiveDbCommand command = tally.Factory.Command("select 1");
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
var ex = Assert.Throws(() => command.IterateReader(r => r.GetValue(0), (Func, int>)null!));
+#pragma warning restore CS0618
Assert.Equal("selector", ex.ParamName);
Assert.Empty(tally.Connections);
diff --git a/Tests/Open.Database.Extensions.Core.Tests/NullRuleMatrixTests.cs b/Tests/Open.Database.Extensions.Core.Tests/NullRuleMatrixTests.cs
index c198afe..a7f45a1 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/NullRuleMatrixTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/NullRuleMatrixTests.cs
@@ -250,28 +250,36 @@ public static void ADataTable_FollowsTheSameRule()
foreach (ColumnDef column in PropsOnColumns) props.Columns.Add(column.Name, column.Type);
props.Rows.Add(DBNull.Value, DBNull.Value, DBNull.Value, DBNull.Value, DBNull.Value, DBNull.Value);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
PropsOn item = Assert.Single(props.To());
+#pragma warning restore CS0618
Assert.Equal((7, null, "init", null, null, null), (item.Value, item.Maybe, item.KeepsInit, item.NoInit, item.Nullable, item.Allowed));
DataTable parameters = new();
foreach (ColumnDef column in ParamsOnColumns) parameters.Columns.Add(column.Name, column.Type);
parameters.Rows.Add(DBNull.Value, DBNull.Value, DBNull.Value, DBNull.Value, DBNull.Value);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
ParamsOn built = Assert.Single(parameters.To());
+#pragma warning restore CS0618
Assert.Equal((null, 7, null, "d", null), (built.NoDefault, built.Value, built.Maybe, built.Keeps, built.Nullable));
DataTable named = new();
named.Columns.Add("Name", typeof(string));
named.Rows.Add(DBNull.Value);
named.Rows.Add("given");
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal(["", "given"], named.To().Select(n => n.Name));
+#pragma warning restore CS0618
DataTable defaulted = new();
defaulted.Columns.Add("Id", typeof(int));
defaulted.Columns.Add("Title", typeof(string));
defaulted.Rows.Add(1, DBNull.Value);
defaulted.Rows.Add(2, "given");
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal([new(1, "n/a"), new(2, "given")], defaulted.To());
+#pragma warning restore CS0618
}
// ---- the cases the owner named ----
diff --git a/Tests/Open.Database.Extensions.Core.Tests/RequiredMemberTests.cs b/Tests/Open.Database.Extensions.Core.Tests/RequiredMemberTests.cs
index 4293e96..888f3b9 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/RequiredMemberTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/RequiredMemberTests.cs
@@ -253,7 +253,9 @@ public static void TheBufferedRoutes_BuildThePlanWhenTheBufferIsHandedOver_SoEve
var table = new DataTable();
table.Columns.Add("Id", typeof(int));
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal(message, Assert.Throws(() => table.To()).Message);
+#pragma warning restore CS0618
// With every column present an empty buffer maps to nothing.
var complete = new QueryResult>([0, 1], ["Id", "Title"], new Queue());
@@ -373,12 +375,16 @@ public static void TheObjectArrayRoutes_MapARequiredType_AndReportAMissingColumn
table.Columns.Add("Title", typeof(string));
table.Rows.Add(1, "one");
table.Rows.Add(2, "two");
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal([(1, "one"), (2, "two")], table.To().Select(p => (p.Id, p.Title)));
+#pragma warning restore CS0618
var withoutTitle = new DataTable();
withoutTitle.Columns.Add("Id", typeof(int));
withoutTitle.Rows.Add(1);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal(Missing("RequiredPost", "Title", "String", "Title"), Assert.Throws(() => withoutTitle.To()).Message);
+#pragma warning restore CS0618
}
// A table is only cleared (clearSourceTable) once the rows could be mapped: a missing required column fails the call and leaves the caller's rows where they were.
@@ -390,12 +396,16 @@ public static void ADataTable_IsNotCleared_WhenARequiredColumnIsMissing()
table.Rows.Add(1);
table.Rows.Add(2);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
_ = Assert.Throws(() => table.To(clearSourceTable: true));
+#pragma warning restore CS0618
Assert.Equal(2, table.Rows.Count);
table.Columns.Add("Title", typeof(string));
foreach (DataRow row in table.Rows) row["Title"] = "t";
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Equal(2, table.To(clearSourceTable: true).Count());
+#pragma warning restore CS0618
Assert.Equal(0, table.Rows.Count); // a mappable table is cleared, as ever
}
diff --git a/Tests/Open.Database.Extensions.Core.Tests/RowMapFamilyTests.cs b/Tests/Open.Database.Extensions.Core.Tests/RowMapFamilyTests.cs
index 16f2eeb..661b1c8 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/RowMapFamilyTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/RowMapFamilyTests.cs
@@ -132,7 +132,9 @@ static async Task> Enumerate(IAsyncEnumerable source)
["ResultsAsync(DbDataReader)"] = m => Enumerate(DbReader().ResultsAsync(m)),
["ResultsBufferedAsync(DbDataReader)"] = async m => (await DbReader().ResultsBufferedAsync(m)).ToList(),
["ResultsAsync(DbCommand)"] = async m => (await DbCmd().ResultsAsync(m)).ToList(),
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
["To(DataTable)"] = m => Now(Table().To(m)),
+#pragma warning restore CS0618
#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
["DequeueAs(QueryResult)"] = m => Now(Queued().DequeueAs(m)),
["DequeueAs(Task)"] = async m => (await Task.FromResult(Queued()).DequeueAs(m)).ToList(),
diff --git a/Tests/Open.Database.Extensions.Core.Tests/SqlServerPackageTests.cs b/Tests/Open.Database.Extensions.Core.Tests/SqlServerPackageTests.cs
index 73d5b8d..328528e 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/SqlServerPackageTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/SqlServerPackageTests.cs
@@ -194,8 +194,12 @@ public static void AddParameterType_OnAnIDbCommandThatIsNotASqlCommand_ThrowsInv
{
IDbCommand command = Substitute.For();
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Throws(() => SqlCmd.AddParameterType(command, "@x", SqlDbType.Int));
+#pragma warning restore CS0618
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
Assert.Throws(() => SqlCmd.AddParameterType(null!, "@x", SqlDbType.Int));
+#pragma warning restore CS0618
}
[Fact]
diff --git a/Tests/Open.Database.Extensions.Core.Tests/SqlServerParameterTypeTests.cs b/Tests/Open.Database.Extensions.Core.Tests/SqlServerParameterTypeTests.cs
index 1b9a296..df66ef2 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/SqlServerParameterTypeTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/SqlServerParameterTypeTests.cs
@@ -23,8 +23,10 @@ static string Show(object? value)
=> value is null ? "null" : value is DBNull ? "DBNull" : Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture)!;
// What the command holds: name=value databaseType direction, "-" when no database type was given.
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
static string Sent(global::Open.Database.Extensions.ExpressiveSqlCommand command)
=> string.Join("; ", command.Params.Select(p => $"{p.Name}={Show(p.Value)} {(p.Type.HasValue ? p.Type.Value.ToString() : "-")} {p.Direction}"));
+#pragma warning restore CS0618
static string Sent(mssql::Open.Database.Extensions.ExpressiveSqlCommand command)
=> string.Join("; ", command.Params.Select(p => $"{p.Name}={Show(p.Value)} {(p.Type.HasValue ? p.Type.Value.ToString() : "-")} {p.Direction}"));
diff --git a/Tests/Open.Database.Extensions.Core.Tests/TransformerBufferTests.cs b/Tests/Open.Database.Extensions.Core.Tests/TransformerBufferTests.cs
index 52bd7e8..31412a5 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/TransformerBufferTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/TransformerBufferTests.cs
@@ -131,7 +131,9 @@ public static void Results_DataTable_ClearTable_BuffersRowsThenClearsTheSource()
{
DataTable table = NewTable();
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
var people = Transformer.Create().Results(table, clearTable: true).ToList();
+#pragma warning restore CS0618
Assert.Empty(table.Rows);
Assert.Equal([1, 2], people.Select(p => p.Id));
@@ -143,7 +145,9 @@ public static void Results_DataTable_NoClear_LeavesTheSourceIntact()
{
DataTable table = NewTable();
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
var people = Transformer.Create().Results(table, clearTable: false).ToList();
+#pragma warning restore CS0618
Assert.Equal(2, table.Rows.Count);
Assert.Equal(2, people.Count);
diff --git a/Tests/Open.Database.Extensions.Core.Tests/TransformerResultsTests.cs b/Tests/Open.Database.Extensions.Core.Tests/TransformerResultsTests.cs
index 7d8e331..f79e3e4 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/TransformerResultsTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/TransformerResultsTests.cs
@@ -30,7 +30,9 @@ public static void To_DefaultMapping_IsCaseInsensitive_AndIgnoresUnderscores()
{
DataTable table = BuildTable();
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
List people = [.. table.To()];
+#pragma warning restore CS0618
Assert.Equal(2, people.Count);
Assert.Equal("John", people[0].FirstName);
@@ -44,7 +46,9 @@ public static void To_WithFieldOverride_MapsColumn()
DataTable table = BuildTable();
var map = new RowMap([new(nameof(Person.LastName), "last_name")]);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
List people = [.. table.To(map)];
+#pragma warning restore CS0618
Assert.Equal("Doe", people[0].LastName);
Assert.Equal("Roe", people[1].LastName);
@@ -57,7 +61,9 @@ public static void To_NullOverride_IgnoresProperty()
// Explicitly ignore Age even though an AGE column is present.
var map = new RowMap([new(nameof(Person.Age), null)]);
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
List people = [.. table.To(map)];
+#pragma warning restore CS0618
Assert.Equal(0, people[0].Age); // ignored -> default
Assert.Equal("John", people[0].FirstName);
diff --git a/Tests/Open.Database.Extensions.Core.Tests/UnmappableTypeTests.cs b/Tests/Open.Database.Extensions.Core.Tests/UnmappableTypeTests.cs
index 9c3e766..48bfd75 100644
--- a/Tests/Open.Database.Extensions.Core.Tests/UnmappableTypeTests.cs
+++ b/Tests/Open.Database.Extensions.Core.Tests/UnmappableTypeTests.cs
@@ -400,7 +400,9 @@ static Task Run(Action call)
["ResultsAsync(DbCommand)"] = async () => await DbCmd().ResultsAsync(),
// Core: DataTable and Retrieve
+#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
["To(DataTable)"] = () => Run(() => Table().To()),
+#pragma warning restore CS0618
#pragma warning disable CS0618 // Obsolete in 11.0, removed in 12: kept covered until then.
["DequeueAs(QueryResult)"] = () => Run(() => Queued().DequeueAs()),
["DequeueAs(Task)"] = async () => await Task.FromResult(Queued()).DequeueAs(),
diff --git a/docsite/articles/foundation.md b/docsite/articles/foundation.md
index 78e09bb..beb7a2f 100644
--- a/docsite/articles/foundation.md
+++ b/docsite/articles/foundation.md
@@ -35,7 +35,7 @@ A command built from a single connection refuses concurrent terminals: a second
Several helpers exist to keep the time a connection is open short:
- `EnsureOpen()` and `EnsureOpenAsync()` open a closed (or broken) connection and return its prior state, so you can restore it.
-- Buffering helpers such as `ResultsBuffered()` buffer the rows and map them in one call. On an `IExecuteReader`, `Results()`, `FirstOrDefault(selector)`, `SingleOrDefault(selector)`, `Take`, `Skip` and `SkipThenTake` are deprecated; use `Map(...)`. See [Deprecated in 11.0](whats-new-11.md#deprecated-in-110).
+- Buffering helpers such as `ResultsBuffered()` buffer the rows and map them in one call. On an `IExecuteReader`, `Results()`, `FirstOrDefault(selector)`, `SingleOrDefault(selector)`, `Take`, `Skip`, `SkipThenTake`, and `ToList`, `ToArray`, `ToImmutableArray` and `IterateReader` with a transform are deprecated; use `Map(...)`. See [Deprecated in 11.0](whats-new-11.md#deprecated-in-110).
- `Map()` streams rows straight out of the reader while the connection is open; the connection is released as soon as the terminal returns.
## Transactions
diff --git a/docsite/articles/mapping.md b/docsite/articles/mapping.md
index 150da3f..e2a73cd 100644
--- a/docsite/articles/mapping.md
+++ b/docsite/articles/mapping.md
@@ -230,7 +230,7 @@ A *result* here is one whole result set: what `NextResult` moves between. It is
Two cases do not read through, so a later failure goes unseen: `.SingleResult()`, and a `Read` or an enumeration you stopped early (a `break` out of the rows, `First()` on them) or that ended in an exception of yours (the mapping, your selector, `SingleOrDefault` finding two rows, your handler). Your own exception is never replaced by a later one. `FirstOrDefault` and `Take` are not stopping early: they return their rows and still read through, and the provider discards the first set's unread rows on the way.
-The deprecated `Results` on an `IExecuteReader`, the selector `FirstOrDefault`, `SingleOrDefault`, `Take`, `Skip` and `SkipThenTake`, `ExpressiveDbCommand.TakeAsync` and `ToListAsync(selector)`, and `Retrieve().DequeueAs()` read only the first result set. `Map` reads through the rest, and `.SingleResult()` restores the old behavior. See [Deprecated in 11.0](whats-new-11.md#deprecated-in-110).
+The deprecated `Results` on an `IExecuteReader`, the selector `FirstOrDefault`, `SingleOrDefault`, `Take`, `Skip` and `SkipThenTake`, the `ToList`, `ToArray`, `ToImmutableArray` and `IterateReader` with a transform, `ExpressiveDbCommand.TakeAsync` and `ToListAsync(selector)`, and `Retrieve().DequeueAs()` read only the first result set. `Map` reads through the rest, and `.SingleResult()` restores the old behavior. See [Deprecated in 11.0](whats-new-11.md#deprecated-in-110).
`.SingleResult()` goes after `Map` (and on the result of `Skip` and `Take`). It asserts nothing: it does not check that there is only one result set. It stops after the first, so a later failure is not seen, and the reader is asked for `SingleResult` (`SingleRow` too for `FirstOrDefault` when nothing is skipped), which lets the provider stop early:
diff --git a/docsite/articles/whats-new-11.md b/docsite/articles/whats-new-11.md
index 94374ce..5b22c88 100644
--- a/docsite/articles/whats-new-11.md
+++ b/docsite/articles/whats-new-11.md
@@ -77,15 +77,24 @@ These are obsolete in 11.0 (compile warning CS0618) and will be removed in 12. E
| Old call | New call | Behaviour difference |
|---|---|---|
-| `Results(IExecuteReader)` | `.Map().ToList()`; with a map `.Map(map).ToList()` | Later result sets are read, so a later failure throws (`.SingleResult()` keeps the old behaviour). Returns a `List`. A null map is not accepted: use `Map()`. |
+| `Results(IExecuteReader)` | `.Map().ToList()`; with a map `.Map(map).ToList()` | Later result sets are read, so a later failure throws (`.SingleResult()` keeps the old behaviour). Returns a `List`. |
| `ExpressiveDbCommand.ResultsAsync` | `.Map().ToListAsync()` (token optional: `.ToListAsync(cancellationToken)`); with a map `.Map(map).ToListAsync()` | The same as above. |
-| `ResultsFromDataTable` (reader, command) | `using var table = command.ToDataTable(); table.To(map, true)` | The same rows: the old body did exactly this, and `true` clears the table as rows are read. A type that cannot be mapped is reported after the data is loaded, not before the command runs: read `RowMap.Default` first to fail early. |
+| `ResultsFromDataTable` (reader, command) | `using var table = command.ToDataTable(); using var tableReader = table.CreateDataReader(); tableReader.Map(map).ToList()` | The same rows. The old call cleared the table as rows were read; clear or dispose it yourself. A type that cannot be mapped is reported after the data is loaded, not before the command runs: read `RowMap.Default` first to fail early. |
| `FirstOrDefault(selector)`, `SingleOrDefault(selector)` on `IExecuteReader` | `.Map(selector).FirstOrDefault()`, `.Map(selector).SingleOrDefault()` | Later result sets are read (`.SingleResult()` keeps the old behaviour). |
| `Take(n, selector)`, `Skip(n, selector)`, `SkipThenTake(s, t, selector)` on `IExecuteReader` | `.Map(selector).Take(n).ToList()`, `.Map(selector).Skip(n).ToList()`, `.Map(selector).Skip(s).Take(t).ToList()` | The selector is not run on skipped rows, so a selector that throws on one no longer fails the call. `Take(0)` now runs the command. Later result sets are read. |
| `ExpressiveDbCommand.TakeAsync(selector, n)`, `ExpressiveDbCommand.ToListAsync(selector)` | `.Map(selector).Take(n).ToListAsync(cancellationToken)`, `.Map(selector).ToListAsync(cancellationToken)` | Later result sets are read. Returns a `List`. `Take(0)` now runs the command. `Map` has no `CommandBehavior` argument: if you passed one, call `IterateReaderAsync(handler, behavior)` or `IterateReaderWhileAsync(handler, behavior)` yourself. |
| `QueryResultCollection`, `QueryResultCollection` | None | Nothing in the library returns it. Use the `List` from `Map().ToList()`, or your own class. |
-| `cmd.Retrieve().DequeueAs()` (`QueryResultExtensions.DequeueAs`, 5 overloads) | `cmd.Map().ToList()`; with a map `cmd.Map(map).ToList()`. Async: `(await cmd.RetrieveAsync()).DequeueAs()` becomes `await cmd.Map().ToListAsync()`. On an `IDbCommand`: `command.Results()`. On an `IDataReader`: `reader.ResultsBuffered()`. On a `DbCommand`, async: `await command.ResultsAsync()`. | `Map` reads in one pass instead of copying raw rows first. A missing `required` column is reported at the first row, so not for an empty result. Later result sets are read (`.SingleResult()` keeps the first only, as `Retrieve` did). A null map is not accepted. `DequeueAs` returned a one-pass enumerable; `ToList()` returns a `List`. `Results(IDbCommand)` takes no `CommandBehavior`, where `Retrieve(IDbCommand, behavior)` did. |
+| `cmd.Retrieve().DequeueAs()` (`QueryResultExtensions.DequeueAs`, 5 overloads) | `cmd.Map().ToList()`; with a map `cmd.Map(map).ToList()`. Async: `(await cmd.RetrieveAsync()).DequeueAs()` becomes `await cmd.Map().ToListAsync()`. On an `IDbCommand`: `command.Results()`. On an `IDataReader`: `reader.ResultsBuffered()`. On a `DbCommand`, async: `await command.ResultsAsync()`. | `Map` reads in one pass instead of copying raw rows first. A missing `required` column is reported at the first row, so not for an empty result. Later result sets are read (`.SingleResult()` keeps the first only, as `Retrieve` did). `DequeueAs` returned a one-pass enumerable; `ToList()` returns a `List`. `Results(IDbCommand)` takes no `CommandBehavior`, where `Retrieve(IDbCommand, behavior)` did. |
| `QueryResultExtensions.AsMappedDictionaries` | `cmd.Retrieve().DequeueAsMappedDictionaries()`; async `(await cmd.RetrieveAsync()).DequeueAsMappedDictionaries()` | Nothing in the library produces the input it takes. `DequeueAsMappedDictionaries` drains the queue as it is enumerated, so it can be read once, where `AsMappedDictionaries` could be read again. |
+| `ToList(transform)`, `ToArray(transform)`, `ToImmutableArray(transform)` on an `IDataReader`; `ToListAsync(transform)` on a `DbDataReader` | `reader.Map(transform).ToList()`, `.ToArray()`, `.ToImmutableArray()`; `await reader.Map(transform).ToListAsync(cancellationToken)` | The same rows, from the current result set; the asynchronous form reads them with `Read()` where `ToListAsync` used `ReadAsync`. The synchronous `ToList` took a `CancellationToken` that `Map` does not: use `reader.Select(transform, cancellationToken).ToList()`. `Select` and `SelectAsync` are not deprecated. |
+| `ToList(transform, behavior)`, `ToArray(transform, behavior)`, `ToImmutableArray(transform, behavior)` on an `IExecuteReader` | `.Map(transform).ToList()`, `.Map(transform).ToArray()`, `.Map(transform).ToImmutableArray()` | Later result sets are read (`.SingleResult()` keeps the old behaviour). `Map` has no `CommandBehavior` argument: if you passed one, call `cmd.ExecuteReader(r => r.Map(transform).ToList(), behavior \| CommandBehavior.SingleResult)`. |
+| `IterateReader(transform, selector, behavior)` on an `IExecuteReader` | `.Map(transform).Read(selector)` | Later result sets are read (`.SingleResult()` keeps the old behaviour). If you passed a `CommandBehavior`, call `cmd.ExecuteReader(r => selector(r.Select(transform)), behavior \| CommandBehavior.SingleResult)`. |
+| `ToListAsync(transform)` on a `DbCommand` (three overloads, with a synchronous transform) | `await command.Map(transform).ToListAsync(cancellationToken)` | Later result sets are read (`.SingleResult()` keeps the old behaviour). `Map` executes asynchronously and then reads the rows with `Read()`, as `useReadAsync: false` did. It has no `CommandBehavior` argument: if you passed one, call `command.ExecuteReaderAsync(r => r.Map(transform).ToList(), behavior \| CommandBehavior.SingleResult, cancellationToken)`. The `IDbCommand` and `DbCommand` selector methods that are not listed here are not deprecated. |
+| `To(DataTable, map, clearSourceTable)` and `Transformer.Results(DataTable, bool)` | `table.CreateDataReader().Map(map).ToList()` | Returns a `List`, not a lazy enumerable. There is no `clearSourceTable`: clear the table yourself after reading it. |
+| `MappedQueryExtensions.ReadAsync` with the `CancellationToken` first (four overloads) | `query.ReadAsync(handler, cancellationToken)` | The same call with the token last. |
+| Every public type of the `Open.Database.Extensions.SqlClient` package | The same type in `Open.Database.Extensions.MSSqlClient` | The same API on `Microsoft.Data.SqlClient`. |
+| `AddParameterType(this IDbCommand, string, SqlDbType)` (both SQL packages) | Call `AddParameterType` on the `SqlCommand` | The overload casts any `IDbCommand` to `SqlCommand`, so it throws `InvalidCastException` for any other command. |
+| `ExpressiveCommandBase.Concat` (protected) | `remaining is null ? [first] : remaining.Prepend(first)` | `Prepend` alone throws on null. Only a subclass that calls it is affected. |
### Before and after
@@ -152,7 +161,7 @@ The old calls read only the first result set. `Map` reads through the later ones
- The unmapped `ToChannel` overloads can still lose the row that was in flight when a read fails while the target is full. The cause is in the underlying channel library.
- Output parameters can be sent, but their values cannot be read back after the call. Providers other than SQL Server supply no real parameter names, so `AddParam(search.Author)` uses the last member as written there.
- `MappedQuery` has `FirstOrDefault` and `SingleOrDefault` but no throwing `First` or `Single`: check the result for `null`.
-- A missing column for a `required` property is reported by `Map` at the first row, so an empty result does not report it. `DataTable.To()`, `ToChannel` and the Dataflow methods know the columns before any row and report it even when no row follows. (`DequeueAs()` did too; it is deprecated.)
+- A missing column for a `required` property is reported by `Map` at the first row, so an empty result does not report it. `DataTable.To