两种不同的方法来为WHERE子句定界字段

时间:2019-06-20 17:51:21

标签: c# inheritance design-patterns composition

我在代码审查交流中发布了此问题的更详细版本,请选中此link。后来,我的一位同事审查了我的实施情况,他提出了另一种解决问题的方法。所以这个问题是要帮助我找出应该选择两种方法中的哪一种,为什么?

如果此方法不适用于StackOverflow,并且仍应位于CodeReviewExchange中,请告诉我,我将其移到那里。

我也想在开始时就这么说,因为我在较早的帖子中对此有所批评。实际的代码与我在这里提供的代码不同,它更复杂,因此我不得不简化它以询问我要弄清楚的内容,即继承与合成,在这里更好?

这是我的一组课程:

public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
    public virtual bool QuotedValues { get; set; } = true;

    public abstract string OperatorSymbol { get; }

    public string BuildCondition(SearchCondition searchCondition)
    {
        var conditionBuilder = new StringBuilder();

        var context = searchCondition.GetContext<TContext>();

        conditionBuilder.Append(context.FieldId);
        conditionBuilder.Append(OperatorSymbol);
        conditionBuilder.Append(GetValue(context));

        return conditionBuilder.ToString();
    }

    public abstract bool CanHandle(FilterAction filterAction);

    public abstract object GetValue(TContext context);

}

public class TextLikeConditionBuilder : ConditionBuilder<TextContext>
{
    public override string OperatorSymbol => " LIKE ";

    public override bool CanHandle(FilterAction action) => action == FilterAction.TextLike;

    public override object GetValue(TextContext context)
    {
        if (context.Text == null)
        {
            return null;
        }

        return string.Concat("%", context.Text, "%");
    }
}

public class TextEqualsConditionBuilder : ConditionBuilder<TextContext>
{
    public override string OperatorSymbol => " = ";

    public override bool CanHandle(FilterAction action) => action == FilterAction.TextEqual;

    public override object GetValue(TextContext context)
    {
        return context.Text;
    }
}

在默认实现中,类为WHERE构建SQL子句。这些类由其他开发人员使用,可以重写以构建不一定要用于数据库的WHERE子句,例如,可以重写protected virtual StringBuilder GetCondition(SearchFilterCondition filterCondition, TContext context)来为QUERY构建Elasticsearch也。

回到主题,在一个特定的实例中,使用这些类的默认行为,我想对FieldId进行定界,以确保FieldId包含空格和特殊字符时它可以工作字符。这就是我实施解决方案的方式:

public interface IDelimitedIdentifier
{
    string Delimit(string input);
}

internal class SqlServerDelimitedIdentifier : IDelimitedIdentifier
{
    public string Delimit(string input)
    {
        return "[" + input.Replace("]", "]]") + "]";
    }
}

internal class OracleDelimitedIdentifier : IDelimitedIdentifier
{
    public string Delimit(string input)
    {
        return "\"" + input + "\"";
    }
}

public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
{
    public virtual bool QuotedValues { get; set; } = true;

    public abstract string OperatorSymbol { get; }

    public string BuildCondition(SearchCondition searchCondition)
    {
        var conditionBuilder = new StringBuilder();

        var context = searchCondition.GetContext<TContext>();

        conditionBuilder.Append(SanitizeFieldId(context.FieldId));
        conditionBuilder.Append(OperatorSymbol);
        conditionBuilder.Append(GetValue(context));

        return conditionBuilder.ToString();
    }

    public abstract bool CanHandle(FilterAction filterAction);

    public abstract object GetValue(TContext context);

    protected virtual string SanitizeFieldId(string fieldId)
    {
        return _delimitedIdentifier.Delimit(fieldId);
    }

}

public class SanitizedFieldConditionBuilder<TContext> : ConditionBuilder<TContext> where TContext : FieldSearchContextBase
{
    private readonly ConditionBuilder<TContext> _baseConditionBuilder;
    private readonly IDelimitedIdentifier _delimitedIdentifier;

