0

制約された列ごとにCLR関数を呼び出すだけの「チェック」制約を実装しています。

各CLR関数は、その列に関連付けられたユーザー定義のC#データクラスのインスタンスを構築しようとする1行または2行のコードです。たとえば、「Score」クラスには、構築が失敗した場合(つまり、スコアが有効な範囲外の場合)に意味のあるエラーメッセージをスローするコンストラクターがあります。

まず、そのアプローチについてどう思いますか? 私にとっては、データ型をC#で一元化し、アプリケーション全体で利用できるようにすると同時に、データベース内で同じ制約を適用するため、プログラマー以外の人が行う可能性のあるManagementStudioでの無効な手動編集を防ぎます。これまでのところうまく機能していますが、アセンブリを更新すると制約が無効になり、すべての制約を再チェックする必要があります(これは完全に合理的です)。私はDBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS、すべてのテーブルのデータが有効および無効の制約に対して引き続き有効であることを確認し、エラーがなくなるまで必要に応じて修正を行うために使用します。次に、を介してすべてのテーブルの制約を再度有効にしますALTER TABLE [tablename] WITH CHECK CHECK CONSTRAINT ALLすべてのテーブルのすべてのチェック制約をチェックして再度有効にするT-SQLステートメントはありますか、それともテーブルごとに再度有効にする必要がありますか?

最後に、チェック制約で使用されるCLR関数については、次のいずれかを実行できます。

  1. 各関数にtry/catchを含めて、データ構築エラーをキャッチし、エラーの場合はfalseを返し、成功の場合はtrueを返し、CLRがデータベースエンジンでエラーを発生させないようにします。
  2. try / catchを省略し、インスタンスを作成してtrueを返すだけで、前述の「意味のある」エラーメッセージがデータベースエンジンで発生するようになります。

私の関数はエラーコードなしで単純であり、management studioを使用している人が無効な列編集を行うと、"Value for type X didn't match regular expression '^p[1-9]\d?$'"「制約違反」などの一般的なSQLエラーではなく、CLRから意味のあるメッセージが表示されるため2が好きです。 CLRエラーをSQLServerに渡すことで深刻な悪影響がありますか、それとも制約違反に起因する他の挿入/更新の失敗と同じですか?

4

2 に答える 2

2

たとえば、「Score」クラスには、構築が失敗した場合(つまり、スコアが有効な範囲外の場合)に意味のあるエラーメッセージをスローするコンストラクターがあります。まず、そのアプローチについてどう思いますか?

ctorを呼び出すにはメモリ割り当てが必要であり、比較的コストがかかるため、少し心配です。挿入された行ごとに、ctorを呼び出しています-そしてその副作用のためだけです。

例外も高価です。それらは必要なときに最適ですが、これはctorコンテキストで使用するが、checkコンテキストでは使用しない場合です。

リファクタリングは、チェックがクラスstaticまたはfree関数として存在することにより、両方のコストを削減できます。次に、チェック制約とctorの両方がそれを呼び出すことができます。

class Score {

  private:
   int score;

  public:
   static bool valid( int score ) { 
    return score > 0  ; 
   }
 
    Score( int s ) {
    if( ! valid( s ) ) { 
      throw InvalidParameter();
    }

    score = s;
   }
}   

制約呼び出しScore::valid()を確認します。構築や例外は必要ありません。

もちろん、CLR呼び出しの各行のオーバーヘッドはまだあります。それが許容できるかどうかは、あなたが決定しなければならないことです。

すべてのテーブルのすべてのチェック制約をチェックして再度有効にするT-SQLステートメントはありますか、それともテーブルごとに再度有効にする必要がありますか?

いいえ。ただし、これを実行してコマンドを生成できます。

select 'ALTER TABLE ' || name || ' WITH CHECK CHECK CONSTRAINT ALL;'
 from sys.tables ;

次に、データベースに対して結果セットを実行します。

OPからのコメント:

すべてのデータ型にConstrainedNumberおよびRegexConstrainedStringという基本クラスを使用しています。あなたが提案したように、これら2つのクラスの単純なコンストラクターコードを別のパブリックブールIsValueValidメソッドに簡単に移動できます。

