ソースコードにSQLを書くことはアンチパターンと見なされますか?


87

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開発者が開発プロセスに関与できることです(ストアドプロシージャの作成など)。

編集 :ハードコーディングされたSQL文に関する次のリンク会談https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/hard-coded-sql-statementsを。SQLステートメントを準備する利点はありますか?


31
「Linqを使用」および「ストアドプロシージャを使用」は理由ではありません。それらは単なる提案です。2週間待ってから、理由を尋ねます。
ロバートハーヴェイ

47
Stack Exchangeネットワークは、Dapperと呼ばれるマイクロORMを使用します。Dapperコードの大部分は「ハードコードされたSQL」(多かれ少なかれ)であると言うのは合理的だと思います。したがって、それが悪い習慣であるなら、それは地球上で最も有名なWebアプリケーションの1つで採用されている悪い習慣です。
ロバートハーヴェイ

16
リポジトリに関する質問に答えるために、ハードコードされたSQLは、どこに置いてもハードコードされたSQLのままです。違いは、リポジトリがハードコードされたSQL をカプセル化する場所を提供することです。これは、プログラムの残りの部分からSQLの詳細を隠す抽象化の層です。
ロバートハーヴェイ

26
いいえ、コード内のSQLはアンチパターンではありません。しかし、これは単純なSQLクエリには非常に多くの定型コードです。
GrandmasterB

45
「ソースコードの上にすべての普及」と「ソースコード中」の違いがあります
tofro

回答:


112

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

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

LINQやJPQLなどのツールに関しては、SQLのフレーバーを単に抽象化するだけで済みます。リポジトリの外部でLINQ-CodeまたはJPQLクエリを使用すると、生のSQLと同様に永続性の抽象化が壊れます。


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

使用する言語がサポートするすべてのプラットフォームで、メモリプロファイルが低く、ユニットテストが高速で、再現可能な結果が得られます。

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

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


80
「永続化テクノロジを変更できる」という考えは、現実世界のプロジェクトの大部分では非現実的であり、不要です。SQL /リレーショナルDBは、MongoDBなどのNoSQL DBとはまったく異なるものです。この詳細な記事を参照してください。せいぜい、NoSQLの使用を開始したプロジェクトは、最終的には間違いを認識し、RDBMSに切り替えます。私の経験では、ビジネスコードからオブジェクト指向クエリ言語(JPA-QLなど)を直接使用できるようにすると、最良の結果が得られます。
ロジェリオ

6
永続化レイヤーが変更される可能性が非常に少ない場合はどうなりますか?永続レイヤーを変更するときに1対1のマッピングがない場合はどうなりますか?余分なレベルの抽象化の利点は何ですか?
嘆願

19
@RogérioNoSQLストアからRDBMSへの移行、またはその逆の移行は、おそらく現実的ではないでしょう(最初はテクノロジーの選択が適切であると仮定)。しかし、私はRDBMSから別のRDBMSに移行した多くの実際のプロジェクトに関与してきました。そのシナリオでは、カプセル化された永続層が間違いなく利点です。
デビッド

6
「「永続化技術を変更できる」という考えは、現実のプロジェクトの大部分で非現実的であり、不必要です。」私の会社は以前、その仮定に沿ってコードを書きました。コードベース全体をリファクタリングして、1つではなく3つのまったく異なるストレージ/クエリシステムをサポートする必要があり、3番目と4番目を検討しています。さらに悪いことに、データベースが1つしかない場合でも、統合の欠如はあらゆる種類のコードの罪につながりました。
NPSF3000

3
@Rogério「実世界のプロジェクトの大部分」を知っていますか?私の会社では、ほとんどのアプリケーションが多くの一般的なDBMSのいずれかで動作します。これは非常に便利です。ほとんどのクライアントにはそれに関する非機能要件があります。
BartoszKP

55

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

  • アプリケーションのどのレイヤーがどの責任を負うか

  • 上記の機能がどのレイヤーからのものか

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

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


1
質問を編集しました。それが光を放つかどうかはわかりません。
w0051977

18
@ w0051977:あなたが投稿したリンクはあなたの申請について何も教えてくれませんよね?SQLを配置するかしないか、すべてのアプリケーションに適合する単純で頭の悪いルールを探しているようです。なにもない。これは、設計上の決定であるあなたがについて確認する必要があり、あなたの個々のアプリケーション(おそらく一緒にあなたのチームと)。
ドックブラウン

