0

オブジェクトのパラメーター (メンバー) を含むクラスLogRequestがあります。
このオブジェクトは、別のクラスDataAccessを使用して SQL クエリに変換されます。
最後に、SQL クエリを実行するクラスSearchがあります。

検索中 :

'Create the request
Dim myRequest As LogRequest = MakeRequest()
'Does the SQL query part
responseList = DataAccess.ReadLogs(myRequest)

私の問題は、LogRequest でWhere部分 (「x=1」、「b=2」、「x と y の間の日付」...) が構築されていることです。

DataAccess で:

' request is a LogRequest object  
For Each filter As String In request.GetFilters()
    sqlfilters.AppendLine(String.Format("{0} {1}", IIf(first, "WHERE", "AND"), filter))
    first = False
Next
sql = String.Format("SELECT * FROM table_x {0} ", sqlfilters)    

これらの文字列を壊して代わりに sqlParameters を使用する方法について何か提案はありますか?

これは設計上の問題ですか?「GetFilters」部分を dataAccess クラスに移動する必要がありますか?

編集:

また、関数から別の関数に sqlParameters を渡す方法についての提案をいただければ幸いです。(コマンドを作成して処理する 2 番目のもの。)

4

2 に答える 2

1

まず、特にユーザー入力を組み込む場合は、文字列パラメーターを sql パラメーターに置き換えることをお勧めします。これにより、いわゆる SQL インジェクション攻撃が防止されます。

解決しようとしている問題は、クエリ オブジェクトの実装によく似ています。クエリ オブジェクト内のクエリを表すすべてのロジックを保持します。あなたの場合、これは LogRequest オブジェクトになります。

次に、特定のクエリ オブジェクトを実行可能な SQL 表現に変換する (SQL) クエリ トランスレータ クラスを作成します。あなたの場合、これは ReadLogs() メソッドになります。ログを読んだり、別の種類のクエリを実行したりしてもかまわないように、より一般的な方法でこれを設定することを考えることができます。これにより、クエリを柔軟に定義、変換、および実行できます。

あなたが望むものを達成するためには、現在のフィルターの実装を取り除く必要があります。個別のプロパティで列名、演算子、およびオペランドを分離する必要があります。最良の方法は、基準クラスを定義することです。

次の例の目的は、正しい方向に導くことだけです。これらは、すぐに使える完全なソリューションではありません。

ところで、このコードが C# であることは申し訳ありませんが、VB.NET を知っている必要がありますが、現時点では時間がかかります。

public class Criterion
{
    public string PropertyName { get; set; }
    public object Value { get; set; }
    public CriterionOperator Operator { get; set; }
}

public enum CriterionOperator
{
    Equals        
}

この例では等価演算子のみがサポートされているため、必要に応じてさらに追加できます。これには、追加の Criterion クラスも必要になる場合があります。

クエリを保持するために、いくつかのインターフェイス、列挙型、および抽象クラスも定義する必要があります。

public enum QueryOperator
{
    And,
    Or
}

public interface IQuery
{
    IEnumerable<Criterion> Criteria { get; }
    QueryOperator QueryOperator { get; set; }
    void Add(Criterion criterion);
}

public interface IQuery<TResult> : IQuery
{
    TResult Execute();
}

public abstract class BaseQuery<TResult> : IQuery<TResult>
{
    private readonly List<Criterion> _criteria = new List<Criterion>();

    public IEnumerable<Criterion> Criteria
    {
        get { return _criteria; }
    }

    public QueryOperator QueryOperator
    {
        get;
        set;
    }

    public void Add(Criterion criterion)
    {
        _criteria.Add(criterion);
    }

    public abstract TResult Execute();
}

public abstract class SqlQuery<TResult> : BaseQuery<TResult>
{
    protected string _baseSelectQuery = String.Empty;

    protected SqlQuery(string baseSelectQuery)
    {
        _baseSelectQuery = baseSelectQuery;
    }
}

これで、IQuery と基本選択クエリを適切な SQLCommand に変換する役割を担う SQL Query Translator クラスを作成できるようになりました。

public static class SqlQueryTranslator
{
    public static void Translate(IQuery query, string baseSelectQuery, SqlCommand command)
    {
        var sqlQuery = new StringBuilder();

        sqlQuery.Append(baseSelectQuery);

        if (query.Criteria.Count() > 0)
        {
            sqlQuery.Append("WHERE ");
        }

        var isNotFirst = false;

        foreach (Criterion criterion in query.Criteria)
        {
            if (isNotFirst)
                sqlQuery.Append(query.QueryOperator == QueryOperator.And ? "AND " : "OR ");

            sqlQuery.Append(GetQueryPartFrom(criterion));

            command.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));

            isNotFirst = true;
        }

        command.CommandType = CommandType.Text;
        command.CommandText = sqlQuery.ToString();
    }

    private static string GetQueryPartFrom(Criterion criterion)
    {
        return string.Format("{0} {1} @{2}",
                             criterion.PropertyName,
                             GetSqlOperatorFor(criterion.Operator),
                             criterion.PropertyName);
    }

    private static string GetSqlOperatorFor(CriterionOperator criterionOperator)
    {
        switch (criterionOperator)
        {
          case CriterionOperator.Equals:
                return "=";
            default:
                throw new ApplicationException("Not supported Operator");
        }
    }
}

次の部分を見てください。

sqlQuery.Append(GetQueryPartFrom(criterion));

command.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));

これが多すぎると思われる場合は、この部分だけを使用して、文字列パラメーターを SQL パラメーターに置き換えることができます。

これで、ReadLogs クエリを作成するために使用できるすべてのタイプが用意されました (この例では、クエリは一連の文字列を返します)。

public class ReadLogsQuery : SqlQuery<IEnumerable<string>>
{
    public ReadLogsQuery(): base("SELECT * FROM table_x ")
    {
        Add(new Criterion() { PropertyName = "x", Operator = CriterionOperator.Equals, Value = 1 });
        Add(new Criterion() { PropertyName = "y", Operator = CriterionOperator.Equals, Value = 2 });
        QueryOperator = QueryOperator.And;
    }

    public override IEnumerable<string> Execute()
    {
        //define result;
        var result = new List<string>();

        using (var conn = new SqlConnection("PUT IN YOU CONNECTION STRING"))
        {
            SqlCommand command = conn.CreateCommand();

            SqlQueryTranslator.Translate(this, _baseSelectQuery, command);

            conn.Open();

            using (SqlDataReader reader = command.ExecuteReader())
            {
                while(reader.Read())
                {
                    //read from the datareader
                    result.Add(reader["colname"].ToString());
                }
            }
        }

        return result;
    }
}

その後、次のようにクエリを実行できます。

var query = new ReadLogsQuery();

IEnumerable<string> result = query.Execute();

前に言ったように、これはすぐに使えるソリューションではありません。たとえば、クエリ クラスを拡張して、並べ替え、トランザクション、名前付きクエリ (ストアド プロシージャ/関数)、より多くの基準演算子 (gt、lt、in など) をサポートしたり、データ アクセス部分により良いレベルの抽象性を追加したりできます。

VB.NET に翻訳するのを他の人が手伝ってくれることを願っています。それほど難しいことではありません。必要があれば、明日の夕方にできます。お知らせ下さい!

また、sqlparameters の受け渡しに関する質問について詳しく説明していただけますか。それの何が問題なのですか?たとえば、SQL パラメータの配列を別の関数に渡すことに問題はありません。

于 2011-10-19T18:28:05.383 に答える