CLRオーバーヘッド(およびメモリ割り当て)は、挿入と更新でのみ発生します。メソッドの単純さと、テーブルの更新が発生する速度を考えると、パフォーマンスへの影響が私のシステムにとって心配することはないと思います。

それでも、ManagementStudioユーザーに提供する情報については例外を設けたいと思っています。エラーをスローしないという「オプション」が得られるので、IsValueValidメソッドが好きです。私のデータ型を使用するアプリケーション内では、インスタンスを作成することで例外を取得できます:)

例外のスローに同意するかどうかはわかりませんが、「持ち帰りのメッセージ」は、問題をパーツに分解することで、支払うパーツを支払うことなく、支払うパーツを選択できるということです。使用しないでください。副作用を得るために呼び出しているだけなので、使用しないctor。そこで、作成とチェックを分解しました。スローをさらに分解できます。

class Score {

  private:
   int score;

  public:
   static bool IsValid( int score ) { 
    return score > 0  ; 
   }

   static checkValid( int score ) {
    if( ! isValid( s ) ) { 
      throw InvalidParameter();
    }

 
    Score( int s ) {
    checkValid( s ) ;    
    score = s;
   }
}   

これで、ユーザーはctorを呼び出して、チェックと可能な例外とコンストラクターを取得し、checkValidを呼び出してチェックと例外を取得するか、isValidを呼び出して有効性を取得し、必要なものだけにランタイムコストを支払うことができます。

于 2009-04-11T19:47:02.173 に答える
-1

いくつかの説明。これらのデータクラスは、プリミティブタイプの1レベル上に設定し、データを制約して意味のあるものにします。

RegexConstrainedString実際、これらはandクラスのすぐ上にありConstrainedNumber<T>ます。ここで、コンストラクターの検証コードを別のメソッドにリファクタリングすることについて話し合っています。

検証コードのリファクタリングの問題は、検証に必要な正規表現がのサブクラスにのみ存在することです。RegexConstrainedStringこれは、各サブクラスに異なるが含まれているためRegexです。これは、検証データがRegexConstrainedStringのコンストラクターでのみ使用可能であり、そのメソッドでは使用できないことを意味します。したがって、検証コードを除外すると、呼び出し元はにアクセスする必要がありますRegex

public class Password: RegexConstrainedString
{
    internal static readonly Regex regex = CreateRegex_CS_SL_EC( @"^[\w!""#\$%&'\(\)\*\+,-\./:;<=>\?@\[\\\]\^_`{}~]{3,20}$" );

