3

新しいアイデアを見つけると、いつもそれに固執し、弱点が見えません。大規模なプロジェクトで新しいアイデアを使用し始めたときに悪いことが起こり、後でそのアイデアが非常に悪く、どのプロジェクトでも使用すべきではないことに気づきました。

そういうわけで、新しいアイデアを持っていて、それを新しい大規模なプロジェクトで使用する準備ができているので、それについてあなたの意見、特に否定的な意見が必要です。


長い間、データベースに直接アクセスする必要があるプロジェクトで、次のブロックを何度も入力したり、コピーして貼り付けたりすることに飽き飽きしていました。

string connectionString = Settings.RetrieveConnectionString(Database.MainSqlDatabase);
using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
    sqlConnection.Open();

    using (SqlCommand getProductQuantities = new SqlCommand("select ProductId, AvailableQuantity from Shop.Product where ShopId = @shopId", sqlConnection))
    {
        getProductQuantities.Parameters.AddWithValue("@shopId", this.Shop.Id);
        using (SqlDataReader dataReader = getProductQuantities.ExecuteReader())
        {
            while (dataReader.Read())
            {
                yield return new Tuple<int, int>((int)dataReader["ProductId"], Convert.ToInt32(dataReader["AvailableQuantity"]));
            }
        }
    }
}

だから私は、上記と同じことをするためにそのようなものを書くことを可能にする小さなクラスを作りました:

