我在代码审查交流中发布了此问题的更详细版本,请选中此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;
}
}
以下是我的理由,为什么不应该按照我的同事的建议这样做:
IDelimitedIdentifier
是非常特定于数据库的,为什么在可以使用扩展支持功能的情况下将其移至基类中。
编辑:由于很多人在这里关注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注入的代码