Skip to content

Instantly share code, notes, and snippets.

@thongdoan
Last active June 14, 2017 18:09
Show Gist options
  • Save thongdoan/d5e84ae0a640fe2a3d0ef7ce8083a9a9 to your computer and use it in GitHub Desktop.
Save thongdoan/d5e84ae0a640fe2a3d0ef7ce8083a9a9 to your computer and use it in GitHub Desktop.
Sqlite.Net Delete, Update, Insert helper
//Thanks to https://github.com/praeclarum/sqlite-net
public static class SQLiteExtension
{
public static StatementBuilder<T> CreateStatement<T>(this SQLiteConnection conn)
{
return new StatementBuilder<T>(conn);
}
}
public class StatementBuilder<T>
{
private Expression _where;
private Dictionary<string, object> _values;
private SQLiteConnection _connection;
private TableMapping _table;
public StatementBuilder(SQLiteConnection connection)
{
_connection = connection;
_table = _connection.GetMapping<T>();
_values = new Dictionary<string, object>();
}
private StatementBuilder()
{
}
public StatementBuilder<T> Set(string name, object value)
{
if (value is Expression valueExpr)
{
if (valueExpr.NodeType == ExpressionType.Lambda)
{
var lambda = (LambdaExpression)valueExpr;
value = lambda.Body;
}
else
{
throw new NotSupportedException("value must be a lambda expression");
}
}
_values[name] = value;
return this;
}
public StatementBuilder<T> Set(Expression<Func<T, object>> expr, object value)
{
return Set(GetColumnName(expr), value);
}
public StatementBuilder<T> Set(Expression<Func<T, object>> expr, Expression<Func<T, object>> value)
{
return Set(GetColumnName(expr), value);
}
public StatementBuilder<T> SetRaw(Expression<Func<T, object>> expr, string rawSql, params object[] args)
{
return Set(GetColumnName(expr), new RawSql(rawSql, args));
}
public StatementBuilder<T> UnSet(Expression<Func<T, object>> expr)
{
return UnSet(GetColumnName(expr));
}
public StatementBuilder<T> UnSet(string column)
{
_values.Remove(column);
return this;
}
public StatementBuilder<T> Where(Expression<Func<T, bool>> predExpr)
{
if (predExpr.NodeType == ExpressionType.Lambda)
{
var lambda = (LambdaExpression)predExpr;
var pred = lambda.Body;
AddWhere(pred);
return this;
}
else
{
throw new NotSupportedException("Must be a predicate");
}
}
public StatementBuilder<T> ChangeWhere(Expression<Func<T, bool>> predExpr)
{
_where = predExpr;
return this;
}
public StatementBuilder<T> PrepareForReuse()
{
_where = null;
_values.Clear();
return this;
}
public int ExecuteUpdate()
{
if (_values.Count == 0)
return 0;
var commandText = new StringBuilder("update \"" + _table.TableName + "\" set ");
var args = new List<object>();
foreach (var kv in _values)
{
switch (kv.Value)
{
case Expression expr:
var v = CompileExpr(kv.Value as Expression, args);
commandText.Append("\"" + kv.Key + "\" = " + v.CommandText + ", ");
break;
case RawSql sql:
commandText.Append("\"" + kv.Key + "\" = " + sql.Sql + ", ");
if (args.Count > 0)
args.AddRange(args);
break;
default:
commandText.Append("\"" + kv.Key + "\" = ?, ");
args.Add(kv.Value);
break;
}
}
commandText.Remove(commandText.Length - 2, 2);
if (_where != null)
{
commandText.Append(" where ");
var w = CompileExpr(_where, args);
commandText.Append(w.CommandText);
}
return _connection.Execute(commandText.ToString(), (object[])args.ToArray());
}
public int ExecuteDelete()
{
var args = new List<object>();
var commandText = new StringBuilder("delete from \"" + _table.TableName + "\" ");
if (_where != null)
{
commandText.Append(" where ");
var w = CompileExpr(_where, args);
commandText.Append(w.CommandText);
}
return _connection.Execute(commandText.ToString(), (object[])args.ToArray());
}
public int ExecuteInsert()
{
return ExecuteInsert("insert into");
}
public int ExecuteInsertOrReplace()
{
return ExecuteInsert("insert or replace into");
}
private int ExecuteInsert(string prefix)
{
var commandText = new StringBuilder();
commandText.Append(prefix + " \"" + _table.TableName + "\" ");
var args = new List<object>();
var columns = new StringBuilder(" (");
var values = new StringBuilder(" values (");
foreach (var kv in _values)
{
columns.Append("\"" + kv.Key + "\", ");
values.Append("?, ");
args.Add(kv.Value);
}
columns.Remove(columns.Length - 2, 2);
values.Remove(values.Length - 2, 2);
columns.Append(") ");
values.Append(")");
commandText.Append(columns);
commandText.Append(values);
return _connection.Execute(commandText.ToString(), (object[])args.ToArray());
}
public StatementBuilder<T> Clone()
{
var s = new StatementBuilder<T>()
{
_connection = _connection,
_table = _table,
_values = new Dictionary<string, object>(_values ?? new Dictionary<string, object>()),
_where = _where
};
return s;
}
private string GetColumnName(Expression<Func<T, object>> predExpr)
{
var propertyName = GetPropertyName(predExpr);
var column = _table.FindColumnWithPropertyName(propertyName);
if (column == null)
throw new InvalidOperationException("Not found column");
return column.Name;
}
private string GetPropertyName(Expression<Func<T, object>> expr)
{
MemberExpression body = expr.Body as MemberExpression;
if (body == null)
{
UnaryExpression ubody = (UnaryExpression)expr.Body;
body = ubody.Operand as MemberExpression;
}
if (body == null)
throw new NotSupportedException("expr must be MemberExpression");
return body.Member.Name;
}
private StatementBuilder<T> AddWhere(Expression pred)
{
if (_where == null)
{
_where = pred;
}
else
{
_where = Expression.AndAlso(_where, pred);
}
return this;
}
//This class is copied from Sqlite.Net TableQuery class
class CompileResult
{
public string CommandText { get; set; }
public object Value { get; set; }
}
//This method is copied and modify from Sqlite.Net TableQuery class
private CompileResult CompileExpr(Expression expr, List<object> queryArgs)
{
if (expr == null)
{
throw new NotSupportedException("Expression is NULL");
}
else if (expr is BinaryExpression)
{
var bin = (BinaryExpression)expr;
var leftr = CompileExpr(bin.Left, queryArgs);
var rightr = CompileExpr(bin.Right, queryArgs);
//If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null")
string text;
if (leftr.CommandText == "?" && leftr.Value == null)
text = CompileNullBinaryExpression(bin, rightr);
else if (rightr.CommandText == "?" && rightr.Value == null)
text = CompileNullBinaryExpression(bin, leftr);
else
text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")";
return new CompileResult { CommandText = text };
}
else if (expr.NodeType == ExpressionType.Call)
{
var call = (MethodCallExpression)expr;
var args = new CompileResult[call.Arguments.Count];
var obj = call.Object != null ? CompileExpr(call.Object, queryArgs) : null;
for (var i = 0; i < args.Length; i++)
{
args[i] = CompileExpr(call.Arguments[i], queryArgs);
}
var sqlCall = "";
if (call.Method.Name == "Like" && args.Length == 2)
{
sqlCall = "(" + args[0].CommandText + " like " + args[1].CommandText + ")";
}
else if (call.Method.Name == "Contains" && args.Length == 2)
{
sqlCall = "(" + args[1].CommandText + " in " + args[0].CommandText + ")";
}
else if (call.Method.Name == "Contains" && args.Length == 1)
{
if (call.Object != null && call.Object.Type == typeof(string))
{
sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + " || '%'))";
}
else
{
sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")";
}
}
else if (call.Method.Name == "StartsWith" && args.Length == 1)
{
sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))";
}
else if (call.Method.Name == "EndsWith" && args.Length == 1)
{
sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))";
}
else if (call.Method.Name == "Equals" && args.Length == 1)
{
sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))";
}
else if (call.Method.Name == "ToLower")
{
sqlCall = "(lower(" + obj.CommandText + "))";
}
else if (call.Method.Name == "ToUpper")
{
sqlCall = "(upper(" + obj.CommandText + "))";
}
else
{
sqlCall = call.Method.Name.ToLower() + "(" + string.Join(",", args.Select(a => a.CommandText).ToArray()) + ")";
}
return new CompileResult { CommandText = sqlCall };
}
else if (expr.NodeType == ExpressionType.Constant)
{
var c = (ConstantExpression)expr;
queryArgs.Add(c.Value);
return new CompileResult
{
CommandText = "?",
Value = c.Value
};
}
else if (expr.NodeType == ExpressionType.Convert)
{
var u = (UnaryExpression)expr;
var ty = u.Type;
var valr = CompileExpr(u.Operand, queryArgs);
return new CompileResult
{
CommandText = valr.CommandText,
Value = valr.Value != null ? ConvertTo(valr.Value, ty) : null
};
}
else if (expr.NodeType == ExpressionType.MemberAccess)
{
var mem = (MemberExpression)expr;
if (mem.Expression != null && mem.Expression.NodeType == ExpressionType.Parameter)
{
//
// This is a column of our table, output just the column name
// Need to translate it if that column name is mapped
//
var columnName = _table.FindColumnWithPropertyName(mem.Member.Name).Name;
return new CompileResult { CommandText = "\"" + columnName + "\"" };
}
else
{
object obj = null;
if (mem.Expression != null)
{
var r = CompileExpr(mem.Expression, queryArgs);
if (r.Value == null)
{
throw new NotSupportedException("Member access failed to compile expression");
}
if (r.CommandText == "?")
{
queryArgs.RemoveAt(queryArgs.Count - 1);
}
obj = r.Value;
}
//
// Get the member value
//
object val = null;
#if !NETFX_CORE
if (mem.Member.MemberType == MemberTypes.Property)
{
#else
if (mem.Member is PropertyInfo) {
#endif
var m = (PropertyInfo)mem.Member;
val = m.GetValue(obj, null);
#if !NETFX_CORE
}
else if (mem.Member.MemberType == MemberTypes.Field)
{
#else
} else if (mem.Member is FieldInfo) {
#endif
#if SILVERLIGHT
val = Expression.Lambda (expr).Compile ().DynamicInvoke ();
#else
var m = (FieldInfo)mem.Member;
val = m.GetValue(obj);
#endif
}
else
{
#if !NETFX_CORE
throw new NotSupportedException("MemberExpr: " + mem.Member.MemberType);
#else
throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType);
#endif
}
//
// Work special magic for enumerables
//
if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is System.Collections.Generic.IEnumerable<byte>))
{
var sb = new System.Text.StringBuilder();
sb.Append("(");
var head = "";
foreach (var a in (System.Collections.IEnumerable)val)
{
queryArgs.Add(a);
sb.Append(head);
sb.Append("?");
head = ",";
}
sb.Append(")");
return new CompileResult
{
CommandText = sb.ToString(),
Value = val
};
}
else
{
queryArgs.Add(val);
return new CompileResult
{
CommandText = "?",
Value = val
};
}
}
}
throw new NotSupportedException("Cannot compile: " + expr.NodeType.ToString());
}
/// <summary>
/// Compiles a BinaryExpression where one of the parameters is null.
/// </summary>
/// <param name="parameter">The non-null parameter</param>
private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter)
{
if (expression.NodeType == ExpressionType.Equal)
return "(" + parameter.CommandText + " is ?)";
else if (expression.NodeType == ExpressionType.NotEqual)
return "(" + parameter.CommandText + " is not ?)";
else
throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString());
}
static object ConvertTo(object obj, Type t)
{
Type nut = Nullable.GetUnderlyingType(t);
if (nut != null)
{
if (obj == null) return null;
return Convert.ChangeType(obj, nut);
}
else
{
return Convert.ChangeType(obj, t);
}
}
string GetSqlName(Expression expr)
{
var n = expr.NodeType;
if (n == ExpressionType.GreaterThan)
return ">";
else if (n == ExpressionType.GreaterThanOrEqual)
{
return ">=";
}
else if (n == ExpressionType.LessThan)
{
return "<";
}
else if (n == ExpressionType.LessThanOrEqual)
{
return "<=";
}
else if (n == ExpressionType.And)
{
return "&";
}
else if (n == ExpressionType.AndAlso)
{
return "and";
}
else if (n == ExpressionType.Or)
{
return "|";
}
else if (n == ExpressionType.OrElse)
{
return "or";
}
else if (n == ExpressionType.Equal)
{
return "=";
}
else if (n == ExpressionType.NotEqual)
{
return "!=";
}
else if (n == ExpressionType.Add)
{
if (expr.Type.IsAssignableFrom(typeof(string)))
return "||";
return "+";
}
else if (n == ExpressionType.Subtract)
{
return "-";
}
else if (n == ExpressionType.Multiply)
{
return "*";
}
else if (n == ExpressionType.Divide)
{
return "/";
}
else if (n == ExpressionType.Modulo)
{
return "%";
}
else if (n == ExpressionType.LeftShift)
{
return "<<";
}
else if (n == ExpressionType.RightShift)
{
return ">>";
}
else
{
throw new NotSupportedException("Cannot get SQL for: " + n);
}
}
private class RawSql
{
public string Sql { get; private set; }
public object[] Args { get; set; }
public RawSql(string value, object[] args)
{
this.Sql = value;
this.Args = args;
}
}
}
@thongdoan
Copy link
Author