    public Password( string value ): base( value.TrimEnd(), regex ) {} //length enforced by regex, so no min/max values specified
    public Password( Password original ): base( original ) {}
    public static explicit operator Password( string value ) {return new Password( value );}
}

したがって、データベースから値を読み取るとき、またはユーザー入力を読み取るとき、PasswordコンストラクターRegexは検証を処理するためにを基本クラスに転送します。もう1つのトリックは、データベースタイプがvarcharではなくcharの場合に、終了文字を自動的にトリミングすることです。そのため、覚えておく必要はありません。とにかく、RegexConstrainedStringのメインコンストラクターは次のようになります。

protected RegexConstrainedString( string value, Regex subclasses_static_regex, int? min_length, int? max_length )
{
    _value      = (value ?? String.Empty);
    if (min_length != null)
        if (_value.Length < min_length)
            throw new Exception( "Value doesn't meet minimum length of " + min_length + " characters." );
    if (max_length != null)
        if (_value.Length > max_length)
            throw new Exception( "Value exceeds maximum length of " + max_length + " characters." );
    value_match = subclasses_static_regex.Match( _value ); //Match.Synchronized( subclasses_static_regex.Match( _value ) );
    if (!value_match.Success)
        throw new Exception( "Invalid value specified (" + _value + "). \nValue must match regex:" + subclasses_static_regex.ToString() );
}

呼び出し元はサブクラスにアクセスする必要があるため、Regex私の最善の策は、サブクラスにメソッドを実装することです。これにより、データが基本クラスのメソッドにIsValueValid転送されます。つまり、次の行をPasswordクラスに追加します。IsValueValidRegexConstrainedString

public static bool IsValueValid( string value ) {return IsValueValid( value.TrimEnd(), regex, min_length, max_length );}

ただし、これは好きではありません。サブクラスのコンストラクターコードを複製しているため、文字列を再度トリミングし、必要に応じて同じ最小/最大長を渡すことを忘れないでください。この要件は、RegexConstrainedStringのすべてのサブクラスに強制されますが、これは私がやりたいことではありません。パスワードのようなこれらのデータクラスは非常に単純です。これは、RegexConstrainedStringがほとんどの作業を処理し、演算子、比較、クローン作成などを実装するためです。

さらに、コードを除外することには他にも複雑な問題があります。一部のデータ型には文字列の特定の要素についてレポートするプロパティがある場合があるため、検証には、インスタンスでの正規表現の一致の実行と保存が含まれます。たとえば、私のSessionIDクラスには、データクラスインスタンスに格納されているMatchから一致したグループを返すTimeStampなどのプロパティが含まれています。肝心なのは、この静的メソッドはまったく異なるコンテキストであるということです。本質的にコンストラクターコンテキストと互換性がないため、コンストラクターはそれを使用できません。そのため、コードをもう一度複製することになります。

つまり...検証コードを複製して静的コンテキストに合わせて微調整し、サブクラスに要件を課すことで検証コードを除外することも、はるかに単純にしてオブジェクトの構築を実行することもできます。文字列とMatch参照のみがインスタンスに格納されるため、割り当てられる相対的な余分なメモリは最小限になります。一致や文字列自体など、他のすべてはとにかく検証によって生成されるため、それを回避する方法はありません。 私は一日中パフォーマンスを心配することができましたが、私の経験はその正しさでした正確さは他の多くの最適化につながることが多いため、より重要です。たとえば、アプリケーションを流れる不適切なフォーマットやサイズのデータ​​について心配する必要はありません。意味のあるデータ型のみが使用されるため、データベースであるかどうかにかかわらず、他の層からアプリケーションへのエントリポイントへの検証が強制されます。またはUI。私の検証コードの99%は、不要になったアーティファクトとして削除されました。最近では、nullをチェックするだけです。ちなみに、この時点に達したので、ヌルを含めることが何十億ドルの間違いだったのかがわかりました。それらは私のシステムには本質的に存在していませんが、私がもうチェックしなければならないのはそれだけのようです。これらのデータ型をフィールドとして持つ複雑なオブジェクトはnullで構築できませんが、プロパティセッターでそれを強制する必要があります。そうしないと、検証コードが必要になることはないため、イライラします...値の変更に応答して実行されるコードのみ。

更新:CLR関数呼び出しを双方向でシミュレートしたところ、すべてのデータが有効な場合、パフォーマンスの違いは1000回の呼び出しあたり1ミリ秒のほんの一部であり、無視できることがわかりました。ただし、パスワードの約半分が無効であり、「インスタンス化」バージョンで例外がスローされると、3桁遅くなります。これは、1000回の呼び出しごとに約1秒余分に相当します。もちろん、テーブル内の複数の列に対して複数のCLR呼び出しが行われるため、違いの大きさは複数になりますが、私のプロジェクトでは3〜5倍になります。それで、コードを非常にシンプルでクリーンに保つためのトレードオフとして、1000回の更新ごとに3〜5秒余分に受け入れることができますか? それは更新レートに依存します。私のアプリケーションが1秒あたり1000の更新を取得している場合、3〜5秒の遅延は壊滅的です。一方、1分または1時間に1000回の更新を取得していた場合は、完全に許容できる可能性があります。 私の状況では、それはかなり受け入れられると言えるので、インスタンス化メソッドを使用して、エラーを通過させると思います。もちろん、このテストでは、SQL Serverにエラーを処理させるのではなく、CLRでエラーを処理しました。エラー情報をSQLServerにマーシャリングしてから、場合によってはアプリケーションに戻すと、処理速度が大幅に低下する可能性があります。実際のテストを行うには、これを完全に実装する必要があると思いますが、この予備テストから、結果がどうなるかはかなり確信しています。

于 2009-04-12T05:20:33.417 に答える