7
+1。特に、メソッド内のSQLとストアドプロシージャ内のSQLには、何かが「ハードコード」されているか、再利用可能、保守可能などの点で、実際の違いはないことに注意してください。手順はメソッドで実行できます。もちろんプロシージャは便利ですが、「メソッドにSQLを含めることはできません。すべてをプロシージャに入れましょう」というルーテルールは、ほとんど利益をもたらさずに多くの問題を引き起こすでしょう。

1
@ user82096一般的に言えば、SPにdbアクセスコードを置くことは、私が見たほぼすべてのプロジェクトで有害です。(MSスタック)実際のパフォーマンスの低下(厳密な実行計画のキャッシュ)は別として、ロジックを分割することでシステムのメンテナンスが難しくなり、アプリロジック開発者とは異なる人々によってSPが維持されました。特にAzureでは、展開スロット間のコード変更は数秒の作業に似ていますが、スロット間でのコードファースト/ dbファースト以外のスキーマの移行は運用上の頭痛の種です。
センチネル

33

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

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

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

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

言い訳も、Hibernateの壁に隠れることもありません。使用頻度の低いORMは、アプリケーションのパフォーマンスを実際に損なう可能性あります。数年前にStack Overflowで次のような質問をしたことを覚えています。

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

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

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

したがって、要約すると、SQLをコードに埋め込むことは悪い習慣になりますが、SQLの埋め込みを避けようとすると、さらに悪いことがあります。


5
「ベンダーロックイン」は悪いことだとは言いませんでした。それにはトレードオフが伴うと述べました。xaaSとしてのdbのある今日の市場では、セルフホストdbを実行するためのこのような古い契約とライセンスはますます稀になります。今日では、10年前と比べて、ベンダーAからBにデータベースを変更するのは非常に簡単です。あなたがコメントを読んだ場合、私はデータストレージのあらゆる機能を利用することに賛成ではありません。そのため、特定のベンダーの詳細を、ベンダーに依存しないもの(アプリ)に入れるのは簡単です。コードを単一のデータストレージにロックする目的で、SOLiDをフォローするよう努めています。大したことではない
-Laiv

2
これは素晴らしい答えです。多くの場合、適切なアプローチは、可能な限り最悪のコードが記述されている場合に最も悪い状況につながるアプローチです。最悪の可能性のあるORMコードは、最悪の可能性のある埋め込みSQLよりもはるかに悪い場合があります。
jwg

@Laivの良い点PaaSはちょっとしたゲームチェンジャーです。その意味についてもっと考えなければなりません。
mcottle

3
@jwg平均以上のORMコードは、平均以下の埋め込みSQLよりも悪いと言いたいです:)
mcottle

@macottle平均以下の埋め込みSQLが、ORMを作成する平均よりも高いpplによって作成されたと仮定します。私はそれを非常に疑います。そこにいる多くの開発者は、最初にSOをチェックアウトせずに、左のJOINを書くことができません。
ライヴ

14

はい、SQL文字列をアプリケーションコードにハードコーディングすることは、一般にアンチパターンです。

本番コードでこれを長年にわたって見てきたことから、私たちが開発した許容値を別にしてみましょう。同じファイルに完全に異なる言語と異なる構文を混在させることは、一般的に望ましい開発手法ではありません。これは、複数の言語に文脈上の意味を与えるように設計されたRazorのようなテンプレート言語とは異なります。以下のようサヴァB.は、以下のコメントに言及し、あなたのC#または他のアプリケーション言語(PythonやC ++など)でのSQLは、他のどのような文字列で、意味的に無意味です。ほとんどの場合、複数の言語を混合する場合にも同じことが当てはまりますが、Cのインラインアセンブリ、HTMLのCSSの小さくてわかりやすいスニペット(CSSはHTMLと混合するように設計されていることに注意してください) )、 その他。

Robert C. MartinによるClean Code、pg。 288 (混合言語のロバートC.マーティン、クリーンコード、第17章「コードの匂いとヒューリスティック」、288ページ)

