In the UI I have a single search text box into which users can type a query. The query will split the string and compare each word to each configured column in the database and return the matches.
The following code works, but I don't know if there is a better way that I'm missing.
/// <summary>
/// Returns a list of users, either active, inactive or all
/// and can perform a search for specific users
/// </summary>
/// <param name="status">1 = active 2 = inactive 3 = all</param>
/// <param name="query">string to use for search or blank = no search</param>
/// <returns>List of Users</returns>
public static List<UM_UserData> GetUsers(int status, string query)
{
List<UM_UserData> data = new List<UM_UserData>();
using (MySqlConnection con = new MySqlConnection(Cfg.connectionString))
{
con.Open();
string sql = GetUsersQuery(status, query);
using (MySqlCommand cmd = new MySqlCommand(sql, con))
{
if (status != 3)
{
cmd.Parameters.AddWithValue("@status", status);
}
if(query != "")
{
string[] words = query.Split(' ');
int x = 1;
foreach(string word in words)
{
string param = "@param" + x;
cmd.Parameters.AddWithValue(param, "%" + word + "%");
x++;
}
}
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while(reader.Read())
{
data.Add(new UM_UserData(reader));
}
}
}
}
return data;
}
private static string GetUsersQuery(int status, string query)
{
string sql = "SELECT * FROM rw_db.v_sys_users";
if (status != 3 || query != "")
{
sql += " WHERE 1 = 1 ";
}
if(status != 3)
{
sql += " AND `user_status` = @status ";
}
if(query == "") { return sql; }
string[] words = query.Split(' ');
string[] fields = new string[] { "user_empid", "user_firstname", "user_lastname", "user_username" };
sql += buildSearch(words, fields);
return sql;
}
private static string buildSearch(string[] words, string[] fields)
{
string s = " AND((";
int x = 1;
foreach(string word in words)
{
if(x != 1) { s += "AND("; }
string paramName = " @param" + x +" ";
int y = 1;
foreach(string field in fields)
{
if(y != 1) { s += " OR "; }
s += field + " LIKE " + paramName + " ";
y++;
}
s += " ) ";
x++;
}
s += ")";
return s;
}
Thank you everyone, I appreciate all of yall's advice.
Here is what I ended up with.
public static List<UM_UserData> GetUsers(int status, string search)
{
List<UM_UserData> data = new List<UM_UserData>();
UM_GetUserQuery sqlObj = new UM_GetUserQuery(status, search);
using (MySqlConnection con = new MySqlConnection(Cfg.connectionString))
{
con.Open();
using (MySqlCommand cmd = new MySqlCommand(sqlObj.sqlQuery, con))
{
cmd.Parameters.AddRange(sqlObj.parameters.ToArray());
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
data.Add(new UM_UserData(reader));
}
}
}
}
return data;
}
internal class UM_GetUserQuery
{
#region FIELDS
public string sqlQuery { get; private set; }
public List<MySqlParameter> parameters { get; private set; }
#endregion
#region CONSTRUCTORS
public UM_GetUserQuery(int status, string search)
{
parameters = new List<MySqlParameter>();
BuildQuery(status, search);
}
#endregion
#region PRIVATE METHODS
private void BuildQuery(int status, string search)
{
var builder = new StringBuilder("SELECT * FROM rw_db.v_sys_users");
var condition = new StringBuilder();
// IF STATUS != 3 "All Users"
if(status != 3)
{
// ADD CONDITION FOR STATUS AND ADD PARAMETER TO COLLECTION
condition.Append(" AND `user_status` = @statusParam ");
parameters.Add(new MySqlParameter("@statusParam", status));
}
// IF SEARCH IS NOT EMPTY
if(!string.IsNullOrWhiteSpace(search))
{
string[] words = search.Split(' '); // SPLIT SEARCH USING SINGLE SPACE
string[] fields = new string[] { "user_empid", "user_firstname", "user_lastname", "user_username" };
// CREATE A NEW SEARCH OBJECT
SearchObj searchObj = new SearchObj(words, fields);
// APPEND SEARCH QUERY TO CONDITION
condition.Append(searchObj.sql);
// APPEND SEARCH PARAMETERS TO COLLECTION
parameters.AddRange(searchObj.parameters);
}
// IF CONDITION IS NOT EMPTY
if (condition.Length > 0)
{
// ADD CONDITION TO BUILDER
builder.Append(" WHERE 1=1 ");
builder.Append(condition);
}
// SET BUILDER TO SQL STRING
sqlQuery = builder.ToString();
}
#endregion
}
internal class SearchObj
{
#region FIELDS
public string sql { get; private set; }
public List<MySqlParameter> parameters { get; private set; }
#endregion
#region CONSTRUCTORS
public SearchObj(string[] words, string[] fields, bool useAND = true)
{
parameters = new List<MySqlParameter>();
BuildSearch(words, fields, true);
}
#endregion
#region PRIVATE METHODS
private void BuildSearch(string[] words, string[] fields, bool useAND = true)
{
StringBuilder builder = new StringBuilder();
string AndOR = useAND ? " AND(" : " OR(";
builder.Append(" AND((");
int x = 1;
// LOOP THROUGH EACH SEARCH WORD IN WORDS
foreach (string word in words)
{
// IF NOT FIRST WORD APPEND AND
if (x != 1) { builder.Append(AndOR); }
string paramName = string.Format("@param{0}", x);
parameters.Add(new MySqlParameter(paramName, "%" + word + "%"));
int y = 1;
// LOOP THROUGH EACH FIELD
foreach (string field in fields)
{
// IF NOT FIRST FIELD APPEND OR
if (y != 1) { builder.Append(" OR "); }
builder.Append($" {field} LIKE {paramName} ");
y++;
}
builder.Append(")");
x++;
}
builder.Append(")");
sql = builder.ToString();
}
#endregion
}