    public SanitizedFieldConditionBuilder(ConditionBuilder<TContext> baseConditionBuilder, IDelimitedIdentifier delimitedIdentifier)
    {
        QuotedValues = false;

        _baseConditionBuilder = baseConditionBuilder;
        _delimitedIdentifier = delimitedIdentifier;
    }

    public override string OperatorSymbol => _baseConditionBuilder.OperatorSymbol;

    public override bool CanHandle(SearchFilterAction action) => _baseConditionBuilder.CanHandle(action);

    public override object GetValue(TContext context) => _baseConditionBuilder.GetValue(context);

    protected override string SanitizeFieldId(string fieldId)
    {
        return _delimitedIdentifier.Delimit(fieldId);
    }
}

public static class ConditionBuilderExtensions
{
    public static SanitizedFieldConditionBuiler<TContext> SanitizeField<TContext>(this ConditionBuilder<TContext> source, IColumnSanitizer columnSanitizer) where TContext : FieldSearchContext
    {
        return new SanitizedFieldConditionBuiler<TContext>(source, columnSanitizer);
    }

    public static ParameterizedConditionBuilder<TContext> WithParameters<TContext>(this ConditionBuilder<TContext> source,
        ParameterCollection parameterCollection, bool parameterizeNullValues = false) where TContext : FieldSearchContext
    {
        return new ParameterizedConditionBuilder<TContext>(source, parameterCollection, parameterizeNullValues);
    }
}

可以如下所示实例化这些类:

class Program
{
  static void Main(string[] args)
  {
   var conditionBuilders = new List<IConditionBuilder>()
        {
            new TextEqualsConditionBuilder().SanitizeField(new SqlServerDelimitedIdentifier()),
            new TextLikeConditionBuilder().SanitizeField(new SqlServerDelimitedIdentifier())
        };
        }
}

我的同事建议使用合成代替扩展。这是按照他的建议讲课的样子。

 public abstract class ConditionBuilder<TContext> : IConditionBuilder where TContext : FieldSearchContext
    {
        private readonly IDelimitedIdentifier DelimitedIdentifier;

        public virtual bool QuotedValues { get; set; } = true;

        public abstract string OperatorSymbol { get; }

        protected ConditionBuilder(IDelimitedIdentifier delimitedIdentifier)
        {
            DelimitedIdentifier = delimitedIdentifier;
        }

        public string BuildCondition(SearchCondition searchCondition)
        {
            var conditionBuilder = new StringBuilder();

            var context = searchCondition.GetContext<TContext>();

            conditionBuilder.Append(DelimitedIdentifier.Delimit(context.FieldId));
            conditionBuilder.Append(OperatorSymbol);
            conditionBuilder.Append(GetValue(context));

            return conditionBuilder.ToString();
        }

        public abstract bool CanHandle(FilterAction filterAction);

        public abstract object GetValue(TContext context);

    }

    public class TextLikeConditionBuilder : ConditionBuilder<TextContext>
    {

        public TextLikeConditionBuilder(IDelimitedIdentifier delimitedIdentifier) : base(delimitedIdentifier)
        {

        }

        public override string OperatorSymbol => " LIKE ";

        public override bool CanHandle(FilterAction action) => action == FilterAction.TextLike;

        public override object GetValue(TextContext context)
        {
            if (context.Text == null)
            {
                return null;
            }

            return string.Concat("%", context.Text, "%");
        }
    }

    public class TextEqualsConditionBuilder : ConditionBuilder<TextContext>
    {

        public TextEqualsConditionBuilder(IDelimitedIdentifier delimitedIdentifier) : base(delimitedIdentifier)
        {

        }

        public override string OperatorSymbol => " = ";

        public override bool CanHandle(FilterAction action) => action == FilterAction.TextEqual;

        public override object GetValue(TextContext context)
        {
            if (context.Text == null)
            {
                return null;
            }

            return context.Text;
        }
    }

