web-dev-qa-db-ja.com

SqlDataReaderフィールドを対応するc#型に(効率的に)変換(キャスト)する方法は?

まず、現在の状況について説明します。データベースからレコードを読み取り、後で使用するためにオブジェクトに入れています。今日、データベースタイプからC#タイプへの変換(キャスト?)に関する質問がありました。

例を見てみましょう:

_namespace Test
{
    using System;
    using System.Data;
    using System.Data.SqlClient;

    public enum MyEnum
    {
        FirstValue = 1,
        SecondValue = 2
    }

    public class MyObject
    {
        private String field_a;
        private Byte field_b;
        private MyEnum field_c;

        public MyObject(Int32 object_id)
        {
            using (SqlConnection connection = new SqlConnection("connection_string"))
            {
                connection.Open();

                using (SqlCommand command = connection.CreateCommand())
                {
                    command.CommandText = "sql_query";

                    using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow))
                    {
                        reader.Read();

                        this.field_a = reader["field_a"];
                        this.field_b = reader["field_b"];
                        this.field_c = reader["field_c"];
                    }
                }
            }
        }
    }
}
_

3つの_this.field_x = reader["field_x"];_呼び出しがCannot implicitly convert type 'object' to 'xxx'. An explicit conversion exists (are you missing a cast?).コンパイラエラーをスローしているため、これは(明らかに)失敗しています。

これを修正するために、私は現在2つの方法を知っています(_field_b_の例を使用しましょう):1つ目はthis.field_b = (Byte) reader["field_b"];であり、 2つ目はthis.field_b = Convert.ToByte(reader["field_b"]);です。

オプション番号1の問題は、キャストが失敗するときにDBNullフィールドが例外をスローすることです(Stringのnull許容型でも)。2番目の問題は、null値を保持しないことです。 (Convert.ToString(DBNull)は_String.Empty_を生成します)、そして列挙型でそれらを使用することもできません。

したがって、インターネットとここStackOverflowでいくつかの検索を行った後、私が思いついたのは次のとおりです。

_public static class Utilities
{
    public static T FromDatabase<T>(Object value) where T: IConvertible
    {
        if (typeof(T).IsEnum == false)
        {
            if (value == null || Convert.IsDBNull(value) == true)
            {
                return default(T);
            }
            else
            {
                return (T) Convert.ChangeType(value, typeof(T));
            }
        }
        else
        {
            if (Enum.IsDefined(typeof(T), value) == false)
            {
                throw new ArgumentOutOfRangeException();
            }

            return (T) Enum.ToObject(typeof(T), value);
        }
    }
}
_

このようにして、私はすべてのケースを処理するべきです

質問は:何か不足していますか? WOMBAT(Waste Of Money、Brain And Time))を実行していますか?それはすべて正しいですか?利益?

29
Albireo

フィールドでnullが許可されている場合は、通常のプリミティブ型を使用しないでください。 C#nullable type および as keyword を使用します。

int? field_a = reader["field_a"] as int?;
string field_b = reader["field_a"] as string;

Null不可のC#型に?を追加すると、null可能になります。 asキーワードを使用すると、オブジェクトを指定した型にキャストしようとします。キャストが失敗した場合(タイプがDBNullの場合と同様)、演算子はnullを返します。

注:asを使用するもう1つの小さな利点は、通常のキャストよりも やや速い であることです。また、間違った型としてキャストしようとするとバグの追跡が難しくなるなどの欠点もあるので、従来のキャストよりも常にasを使用する理由とは見なされません。通常のキャスティングはすでにかなり安価な操作です。

38
Dan Herbert

reader.Get*メソッド ?唯一の厄介なことは、列番号を取得するため、アクセサを GetOrdinal() への呼び出しでラップする必要があることです。

using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow))
{
    reader.Read();

    this.field_a = reader.GetString(reader.GetOrdinal("field_a"));
    this.field_a = reader.GetDouble(reader.GetOrdinal("field_b"));
    //etc
}
12
Sam Holder

これは私が過去にそれに対処した方法です:

    public Nullable<T> GetNullableField<T>(this SqlDataReader reader, Int32 ordinal) where T : struct
    {
        var item = reader[ordinal];

        if (item == null)
        {
            return null;
        }

        if (item == DBNull.Value)
        {
            return null;
        }

        try
        {
            return (T)item;
        }
        catch (InvalidCastException ice)
        {
            throw new InvalidCastException("Data type of Database field does not match the IndexEntry type.", ice);
        }
    }

使用法:

int? myInt = reader.GetNullableField<int>(reader.GetOrdinal("myIntField"));
6
BFree

データ型ごとに1組の拡張メソッドのセットを作成できます。

    public static int? GetNullableInt32(this IDataRecord dr, string fieldName)
    {
        return GetNullableInt32(dr, dr.GetOrdinal(fieldName));
    }

    public static int? GetNullableInt32(this IDataRecord dr, int ordinal)
    {
        return dr.IsDBNull(ordinal) ? null : (int?)dr.GetInt32(ordinal);
    }

これを実装するのは少し面倒ですが、かなり効率的です。 System.Data.DataSetExtensions.dllで、Microsoftは Field<T> method は複数のデータ型を一般的に処理し、DBNullをNullableに変換できます。

実験として、私はかつてDataReadersの同等のメソッドを実装しましたが、Reflectorを使用して、DataSetExtensions(UnboxT)から内部クラスを借りて、実際の型変換を効率的に行いました。その借用したクラスを配布することの合法性について私は確信が持てないので、おそらくコードを共有すべきではありませんが、自分で探すのはかなり簡単です。

4
Joel Mueller

ここに投稿された一般的な処理コードはすばらしいですが、質問のタイトルには「効率的に」という単語が含まれているので、あまり一般的ではありませんが(より効率的)な回答を投稿します。

他の人が言及しているgetXXXメソッドを使用することをお勧めします。 bebopが説明する列番号の問題に対処するために、次のように列挙型を使用します。

enum ReaderFields { Id, Name, PhoneNumber, ... }
int id = sqlDataReader.getInt32((int)readerFields.Id)

これは少し余分なタイピングですが、各列のインデックスを見つけるためにGetOrdinalを呼び出す必要はありません。そして、列名を気にする代わりに、列の位置を気にします。

Null許容列を処理するには、DBNullを確認し、おそらくデフォルト値を提供する必要があります。

string phoneNumber;
if (Convert.IsDBNull(sqlDataReader[(int)readerFields.PhoneNumber]) {
  phoneNumber = string.Empty;
}
else {
  phoneNumber = sqlDataReader.getString((int)readerFields.PhoneNumber);
}
2
Ray