この応答では、SQLに焦点を当てます(質問で尋ねたとおり)。関連付けられていない文字列のアラカルトセットとしてSQLを保存する場合、次の問題が発生する可能性があります。

  • データベースロジックを見つけるのは困難です。すべてのSQLステートメントを見つけるために何を検索しますか?「SELECT」、「UPDATE」、「MERGE」などの文字列?
  • 同じまたは類似のSQLの使用をリファクタリングすることは難しくなります。
  • 他のデータベースのサポートを追加することは困難です。これをどのように達成できますか?各データベースにif..thenステートメントを追加し、すべてのクエリをメソッドに文字列として保存しますか?
  • 開発者は別の言語のステートメントを読み、メソッドの目的からメソッドの実装の詳細(データの取得方法および取得元)へのフォーカスのシフトに気を取られます。
  • ワンライナーはそれほど大きな問題ではないかもしれませんが、ステートメントがより複雑になるにつれて、インラインSQL文字列はバラバラになり始めます。113行のステートメントで何をしますか?メソッドに113行すべてを入れますか?
  • 開発者は、SQLエディター(SSMS、SQL Developerなど)とソースコードの間でクエリを効率的にやり取りする方法を教えてください。C#の@プレフィックスはこれを簡単にしますが、各SQL行を引用して改行をエスケープするコードをたくさん見ました。 "SELECT col1, col2...colN"\ "FROM painfulExample"\ "WHERE maintainability IS NULL"\ "AND modification.effort > @necessary"\
  • SQLを周囲のアプリケーションコードに合わせるために使用されるインデント文字は、実行のたびにネットワーク経由で送信されます。これはおそらく小規模なアプリケーションにとっては重要ではありませんが、ソフトウェアの使用量が増えるにつれて増える可能性があります。

完全なORM(Entity FrameworkやHibernateなどのオブジェクトリレーショナルマッパー)を使用すると、アプリケーションコード内のランダムにペッパーSQLを削除できます。SQLとリソースファイルの私の使用はほんの一例です。ORM、ヘルパークラスなどはすべて、よりクリーンなコードの目標を達成するのに役立ちます。

Kevinが以前の回答で述べたように、コード内のSQLは小さなプロジェクトで受け入れられる可能性がありますが、大規模なプロジェクトは小さなプロジェクトとして始まり、ほとんどのチームが戻って適切に実行される確率は、コードサイズに反比例することがよくあります。

SQLをプロジェクトに保持するための多くの簡単な方法があります。私がよく使用する方法の1つは、通常「sql」という名前のVisual Studioリソースファイルに各SQLステートメントを配置することです。ツールによっては、テキストファイル、JSONドキュメント、またはその他のデータソースが妥当な場合があります。場合によっては、SQL文字列を固定するための専用のクラスが最適なオプションかもしれませんが、上記の問題のいくつかが発生する可能性があります。

SQLの例:どちらがよりエレガントに見えますか?:

using(DbConnection connection = Database.SystemConnection()) {
    var eyesoreSql = @"
    SELECT
        Viewable.ViewId,
        Viewable.HelpText,
        PageSize.Width,
        PageSize.Height,
        Layout.CSSClass,
        PaginationType.GroupingText
    FROM Viewable
    LEFT JOIN PageSize
        ON PageSize.Id = Viewable.PageSizeId
    LEFT JOIN Layout
        ON Layout.Id = Viewable.LayoutId
    LEFT JOIN Theme
        ON Theme.Id = Viewable.ThemeId
    LEFT JOIN PaginationType
        ON PaginationType.Id = Viewable.PaginationTypeId
    LEFT JOIN PaginationMenu
        ON PaginationMenu.Id = Viewable.PaginationMenuId
    WHERE Viewable.Id = @Id
    ";
    var results = connection.Query<int>(eyesoreSql, new { Id });
}

になる

using(DbConnection connection = Database.SystemConnection()) {
    var results = connection.Query<int>(sql.GetViewable, new { Id });
}

SQLは常に見つけやすいファイルまたはファイルのグループ化されたセットにあり、各ファイルには、それがどのように実行されるかを説明する説明的な名前があり、それぞれにアプリケーションコードのフローを中断しないコメント用のスペースがあります:

リソース内のSQL

この単純なメソッドは、単独のクエリを実行します。私の経験では、「外国語」の使用がより洗練されるにつれて、利益は拡大します。 リソースファイルの私の使用は単なる例です。言語(この場合はSQL)とプラットフォームに応じて、異なる方法がより適切な場合があります。

このメソッドおよび他のメソッドは、次の方法で上記のリストを解決します。

  1. データベースコードは既に集中化されているため、簡単に見つけることができます。大規模なプロジェクトでは、like-SQLをおそらくという名前のフォルダーの下の個別のファイルにグループ化しますSQL
  2. 2番目、3番目などのデータベースのサポートは簡単です。各データベースの一意のステートメントを返すインターフェイス(または他の言語の抽象化)を作成します。各データベースのreturn SqlResource.DoTheThing;実装は、次のようなステートメントにすぎません:True、これらの実装はリソースをスキップしてSQLを文字列に含めることができますが、上記の(すべてではない)いくつかの問題が依然として表面化します。
  3. リファクタリングは簡単です-同じリソースを再利用するだけです。いくつかのフォーマットステートメントを使用すれば、多くの場合、さまざまなDBMSシステムに同じリソースエントリを使用できます。私はこれを頻繁に行います。
  4. 二言語の使用は、使用することができますわかりやすい名前などsql.GetOrdersForAccountではなく、多くの鈍角をSELECT ... FROM ... WHERE...
  5. SQLステートメントは、サイズと複雑さに関係なく1行で呼び出されます。
  6. SQLは、SSMSやSQL Developerなどのデータベースツール間で、変更したり慎重にコピーしたりすることなくコピーして貼り付けることができます。引用符なし。末尾のバックスラッシュはありません。特にVisual Studioリソースエディターの場合、1回クリックするとSQLステートメントが強調表示されます。Ctrl + Cキーを押して、SQLエディターに貼り付けます。

リソースでのSQLの作成は迅速であるため、リソースの使用とSQL-in-codeを混在させる推進力はほとんどありません。

選択した方法に関係なく、言語を混在させると通常コードの品質が低下することがわかりました。ここで説明するいくつかの問題と解決策が、開発者が適切なときにこのコードの臭いをなくすのに役立つことを願っています。


13
これは、ソースコードにSQLがあるという悪い批判に焦点を合わせすぎていると思います。同じファイルに2つの異なる言語があることは、必ずしもひどいことではありません。それは、それらがどのように関連し、それぞれがどのように提示されるかなど、多くのことに依存します。
-jwg

9
「まったく同じ言語で完全に異なる言語と異なる構文を混在させる」これはひどい議論です。また、Razorビューエンジン、HTML、CSS、およびJavascriptにも適用されます。グラフィック小説が大好き!
イアンニューソン

4
これにより、複雑さが他のどこかに押しやられます。はい、元のコードはよりクリーンです。ただし、開発者がメンテナンスのためにナビゲートする必要があるインダイレクションを追加することを犠牲にします。これを行う本当の理由は、各SQLステートメントがコード内の複数の場所で使用されている場合です。そのように、他の通常のリファクタリング(「抽出メソッド」)と実際には違いはありません。
ロバートハーヴェイ

3
明確にするために、これは「SQL文字列で何をするか」という質問に対する答えではなく、「十分に複雑な文字列のコレクションで何をするか」という質問に対する答えです。あなたの答えは雄弁に対処します。
ロバートハーヴェイ

2
@RobertHarvey:私たちの経験は異なると確信していますが、私自身は問題の抽象化がほとんどの場合に勝つことを発見しました。長年にわたって行ってきたように、抽象化のトレードオフを確実に改善し、いつかSQLをコードに戻すかもしれません。YMMV。:)マイクロサービスは素晴らしいと思いますし、一般に、SQLの詳細(SQLを使用している場合)をコアアプリケーションコードから切り離しています。「特定の方法を使用する:リソース」を推奨することは少なく、「一般的には、油と水の言語を混ぜないでください」と主張しています。
チャールズバーンズ

13

場合によります。動作できるさまざまなアプローチがいくつかあります。

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

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

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

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

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

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

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

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


書かれている2は、基本的に読み取り専用データベースを想定しています。以前は、ストアドプロシージャは変更するクエリでは珍しくありませんでした。
jpmc26

@ jpmc26:括弧内の書き込みについて説明します...この回答を改善するための具体的な推奨事項はありますか?
ケビン

ふむ 回答を完了する前にコメントし、気が散ってしまうことをおologiesびします。しかし、更新可能なビューでは、テーブルが更新されている場所を追跡するのがかなり難しくなります。メカニズムに関係なく、テーブルの更新を直接制限することには、コードロジックの理解という点で利点があります。ロジックをDBに制約している場合、ストアドプロシージャなしでは強制できません。また、1回の呼び出しで複数の操作を許可するという利点もあります。結論:あなたは彼らにそんなに苦労すべきではないと思います。
jpmc26

@ jpmc26:それは公平です。
ケビン

8

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

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

問題は、ステートメントを配置する場所にあります。SQL文をプロジェクトのいたるところに配置すると、通常私たちが従おうとする固い原則のいくつかを無視することになります。

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

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を安全で効率的な方法で使用する方法を説明しています。

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


1
ありがとう。「SQLステートメントのハードコーディングにより、アプリケーションをdbエンジンに緊密に結合できる場合があります」の+1。彼がSQLではなくSQL Serverを参照していた場合、つまりdbConnectionではなくSQLConnectionを使用していた場合、私はさまよう。dbCommandではなくSQLCommandおよびdbDataReaderではなくSQLDataReader。
w0051977

いいえ。ANSIに準拠していない式の使用について言及していました。たとえば、select GETDATE(), ....GETDATE()はSqlServerの日付関数です。他のエンジンでは、関数には異なる名前、異なる式があります...-
Laiv

14
「ベンダーロックイン」...人/企業が実際に既存の製品のデータベースを別のRDBMS 移行する頻度。ORMのみを使用するプロジェクトでさえ、私はそれが起こるのを見たことはありません。通常、ソフトウェアは最初から書き直されます。その間にニーズが変わったからです。したがって、これについて考えることは、私にとって「時期尚早な最適化」に非常に似ています。はい、それは完全に主観的です。
オリビエグレゴワール

1
どのくらいの頻度で発生するかは気にしません。私は気にしますそれは起こるかもしれません。グリーンフィールドプロジェクトでは、dbから抽象化されたDALを実装するコストはほぼ0です。可能な移行はそうではありません。ORMに対する議論は非常に弱いです。ORMは、Hibernateがかなりグリーンだった15年前とは異なります。私が見たのは、多くの「デフォルト」構成と、非常に悪いデータモデル設計です。他の多くのツールと同様に、多くの人々が「入門」チュートリアルを行い、内部で何が起こるかを気にしません。このツールは問題ではありません。問題は、それをいつどのように使用するかわからない人にあります。
ライヴ

一方、ベンダーのロックインは必ずしも悪いことではありません。私が求めていた場合、私は言うだろう、私は好きではありません。ただし、すでにSpring、Tomcat、JBoss、またはWebsphereにロックされています(さらに悪いことに)。私が避けることができるもの、私はします。私はそれと一緒に生きることができないもの。それは好みの問題です。
ライヴ

3

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

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

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

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

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

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


DBAがアプリケーションを更新せずにSQLを変更できるようにすることは、アプリケーションを2つの個別に開発され、個別にデプロイされたエンティティに効果的に分割することです。これらの間のインターフェイスコントラクトを管理し、相互依存する変更のリリースを同期する必要があります。これは良いことでも悪いことでもありますが、「すべてのSQLを1か所に置く」だけではありません。 -アーキテクチャの決定に達する。
IMSoP

2

これはアンチパターンではありません(この答えは危険なほど意見に近いです)。ただし、コードの書式設定は重要です。SQL文字列は、それを使用するコードから明確に分離されるように書式設定する必要があります。例えば

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

7
これに関する最も明白な問題は、その値42がどこから来るのかという問題です。その値を文字列に連結していますか?もしそうなら、それはどこから来たのですか?SQLインジェクション攻撃を軽減するためにどのようにスクラブされましたか?この例にパラメーター化されたクエリが表示されないのはなぜですか?
クレイグ

フォーマットを表示するだけでした。申し訳ありませんが、パラメータ化するのを忘れていました。msdn.microsoft.com/library/bb738521(v=vs.100).aspx
rleir

書式設定の重要性のために+1。ただし、SQL文字列は書式設定に関係なくコードの匂いだと主張しています。
チャールズバーンズ

1
ORMの使用を主張していますか?ORMが使用されていない場合、小規模なプロジェクトでは、埋め込みSQLを含む「shim」クラスを使用します。
-rleir