以下是我的理由,为什么不应该按照我的同事的建议这样做:

  1. IDelimitedIdentifier是非常特定于数据库的,为什么在可以使用扩展支持功能的情况下将其移至基类中。
    1. 扩展减少了更改的次数,否则添加构造函数将意味着更改所有派生类,让我告诉您有15个奇数派生类
    2. 不是根据开放式封闭原则添加构造函数吗?

编辑:由于很多人在这里关注SQL注入问题,所以我没有添加我要回答的内容,而是添加了照顾它的其他代码。我还更新了上面定义的ConditionBuilderExtensions类。

public class ParameterizedConditionBuilder<TContext> : ConditionBuilder<TContext>
        where TContext : FieldSearchContext
    {
        private readonly ConditionBuilder<TContext> baseConditionBuilder;
        private readonly ParameterCollection parameterCollection;
        private readonly bool parameterizeNullValues;

        public override bool QuotedValues { get; set; } = false;

        public ParameterizedConditionBuilder(ConditionBuilder<TContext> baseConditionBuilder,
            ParameterCollection parameterCollection, bool parameterizeNullValues = false)
        {
            this.baseConditionBuilder = baseConditionBuilder;
            this.parameterCollection = parameterCollection;
            this.parameterizeNullValues = parameterizeNullValues;
        }

        public override bool CanHandle(FilterAction action) => baseConditionBuilder.CanHandle(action);
        public override string OperatorSymbol => baseConditionBuilder.OperatorSymbol;

        public override object GetValue(TContext context)
        {
            object val = baseConditionBuilder.GetValue(context);
            if (val == null && !parameterizeNullValues)
            {
                return null;
            }

            string p = parameterCollection.AddParameter(val);
            return p;
        }
    }

这是我建立条件,然后在DBCommand中使用它们的方式。它不受SQL注入的影响

private static DbCommand CreateDbCommand(Database database, MergeOperation mergeOperation, IList<SearchCondition> searchCondition)
        {
            var whereConditions = new List<string>();

            ParameterCollection paramCollection = new ParameterCollection("{{{0}}}");

            var conditionBuilders = new List<IConditionBuilder>()
            {
                new TextEqualsConditionBuilder().WithParameters(paramCollection).SanitizeField(new SqlSanitizer()),
                new TextLikeConditionBuilder().WithParameters(paramCollection).SanitizeField(new SqlSanitizer())
            };

            foreach (var condition in searchCondition)
            {
                var context = condition.GetContext<FieldSearchContext>();
                var conditionBuilder = conditionBuilders.FirstOrDefault(u => u.CanHandle(condition.FilterAction));
                whereConditions.Add(conditionBuilder.BuildCondition(condition));
            }

            SqlBuilder sqlBuilder = new SqlBuilder();

            sqlBuilder.SELECT("Id");
            sqlBuilder.FROM("Students");
            if (whereConditions.Count > 0)
            {
                sqlBuilder.WHERE(string.Join(Convert.ToString(" " + mergeOperation + " "), whereConditions), paramCollection.GetParameters().Select(pair => pair.Value).ToArray());
            }

            DbExtensions.Database sqlBuilderDb = new DbExtensions.Database(database.CreateConnection());
            IDbCommand sqlBuilderCommand = sqlBuilderDb.CreateCommand(sqlBuilder);

            DbCommand databaseCommand = database.GetSqlStringCommand(sqlBuilderCommand.CommandText);
            if (sqlBuilderCommand.Parameters != null)
            {
                foreach (IDataParameter dataParameter in sqlBuilderCommand.Parameters)
                {
                    database.AddInParameter(databaseCommand, dataParameter.ParameterName, dataParameter.DbType, dataParameter.Value);
                }
            }

            return databaseCommand;
        }

SqlBuilder来自nuget包DBExtensions。我使用Microsoft.Enterprise.Library.Data进行数据库访问。

编辑2:删除了经过SQL注入的代码

0 个答案:

没有答案