First of all it's a good thing to replace your string parameters by sql parameters, especially if you incorporate user input in it. This prevents so called SQL Injection attacks!
The problem you try to solve looks a lot like a implementation of query object. I would keep all the logic that represents a query inside a query object, in your case, this would be the LogRequest object.
I would then make a (SQL) query translator class that transforms a given query object into a sql representation which you then can execute. In your case this would be the ReadLogs() method.
You could think about setting this up in a more generic way so that it doesn't mind if you want to read the logs or do another kind of query. This gives you a flexible way to define, transform
and execute queries.
In order to accomiplish want you want you will have to get rid of your current filter implementation. You will have to seperate the columnname, operator and operand in seperate properties. The best way is by defining a criterion class.
The purpose of the following examples is to only put you in the right direction. They are not a complete out of the box solution.
BTW I am sorry that this code is in C#, while I should know some VB.NET it takes me more time which I don't have at the moment.
public class Criterion
{
public string PropertyName { get; set; }
public object Value { get; set; }
public CriterionOperator Operator { get; set; }
}
public enum CriterionOperator
{
Equals
}
As my example supports only the equality operator, you can add more to your needs. This may need additional Criterion classes as well.
For holding our queries we need to define some interfaces, enums and abstract classes as well.
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;
}
}
We can now create a SQL Query Translator class which is responsible for transforming a IQuery plus a base select query to a proper SQLCommand.
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");
}
}
}
Take a look at the part:
sqlQuery.Append(GetQueryPartFrom(criterion));
command.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));
If you think this is all to much you can use only this part to replace the string parameters with SQL parameters.
Ok now we have all types available to create your ReadLogs query (in this example the query returns a sequence of strings):
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;
}
}
You could then execute the query as follows:
var query = new ReadLogsQuery();
IEnumerable<string> result = query.Execute();
Like I said before, this is no out of the box solution! For example you could extend the query classes to support sorting, transactions, named queries (stored procedure/functions), more criterion operators (gt, lt, in, etc) or add a beter level of abstractness to the data accessing part.
I hope other people can help you translate it to VB.NET it shouldn't be that hard. If it's necessary I can do this tommorow in the evening. Let me know!
Could you also elaborate some more about your question on passing sqlparameters. What is the problem with it? There is no problem passing for example a array of SQL parameters to antoher function.