30

まず、現在の状況を説明させてください。データベースからレコードを読み取り、後で使用するためにそれらをオブジェクトに入れています。今日、データベースの型から 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例を使用しましょう) 。this.field_b = (Byte) reader["field_b"];this.field_b = Convert.ToByte(reader["field_b"]);

オプション番号1の問題はDBNull、キャストが失敗したときにフィールドが例外をスローすることです(null許容型が as であってもString) 。それらも列挙型で。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 ) を行っていますか? それはすべて正しいですか?利益?

4

5 に答える 5

40

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

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

?null 非許容の C# 型にa を追加すると、"null 許容" になります。キーワードを使用するasと、オブジェクトを指定された型にキャストしようとします。キャストが失敗した場合 (型が の場合のようにDBNull)、演算子は を返しますnull

注:を使用するもう 1 つの小さな利点は、通常のキャストよりもわずかに高速asであることです。間違ったタイプとしてキャストしようとするとバグの追跡が難しくなるなど、いくつかの欠点もある可能性があるため、これは常に従来のキャストを使用する理由と見なされるべきではありません。通常のキャストは、すでにかなり安価な操作です。as

于 2010-01-26T18:22:09.557 に答える
13

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
}
于 2010-01-26T18:21:50.190 に答える
7

これは私が過去にそれを扱った方法です:

    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"));
于 2010-01-26T18:52:11.320 に答える
5

データ型ごとに 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 は、複数のデータ型を一般的に処理し、DBNull を Nullable に変換できるField<T>メソッドを使用して、DataSet の同じ問題を解決しました。

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

于 2010-01-26T18:37:13.180 に答える
3

The generic hanlding code posted here is cool, but since the question title includes the word 'efficiently' I will post my less generic but (I hope) more efficient answer.

I suggest you use the getXXX methods that others have mentioned. To deal with the column number problem that bebop talks about, I use an enum, like this:

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

It's a little extra typing, but then you don't need to call GetOrdinal to find the index for each column. And, instead of worrying about column names, you worry about column positions.

To deal with nullable columns, you need to check for DBNull, and perhaps provide a default value:

string phoneNumber;
if (Convert.IsDBNull(sqlDataReader[(int)readerFields.PhoneNumber]) {
  phoneNumber = string.Empty;
}
else {
  phoneNumber = sqlDataReader.getString((int)readerFields.PhoneNumber);
}
于 2010-01-26T19:05:57.980 に答える