Skip to content

[API Proposal]: DbDataReader - span based column name APIs #113741

Closed
@mgravell

Description

@mgravell

Background and motivation

Column names are an inherent part of DB reads. Currently, DbDataReader exposes multiple APIs for discussing column names, the most obvious being:

public abstract class DbDataReader
{
    public abstract string GetName(int ordinal);
    public abstract int GetOrdinal(string name);
}

While it is possible to ignore the column names and rely purely on ordinals, a lot of code - especially ORM code - uses the column names to assert the intent (think "select * from orders where region=@region" - what is the column order?)

API Proposal

Suggestion:

public abstract class DbDataReader
{
    public abstract string GetName(int ordinal);
    public abstract int GetOrdinal(string name);

+    public virtual ReadOnlySpan<char> GetNameSpan(int ordinal) => GetName(ordinal).AsSpan();
+    public virtual int GetOrdinal(ReadOnlySpan<char> name) => GetOrdinal(name.ToString());
}

This would allow implementations to use pooled char[] data for the names, only lazily materializing a string if the GetName API is used. Callers such as ORMs could update to use span-based testing, completely avoiding the string usage.

I would happily update Dapper as a prototype.

API Usage

This is an existing Dapper (ORM) usage, taken from AOT generator output:

 public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span<int> tokens, int columnOffset)
            {
                for (int i = 0; i < tokens.Length; i++)
                {
                    int token = -1;
                    var name = reader.GetName(columnOffset);
                    var type = reader.GetFieldType(columnOffset);
                    switch (NormalizedHash(name))
                    {
                        case 926444256U when NormalizedEquals(name, "id"):
                            token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible
                            break;
                        case 2369371622U when NormalizedEquals(name, "name"):
                            token = type == typeof(string) ? 1 : 4;
                            break;
                        case 4237030186U when NormalizedEquals(name, "birthdate"):
                            token = type == typeof(DateOnly) ? 2 : 5;
                            break;

                    }
                    tokens[i] = token;
                    columnOffset++;

                }
                return null;
            }

The complete change here would be to the line:

- var name = reader.GetName(columnOffset);
+ var name = reader.GetNameSpan(columnOffset);

et voila, zero strings; if the provider supports the usage! obviously this also requires

If in doubt over the .ToString() in the base method: I'll lose GetOrdinal in a heartbeat to get GetNameSpan !

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions