web-dev-qa-db-ja.com

SQLをソースコードで記述することはアンチパターンと見なされますか?

SQLを次のようなアプリケーションにハードコードすることは、アンチパターンと見なされますか。

public List<int> getPersonIDs()
{    
    List<int> listPersonIDs = new List<int>();
    using (SqlConnection connection = new SqlConnection(
        ConfigurationManager.ConnectionStrings["Connection"].ConnectionString))
    using (SqlCommand command = new SqlCommand())
    {
        command.CommandText = "select id from Person";
        command.Connection = connection;
        connection.Open();
        SqlDataReader datareader = command.ExecuteReader();
        while (datareader.Read())
        {
            listPersonIDs.Add(Convert.ToInt32(datareader["ID"]));
        }
    }
    return listPersonIDs;
}

通常はリポジトリレイヤーなどがありますが、簡単にするために上記のコードでは除外しています。

最近、SQLがソースコードで記述されていると不平を言っている同僚からいくつかのフィードバックがありました。理由を尋ねる機会がなかったので、彼は2週間(おそらくそれ以上)離れています。私は彼がどちらかを意味していたと思います:

  1. LINQを使用する
    または
  2. SQLのストアドプロシージャを使用する

私は正しいですか? SQLをソースコードで記述することはアンチパターンと見なされますか?私たちはこのプロジェクトに取り組んでいる小さなチームです。ストアドプロシージャの利点は、SQL開発者が開発プロセス(ストアドプロシージャの記述など)に参加できることだと思います。

Edit次のリンクは、ハードコードされたSQLステートメントについて説明しています: https://docs.Microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements 。 SQLステートメントを準備する利点はありますか?

88
w0051977

単純化のために重要な部分を除外しました。リポジトリは永続化のための抽象化レイヤーです。 変更必要なときにより簡単に永続化テクノロジを使用できるように、永続化を独自のレイヤーに分離します。したがって、SQLを永続化レイヤーの外側に置くと、別の永続化レイヤーを用意する手間が完全になくなります。

結果として、SQLは、SQLテクノロジに固有の永続化レイヤー内では問題ありません(たとえば、SQLCustomerRepositoryではSQLは問題ありませんが、MongoCustomerRepositoryでは問題ありません)。永続化レイヤーの外では、SQLは抽象化を壊し、したがって非常に悪い習慣と見なされます(私が)。

LINQやJPQLなどのツールについては、SQLのフレーバーを抽象化するだけで済みます。 LINQ-CodeまたはJPQLクエリをリポジトリの外部に置くと、生のSQLと同じように永続性の抽象化が破られます。


別の永続化レイヤーのもう1つの大きな利点は、DBサーバーをセットアップしなくてもビジネスロジックコードを単体テストできることです。

あなたの言語がサポートするすべてのプラットフォームにわたって再現可能な結果で低メモリプロファイルの高速ユニットテストを取得します。

MVC + Serviceアーキテクチャでは、これはリポジトリインスタンスをモックし、メモリにモックデータを作成し、特定のゲッターが呼び出されたときにリポジトリがそのモックデータを返すように定義するという簡単なタスクです。その後、ユニットテストごとにテストデータを定義でき、後でDBをクリーンアップする必要はありません。

DBへの書き込みのテストは簡単です。永続化レイヤーの関連する更新メソッドが呼び出されていることを確認し、それが発生したときにエンティティが正しい状態にあったことをアサートします。

111
marstato

今日のほとんどの標準的なビジネスアプリケーションは、さまざまな責任を持つさまざまなレイヤーを使用しています。ただし、yourアプリケーションで使用するwhichレイヤー、およびどのレイヤーがどの責任を持つかは、ユーザーとチームの責任です。 SQLを関数に直接配置することが正しいか間違っているかを判断する前に、知っておく必要があることを示した

  • アプリケーションのどの層にどの責任があるか

  • 上記の関数の元になるレイヤー

これに対する「万能」ソリューションはありません。一部のアプリケーションでは、設計者はORMフレームワークを使用し、フレームワークにすべてのSQLを生成させます。一部のアプリケーションでは、設計者はそのようなSQLをストアドプロシージャにのみ格納することを好みます。一部のアプリケーションには、SQLが存在する手書きの永続性(またはリポジトリ)レイヤーがあり、他のアプリケーションでは、特定の状況下でその永続性レイヤーにSQLを厳密に配置することから例外を定義してもかまいません。

だからあなたが考える必要があるのは、あなたの特定のアプリケーションであなたはどの層wantまたはneedをしているか、そしてyoはどのように責任を求めているのですか? 「通常はリポジトリレイヤーがあります」と書きましたが、そのレイヤー内にどのような責任を持たせたいのか、他の場所にどのような責任を持たせたいのですか?最初にその質問に答えてから、自分で質問に答えることができます。

55
Doc Brown

Marstatoは良い答えを出しますが、もう少しコメントを追加したいと思います。

ソース内のSQLはアンチパターンではありませんが、問題を引き起こす可能性があります。すべてのフォームにドロップされたコンポーネントのプロパティにSQLクエリを入力する必要があったときのことを覚えています。これにより、物事は非常に醜く、非常に速くなり、クエリを見つけるためにフープを飛び越える必要がありました。私は、使用している言語の制限内でデータベースアクセスをできる限り集中化することを強く主張しました同僚がフラッシュバックを取得している可能性がありますこれらの暗い日。

さて、コメントのいくつかは、それが自動的に悪いことであるようにベンダーロックインについて話している。そうではありません。 Oracleを使用するために毎年6桁のチェックに署名している場合、そのデータベースにアクセスするすべてのアプリケーションで追加のOracle構文を適切に使用する必要があることに間違いはありませんしかし、最大限に。データベースを壊さないSQLを書く「Oracleの方法」があるとき、私の光沢のあるデータベースがバニラANSI SQLをひどく書くプログラマーによって不自由にされているなら、私は幸せではありません。はい、データベースの変更はより困難になりますが、大きなクライアントサイトで20年以上の間に数回しか行われていないことを確認しただけで、DB2をホストしていたメインフレームが廃止され、廃止されたため、それらのケースの1つがDB2-> Oracleから移行しました。 。はい、それはベンダーロックインですが、企業顧客にとっては、OracleやMicrosoft SQL Serverのような高価な有能なRDBMSにお金を払い、それを最大限に活用することが実際に望ましいです。あなたは快適な毛布としてサポート契約を結んでいます。ストアドプロシージャが豊富に実装されたデータベースにお金を払っているのであれば、それが理にかなった場合に使用したいと思います。

これは次のポイントにつながります。SQLデータベースにアクセスするアプリケーションを作成している場合SQLを学習する必要がありますと他の言語、そして学習とはクエリの最適化も意味します。 1つの巧妙にパラメーター化されたクエリを使用できたときに、ほぼ同じクエリの弾幕でSQLキャッシュをフラッシュするSQL生成コードを記述した場合、私はあなたに腹を立てます。

言い訳も、Hibernateの壁の後ろに隠れることもありません。不適切に使用されたORMはreallyアプリケーションのパフォーマンスを低下させる可能性があります。 Stack Overflowで数年前に次のような質問があったことを覚えています。

Hibernateでは、250,000件のレコードを繰り返し処理して、いくつかのプロパティの値をチェックし、特定の条件に一致するオブジェクトを更新しています。少し遅いのですが、どうすればスピードアップできますか?

「UPDATE table SET field1 = where field2 is True and Field3> 100」についてはどうでしょう。 250,000個のオブジェクトの作成と破棄が問題になる可能性があります...

つまり、使用するのが適切でない場合はHibernateを無視します。データベースを理解します。

したがって、要約すると、SQLをコードに埋め込むことは悪い習慣になる可能性がありますが、SQLの埋め込みを回避しようとすると、結局ははるかに悪いことが起こり得ます。

33
mcottle

場合によります。機能するさまざまなアプローチがいくつかあります。

  1. SQLをモジュール化し、別のクラス、関数、またはパラダイムが使用する抽象化の単位に分離し、アプリケーションロジックでSQLを呼び出します。
  2. すべての複雑なSQLをビューに移動し、アプリケーションロジックで非常に単純なSQLのみを実行するため、何もモジュール化する必要がありません。
  3. オブジェクトリレーショナルマッピングライブラリを使用します。
  4. YAGNI、アプリケーションロジックで直接SQLを記述します。

よくあることですが、プロジェクトでこれらの手法のいずれかをすでに選択している場合は、プロジェクトの残りの部分と一貫している必要があります。

(1)と(3)はどちらも、アプリケーションロジックとデータベース間の独立性を維持するのに比較的優れています。つまり、データベースを別のベンダーに置き換えても、アプリケーションは引き続きコンパイルして基本的なスモークテストに合格します。ただし、ほとんどのベンダーはSQL標準に完全に準拠していないため、使用する手法に関係なく、ベンダーを他のベンダーに置き換えるには、広範囲にわたるテストとバグハンティングが必要になる可能性があります。これは、人々が考えているほど大きな問題であることに懐疑的です。データベースを変更することは、基本的に、現在のデータベースをニーズに合わせることができない場合の最後の手段です。その場合は、データベースの選択が適切ではない可能性があります。

(1)と(3)のどちらを選択するかは、主にORMがどれだけ好きかという問題です。私の意見では、それらは使いすぎです。オブジェクトと同じように行にIDがないため、リレーショナルデータモデルの表現としては不十分です。一意の制約や結合の問題に遭遇する可能性が高く、ORMの能力によっては、さらに複雑なクエリを表現するのが難しい場合があります。一方、(1)はおそらくORMよりもはるかに多くのコードを必要とします。

(2)私の経験ではめったに見られません。問題は、多くのショップがSWEがデータベーススキーマを直接変更することを禁止していることです(「それはDBAの仕事です」)。これは必ずしもそれ自体が悪いことではありません。スキーマの変更は、物事を壊す大きな可能性があり、慎重に展開する必要がある場合があります。ただし、(2)が機能するためには、SWEは少なくとも新しいビューを導入し、官僚主義を最小限またはまったく行わずに既存のビューのバッキングクエリを変更できる必要があります。これがあなたの勤務先で当てはまらない場合、(2)多分あなたのために働かないでしょう。

一方、(2)が機能する場合は、アプリケーションコードではなくSQLでリレーショナルロジックを保持するため、他のほとんどのソリューションよりもはるかに優れています。汎用プログラミング言語とは異なり、SQLはリレーショナルデータモデル用に特別に設計されているため、複雑なデータクエリや変換の表現に優れています。ビューは、データベースを変更するときに残りのスキーマと一緒に移植することもできますが、そのような移動はより複雑になります。

読み取りの場合、ストアドプロシージャは、基本的には(2)のより悪質なバージョンです。私はそれらをその容量でお勧めしませんが、データベースが 更新可能なビュー をサポートしていない場合、または、一度に1行(トランザクション、読み取り後書き込みなど)。トリガーを使用して、ストアドプロシージャをビューに結合できます(つまり、CREATE TRIGGER trigger_name INSTEAD OF INSERT ON view_name FOR EACH ROW EXECUTE PROCEDURE procedure_name;)ですが、これが実際に良いアイデアかどうかについては、意見がかなり異なります。提案者は、アプリケーションが実行するSQLを可能な限りシンプルに保つことを教えてくれます。批判者は、これは許容できないレベルの「マジック」であり、アプリケーションから直接プロシージャを実行する必要があることを教えてくれます。ストアドプロシージャがINSERTUPDATE、またはDELETEのように見えるまたは機能する場合はこれがより良いアイデアであり、そうしている場合はさらに悪いアイデアだと思います他の何か。最終的には、どのスタイルがより理にかなっているのかを自分で決める必要があります。

(4)は非解決策です。小規模なプロジェクトや、散発的にデータベースと対話するだけの大きなプロジェクトには価値があります。ただし、SQLが多いプロジェクトの場合、同じクエリの重複またはバリエーションが不規則に散らばって読みやすくなり、リファクタリングが妨げられる可能性があるため、お勧めできません。

13
Kevin

SQLをソースコードで記述することはアンチパターンと見なされますか?

必ずしも。ここですべてのコメントを読むと、ソースコードでSQLステートメントをハードコーディングするための有効な引数が見つかります。

問題は、ステートメントを配置する場所で発生します。 SQLステートメントをプロジェクトの至る所に配置すると、SOLID原則として、私たちが通常従うことを目指している原則のいくつかを無視することになります。

彼が意味したと仮定します。どちらか:

1)LINQを使用する

または

2)SQLのストアドプロシージャを使用する

私たちは彼の意味を言うことはできません。ただし、推測は可能です。たとえば、最初に頭に浮かぶのはベンダーロックインです。 SQLステートメントをハードコーディングすると、アプリケーションをDBエンジンに密結合する可能性があります。たとえば、ANSIに準拠していないベンダーの特定の機能を使用します。

これは必ずしも悪いことでも悪いことでもありません。私は事実を指摘しているだけです。

SOLIDの原則とベンダーロックを無視すると、無視する可能性のある悪影響が生じる可能性があります。そのため、通常はチームと一緒に座って疑問を明らかにするのが良いでしょう。

ストアドプロシージャの利点は、SQL開発者が開発プロセス(ストアドプロシージャの記述など)に参加できることだと思います。

ストアドプロシージャの利点とは何の関係もないと思います。さらに、同僚がハードコードされたSQLを嫌っている場合、ビジネスをストアドプロシージャに移行すると、嫌いになる可能性があります。

編集:次のリンクは、ハードコードされたSQLステートメントについて説明しています: https://docs.Microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statements =。 SQLステートメントを準備する利点はありますか?

はい。この投稿では、準備されたステートメントの利点が列挙されています。これは一種のSQLテンプレートです。文字列の連結よりも安全です。しかし、この投稿は、あなたがこのように進むことを奨励したり、あなたが正しいことを確認したりするものではありません。ハードコードされたSQLを安全かつ効率的な方法で使用する方法を説明しているだけです。

要約すると、最初に同僚に尋ねてみてください。メールを送って、彼に電話してください...彼が答えるかどうかにかかわらず、チームと一緒に座って、あなたの疑問を明らかにしてください。要件に最適なソリューションを見つけてください。そこで読んだことに基づいて誤った仮定をしないでください。

8
Laiv

はい、それは悪い習慣だと思います。また、すべてのデータアクセスコードを独自のレイヤーに保持することの利点を指摘する人もいます。探し回る必要はなく、最適化とテストが簡単です...しかし、そのレイヤー内でも、ORMを使用する、sprocsを使用する、SQLクエリを文字列として埋め込むなど、いくつかの選択肢があります。 SQLクエリ文字列は、最悪の選択肢だと私は思います。

ORMを使用すると、開発がはるかに簡単になり、エラーが発生しにくくなります。 EFでは、モデルクラスを作成するだけでスキーマを定義します(とにかくこれらのクラスを作成する必要がありました)。 LINQを使用したクエリは簡単です。2行のc#を使用すると、sprocを作成して維持する必要がある場合があります。 IMO、これは生産性と保守性に関して大きな利点があります-コードが少なく、問題が少ないです。ただし、何をしているのかわかっていても、パフォーマンスのオーバーヘッドがあります。

Sprocs(または関数)は他のオプションです。ここでは、SQLクエリを手動で記述します。しかし、少なくともあなたはそれらが正しいことを保証します。 .NETで作業している場合、SQLが無効な場合でもVisual Studioはコンパイラエラーをスローします。これは素晴らしい。一部の列を変更または削除し、一部のクエリが無効になった場合、少なくともコンパイル時にその列を見つける可能性があります。また、独自のファイルでsprocを保守する方がはるかに簡単です。おそらく、構文の強調表示、autocomplete..etcが表示されます。

クエリがsprocとして保存されている場合は、アプリケーションを再コンパイルして再デプロイすることなく、それらを変更することもできます。 SQLの何かが壊れていることに気付いた場合、DBAはアプリのコードにアクセスする必要なく、問題を修正できます。一般に、クエリが文字列リテラルである場合、dbasはその仕事をほとんど簡単に行うことができません。

文字列リテラルとしてのSQLクエリも、データアクセスのc#コードを読みにくくします。

経験則として、マジック定数は悪いです。これには文字列リテラルが含まれます。

3
Sava B.

これはアンチパターンではありません(この回答は非常に意見に近いものです)。ただし、コードのフォーマットは重要であり、SQL文字列は、それを使用するコードとは明確に区別されるようにフォーマットする必要があります。例えば

    string query = 
    @"SELECT foo, bar
      FROM table
      WHERE id = @tn";
2
rleir

ここにたくさんの素晴らしい答えがあります。すでに述べたこととは別に、もう1つ追加したいと思います。

インラインSQLをアンチパターンとして使用することは考えていません。しかし、私がアンチパターンと考えるのは、すべてのプログラマーが自分のことをやっているということです。チームとして共通の基準で決定する必要があります。全員がlinqを使用している場合はlinqを使用し、全員がビューとストアドプロシージャを使用している場合はそれを実行します。

ただし、常にオープンマインドを保ち、ドグマに陥らないでください。ショップが「linq」ショップの場合は、それを使用します。 linqが完全に機能しない1つの場所を除いて。 (しかし、時間の99.9%をすべきではありません。)

だから私がやろうとしていることは、コードベースのコードを調べて、同僚がどのように機能しているかを確認することです。

1
Pieter B

私はあなたの同僚が何を意味したのかあなたに言うことはできません。しかし、私はこれに答えることができます:

SQLステートメントを準備する利点はありますか?

[〜#〜]はい[〜#〜]。バインドされた変数で準備されたステートメントを使用することは、 SQLインジェクション に対する推奨される防御策です。これは 10年以上Webアプリケーションの単一の最大のセキュリティリスク のままです。この攻撃は非常に一般的で、 ウェブコミック で約10年前に取り上げられました。

...クエリ文字列の不適切な連結に対する最も効果的な防御策は、最初からクエリを文字列として表すのではなく、タイプセーフなクエリAPIを使用してクエリを作成することです。たとえば、ここでは、QueryDSLでJavaにクエリを記述する方法を示します。

List<UUID> ids = select(person.id).from(person).fetch();

ご覧のとおり、ここには単一の文字列リテラルがないため、SQLインジェクションは不可能に近くなっています。さらに、これを書いているときにコード補完があり、IDEは、id列の名前を変更することを選択した場合にリファクタリングできます。また、my IDE person変数がアクセスされる場所です。ああ、それはコードよりもかなり短くて目に優しいことに気づきましたか?

このようなものがC#でも利用可能であるとしか想定できません。たとえば、LINQについて素晴らしいことを聞いています。

要約すると、クエリをSQL文字列として表すと、IDEがクエリの記述とリファクタリングを有意義に支援することが難しくなり、構文エラーと型エラーの検出がコンパイル時から実行時まで延期されます。 SQLインジェクションの脆弱性[1]の原因です。そうです、ソースコードにSQL文字列を含めたくないという正当な理由があります。

[1]:はい、準備済みステートメントを正しく使用すると、SQLインジェクションも防止されます。しかし、このスレッドの別の答えは、コメンターがこれを指摘するまでSQLインジェクションに対して脆弱でした。これは、ジュニアプログラマーが必要なときにいつでもそれらを正しく使用するという自信を刺激しない...

1
meriton

コードの変更を数分でコンパイル、テスト、本番環境にプッシュできる迅速な導入パイプラインを備えた多くの現代の組織にとって、SQLステートメントをアプリケーションコードに埋め込むことは完全に理にかなっています。これは、それをどのように行うかに応じて、必ずしもアンチパターンではありません。

現在ストアドプロシージャを使用する有効なケースは、数週間のQAサイクルなどを伴う可能性のある運用展開に関する多くのプロセスがある組織です。このシナリオでは、DBAチームは、最適化することにより、最初の運用展開の後にのみ発生するパフォーマンスの問題を解決できます。ストアドプロシージャのクエリ。

この状況でない限り、SQLをアプリケーションコードに埋め込むことをお勧めします。このスレッドで他の回答を読んで、最善の方法についてアドバイスを求めてください。


コンテキストの元のテキスト

ストアドプロシージャの主な利点は、アプリケーションを再コンパイルして再デプロイすることなく、データベースのパフォーマンスを最適化できることです。

多くの組織では、コードはQAサイクルなどで数日または数週間かかる場合にのみ本番環境にプッシュできます。使用パターンの変更やテーブルサイズの増加などが原因でシステムでデータベースパフォーマンスの問題が発生した場合は、ハードコードされたクエリで問題を追跡するのは困難ですが、データベースには、問題のあるストアドプロシージャを特定するツール/レポートなどがあります。ストアドプロシージャを使用すると、運用環境でソリューションをテスト/ベンチマークでき、問題を迅速に修正できます。新しいバージョンのアプリケーションソフトウェア。

この問題がシステムで重要でない場合は、SQLステートメントをアプリケーションにハードコーディングすることは完全に有効な決定です。

0
bikeman868

コードは、データベースと残りのコードの間にあるデータベース相互作用レイヤーからのように見えます。もしそうなら、これはnotアンチパターンです。

このメソッドIS OPが異なって考えている(プレーンデータベースアクセス、他のものとの混合はない)場合でも、レイヤーの一部です。これは、コード内に不適切に配置されている可能性があります。このメソッドは、別のクラス、「データベースインターフェイスレイヤー」。非データベースアクティビティを実装するメソッドの横にある通常のクラス内に配置するのは、アンチパターンです。

アンチパターンは、他のランダムなコードの場所からSQLを呼び出すことです。データベースと残りのコードの間にレイヤーを定義する必要がありますが、そこで役立つ一般的なツールを絶対に使用する必要があるわけではありません。

0
h22

私の意見では、noはアンチパターンではありませんが、特に1行に収まらない大きなクエリの場合、文字列のフォーマット間隔を処理することに気づくでしょう。

別の利点は、特定の条件に従って構築する必要がある動的クエリを処理するときに、はるかに難しくなることです。

var query = "select * from accounts";

if(users.IsInRole('admin'))
{
   query += " join secret_balances";
}

sql query builder を使用することをお勧めします

0
amd