IEnumerable<Tuple<int, int>> quantities = DataAccess<Tuple<int, int>>.ReadManyRows(
    "select ProductId, AvailableQuantity from Shop.Product where ShopId = @shopId",
    new Dictionary<string, object> { { "@shopId", this.Shop.Id } },
    new DataAccess<string>.Yield(
        dataReader =>
        {
            return new Tuple<int, int>(
                (int)dataReader["ProductId"],
                Convert.ToInt32(dataReader["AvailableQuantity"]);
        }));

2 番目のアプローチは次のとおりです。

  • 短く書くと、

  • 読みやすく(少なくとも私にとっては、実際にははるかに読みにくいと言う人もいるかもしれません)、

  • エラーが発生しにくい (たとえば、最初のケースでは、接続を使用する前に接続を開くのを忘れたり、whileブロックを忘れたりするなど)、

  • インテリセンスの助けを借りてより速く、

  • 特に単純なリクエストの場合、はるかに凝縮されています。

例:

IEnumerable<string> productNames = DataAccess<string>.ReadManyRows(
    "select distinct ProductName from Shop.Product",
    new DataAccess<string>.Yield(dataReader => { return (string)dataReader["ProductName"]; }));

小さなプロジェクトで simple とジェネリックを使用してそのようなことを実装した後ExecuteNonQuery、コードExecuteScalarがはるかに短くなり、保守が容易になったことを嬉しく思います。ReadManyRowsDataAccess<T>.ReadManyRows

私は2つの欠点しか見つけませんでした:

  • 要件の変更によっては、大幅なコード変更が必要になります。たとえば、トランザクションを追加する必要がある場合、通常のSqlCommandアプローチで非常に簡単に行うことができます。代わりに私のアプローチを使用すると、プロジェクト全体をSqlCommands とトランザクションを使用するように書き直す必要があります。

  • コマンド レベルでのわずかな変更により、私のアプローチから標準SqlCommandの s に移行する必要があります。たとえば、1 行のみを照会する場合、このケースを含めるようにクラスを拡張するか、代わりにコードで withをDataAccess直接使用する必要があります。SqlCommandExecuteReader(CommandBehavior.SingleRow)

  • 少しパフォーマンスが低下する可能性があります (正確な指標はまだありません)。

このアプローチの他の弱点は何DataAccess<T>.ReadManyRowsですか?

4

5 に答える 5

2

あなたが達成しようとしていることは素晴らしいです。私は実際にこの種の構文が好きで、かなり柔軟だと思います。ただし、API をより適切に設計する必要があると思います。

コードは読みやすく、ほとんど美しいですが、理解するのは困難です。これは主に、各型の意味を正確に理解していない限り意味をなさないジェネリックが多数あるためです。それらのいくつかを排除するために、可能な限りジェネリック型推論を使用します。そのためには、ジェネリック型ではなくジェネリック メソッドを使用することを検討してください。

いくつかの構文の提案 (私は現在コンパイラを持っていないので、基本的にアイデアです):

辞書の代わりに匿名型を使用する

匿名型を辞書に変換するヘルパーを書くのは簡単ですが、表記法が大幅に改善され、書く必要がなくなると思いますnew Dictionary<string, object>

Tuple.Create を使用する

この静的メソッドは、型を明示的に指定することを避けるために作成されました。

DataReader の厳密な型指定のラッパーを作成する

DataReaderこれにより、あちこちの醜い変換が削除されます。実際、そのラムダにアクセスする必要は本当にありますか?

あなたの例のコードでこれを説明します。アイデアを連鎖させ
てくれたDavid Harknessに敬意を表します。

var tuples = new DataAccess ("select ProductId, AvailableQuantity from Shop.Product where ShopId = @shopId")
    .With (new { shopId = this.Shop.Id }) // map parameters to values
    .ReadMany (row =>
         Tuple.Create (row.Value<int> ("ProductId"), row.Value<int> ("AvailableQuantity"))); 

var strings = new DataAccess ("select distinct ProductName from Shop.Product")
    .ReadMany (row => row.Value<string> ("ProductName")); 

単一行の選択を処理するために拡張されていることもわかります。

var productName = new DataAccess ("select ProductName from Shop.Product where ProductId = @productId")
    .With (new { productId = this.SelectedProductId }) // whatever
    .ReadOne (row => row.Value<string> ("ProductName")); 

Rowこれはクラスのラフ ドラフトです。

class Row {
    DataReader reader;

    public Row (DataReader reader)
    {
        this.reader = reader;
    }

    public T Value<T> (string column)
    {
        return (T) Convert.ChangeType (reader [column], typeof (T));
    }
}

内部でインスタンス化されReadOne、呼び出しが行われ、セレクター ラムダのReadMany基礎となる便利な (そして制限付きの) アクセスが提供されます。DataReader

于 2011-02-06T03:09:14.687 に答える
1

あなたの抽象化アプローチは健全に見えます。余分なメソッド呼び出しによるパフォーマンスの低下は些細なことであり、開発者の時間はCPU時間よりもはるかに高価です。トランザクションまたは単一行の選択を追加する必要がある場合は、ライブラリクラスを拡張できます。ここでは、 Do n'tRepeatYourselfをうまく利用しています。

JdbcTemplateSpring Framework for Javaは、これらのタイプのテンプレートクラスやヘルパーなどを多用しHibernateTemplate、開発者が定型コードを作成する必要をなくします。アイデアは、それを一度よく書いてテストし、何度も再利用することです。

于 2011-02-05T23:39:03.527 に答える
1

私の考え:SQLをコード、文字列に埋め込んでいます(少なくとも構文チェックされたLINQを使用するのではなく、DBMLまたはEDMXマッピングファイルをデータベース構造と同期させておくのに役立ちます)。このように SQL を構文チェックされていないコードに埋め込むと、保守不能なコードが簡単に作成され、後でアプリケーションを壊すような方法でデータベース構造または埋め込み SQL 文字列を変更する可能性があります。SQL を文字列に埋め込むと、見つけにくいバグが発生しやすくなります。これは、論理エラーのあるコードが正しくコンパイルされるためです。これにより、コード ベースに慣れていない開発者は、自分が加えた変更が悪影響や意図しない影響を与えていないという誤った安心感を得る可能性が高くなります。

于 2011-02-05T23:36:38.013 に答える
1

まず第一に、コードをコピーして貼り付けないことを決して謝らないでください。

あなたの抽象化はうまく見えますが、私をもう少し悩ませているのは、あなたが与えた最初の例がSqlConnection必要以上に長く開いているという事実です。

an の使用IEnumerable<T>は優れています。なぜなら、それが消費されるタイミングと場合に実行を延期するからです。ただし、列挙の最後に到達していない限り、接続は開いたままです。

メソッドの実装は、 を介して列挙全体を消費し、ToList()代わりにリストを返すことができます。小さなカスタム列挙子を実装することで、遅延実行をサポートすることもできます。

ただし、これには注意が必要です。列挙中に何らかの魔法を実行する古いコードがないことを確認してください。

于 2011-02-06T01:42:02.430 に答える
0

デリゲートは、このアプローチに慣れていない人がすぐに読んだり理解したりするのを少し難しくしますが、最終的には簡単に理解できるはずです.

保守性の観点からは、最終的にエラーが忍び寄ると、スタック トレースを理解するのが難しくなる可能性があります。主に、このセクションでエラーが発生した場合:

new DataAccess<string>.Yield(
    dataReader =>
    {
        return new Tuple<int, int>(
            (int)dataReader["ProductId"],
            Convert.ToInt32(dataReader["AvailableQuantity"]);
    }));

yield を使用すると、try/catch できる場所にいくつかの制限が課せられます (なぜ catch を使用して try ブロック内に yield return が表示されないのですか? )、これは前のアプローチの問題でもあり、シナリオには関係ない場合があります.

于 2011-02-06T02:41:50.280 に答える