SQL文字列は、間違いなくコードの匂いではありません。ORMがなければ、管理者およびアーキテクトとして、メンテナンスとパフォーマンスの両方の理由からSPを推奨します。
センチネル

1

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

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

はい。変数をバインドした準備済みステートメントを使用することは、SQLインジェクションに対する推奨される防御策です。SQLインジェクション、10年以上にわたってWebアプリケーションにとって唯一の最大のセキュリティリスクです。この攻撃は非常に一般的であり、ほぼ10年前にWebコミックで取り上げられ、効果的な防御策が展開されたのは非常に早い時期です。

...そして、クエリ文字列の不適切な連結に対する最も効果的な防御は、そもそも文字列としてクエリを表すのではなく、タイプセーフクエリAPIを使用してクエリを構築することです。たとえば、QueryDSLを使用してJavaでクエリを記述する方法は次のとおりです。

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

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

このようなものはC#でも同様に利用できると仮定することができます。たとえば、LINQについて素晴らしいことを聞きます。

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

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


明確にするために:準備済みステートメントを使用しても、SQLインジェクションは妨げられません。バインドされた変数で準備されたステートメントを使用することはできません。信頼できないデータから作成された準備済みステートメントを使用することができます。
アンディレスター

1

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

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

ただし、常に心を開いて、教義にとらわれないでください。ショップが「linq」ショップの場合は、それを使用します。linqがまったく機能しない1つの場所を除きます。(しかし、99.9%の時間はすべきではありません。)

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


+1良い点。サポート性は他のほとんどの考慮事項よりも重要ですので、あまり賢くはいけません:)とはいえ、ORMショップの場合、ORMが答えではない場合があり、直接SQLを記述する必要があることを忘れないでください。時期尚早に最適化しないでください。ただし、このルートを下る必要がある場合は、重要なアプリケーションでは時間がかかることがあります。
mcottle

0

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

OPの考え方が異なっていても、この方法はレイヤーの一部です(単純なデータベースアクセス、他のものとの混合はありません)。コード内にひどく配置されているだけかもしれません。このメソッドは、別のクラス「データベースインターフェースレイヤー」に属します。非データベースアクティビティを実装するメソッドの隣の通常のクラス内に配置するのはアンチパターンです。

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


0

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

別の長所は、特定の条件に従って構築する必要がある動的クエリを処理する場合に、はるかに困難になることです。

var query = "select * from accounts";

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

SQLクエリビルダーを使用することをお勧めします


単に速記を使用しているかもしれませんが、不要なフィールド(帯域幅!!)を戻すため、盲目的に "SELECT *"を使用することは本当に悪い考えです。それは非常に滑りやすい斜面を下って条件付きの「WHERE」句につながり、それがパフォーマンスの地獄に直接つながる道です。
mcottle

0

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

今日、ストアドプロシージャを使用する有効なケースは、数週間のQAサイクルなどを伴う可能性のある実稼働展開に多くのプロセスがある組織です。このシナリオでは、DBAチームは、ストアドプロシージャのクエリ。

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


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

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

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

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


間違った...........
センチネル

あなたのコメントは誰かに役立つと思いますか?
bikeman868

これは単純に正しくありません。データアクセスとしてEF、ストレージとしてAzure Sqlを使用するWeb API2プロジェクトがあるとします。いいえ、EFを削除して手作りのSQLを使用しましょう。dbスキーマはSSDT SQL Serverプロジェクトに保存されます。dbスキーマの移行には、テストに変更をプッシュする前に、コードベースがdbスキーマに同期され、完全にテストされていることを確認することが含まれますAzureのdb.Aコードの変更には、App Serviceデプロイスロットへのプッシュが含まれ、2秒かかります。さらに、手作りのSQLには、逆に「知恵」があるにもかかわらず、一般的にはストアドプロシージャよりもパフォーマンスが優れています。
センチネル

あなたの場合、展開には数秒かかるかもしれませんが、これは多くの人には当てはまりません。ストアドプロシージャの方が優れているとは言いませんでしたが、状況によっては利点があるというだけです。私の最後の声明は、これらの状況が当てはまらない場合、アプリケーションにSQLを置くことは非常に有効であるということです。これはあなたが言っていることとどのように矛盾しますか?
bikeman868

ストアドプロシージャは手作りのSQLです。
bikeman868
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.