thongdoan commented May 31, 2017

Example:

        class Foo
        {
            [PrimaryKey, AutoIncrement]
            public int Id { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string FullName { get; set; } 
            public int Value1 { get; set; }
            public int Value2 { get; set; }
            public int Sum { get; set; }
        }

        public static void DoSomething(SQLiteConnection conn)
        {
            var smt = conn.CreateStatement<Foo>();
            for (int i = 0; i < 10; i++)
            {
                //Insert into Foo(Value1, Value2) Values (10, i)
                smt.PrepareForReuse()
                   .Set(f => f.FirstName, "Foo")
                   .Set(f => f.LastName, i.ToString())
                   .Set(f => f.Value1, 10)
                   .Set(f => f.Value2, i)
                   .ExecuteInsert();
            }

            //Update table Foo: 
            //FullName = FirstName + " " + LastName
            //Sum = value1 + value2 
            conn.CreateStatement<Foo>()
                .Set(f => f.FullName, f => f.FirstName + " " + f.LastName)
                .Set(f => f.Sum, f => f.Value1 + f.Value2)
                .Where(f => f.Id > 3)
                .ExecuteUpdate();

            //Delete records have Sum < 50
            conn.CreateStatement<Foo>()
                .Where(f => f.Sum < 50)
                .ExecuteDelete();
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment