Позволяет ли ServiceStack.OrmLite.JoinSqlBuilder создавать простой запрос
Мне интересно, позволяет ли JoinSqlBuilder из ServiceStack.OrmLite создать следующий простой запрос:
SELECT * FROM Table1 a
INNER JOIN Table2 b ON ...
WHERE a.Column1 = 1 AND (a.Column2 = 2 OR b.Column3 = 3);
Проблема состоит в том, чтобы построить (a.Column2 = 2 OR b.Column3 = 3)
часть. JoinSqlBuilder имеет список методов, таких как Where<T>, And<T>, Or<T>
которые позволяют добавлять условия для запроса.
Например, если я делаю:
builder
.Join(...)
.Where<Table1Poco>(a => a.Column1 == 1)
.And<Table1Poco>(a => a.Column2 == 2)
.Or<Table2Poco>(a => a.Column3 == 3)
...;
Я получу:
... WHERE a.Column1 = 1 AND a.Column2 = 2 OR b.Column3 = 3;
Есть ли способ построить a.Column1 = 1 AND (a.Column2 = 2 OR b.Column3 = 3)
с ServiceStack.OrmLite?
Я знаю, что могу сделать это с помощью raw sql, но это не вариант, так как я не хочу терять безопасность типов и независимость от диалектов.
1 ответ
Я согласен с Кунджи, что это не то, для чего микро-форма хороша. С учетом сказанного я могу думать о 2 возможных вариантах... ни один из которых на самом деле я бы не рекомендовал в качестве решения для полномасштабного ORM (EF или nHibernate). Но, возможно, это поможет найти лучшие варианты.
Вариант 1. Создайте "строку предложения", используя отражение, чтобы сохранить некоторую "безопасность типов". Вам все еще нужно будет написать немного SQL.
пример
var jn = new JoinSqlBuilder<Table1, Table2>();
jn = jn.Join<Table1, Table2>(s => s.Column1, d => d.Field1);
//using ExpressionVisitor because I didn't see a way to allow a Where clause string parameter to be used
//on a JoinSqlBuilder method
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<Table1>();
ev.Where(
SqlHelper.ToSqlField<Table1>(x => x.Column1) + "={0} AND (" +
SqlHelper.ToSqlField<Table1>(x => x.Column2) + "={1} OR " + SqlHelper.ToSqlField<Table2>(x => x.Column3) +
"={2})", "1", "2", "3");
var sql = jn.ToSql() + ev.WhereExpression;
Хелпер Класс
public static class SqlHelper
{
public static string ToSqlField<T>(Expression<Func<T, object>> expression)
{
//This should return something like 'Table1.Column1'
return typeof(T).Name + "." + GetMemberInfo(expression).Name;
}
// Stolen from FluentNHibernate.ReflectionUtility
public static MemberInfo GetMemberInfo<TEntity>(Expression<Func<TEntity, object>> expression)
{
MemberInfo memberInfo = null;
switch (expression.Body.NodeType)
{
case ExpressionType.Convert:
{
var body = (UnaryExpression)expression.Body;
if (body.Operand is MethodCallExpression)
{
memberInfo = ((MethodCallExpression)body.Operand).Method;
}
else if (body.Operand is MemberExpression)
{
memberInfo = ((MemberExpression)body.Operand).Member;
}
}
break;
case ExpressionType.MemberAccess:
memberInfo = ((MemberExpression)expression.Body).Member;
break;
default:
throw new ArgumentException("Unsupported ExpressionType", "expression");
}
if (memberInfo == null) { throw new ArgumentException("Could not locate MemberInfo.", "expression"); }
return memberInfo;
}
}
Вариант 2. Беспорядок / загрязнение ваших классов и отключение префиксов таблиц в ExpressionVisitor, чтобы позволить генерировать правильный SQL. Это полностью взорвется, если 2 класса будут иметь одно и то же свойство и использоваться в предложении Where.
//Modify Table1 to include a reference to Table2
public class Table1
{
public string Column1 { get; set; }
public string Column2 { get; set; }
[ServiceStack.DataAnnotations.Ignore]
public Table2 Table2 { get; set; }
}
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<Table1>();
ev.PrefixFieldWithTableName = false;
var jn = new JoinSqlBuilder<Table1, Table2>();
jn = jn.Join<Table1, Table2>(s => s.Column1, d => d.Field1);
ev.Where(x => x.Column1 == "1");
ev.Where(x => x.Column2 == "2" || ((Table2)x.Table2).Column3 == "3"); //do cast to avoid InvalidOperationException
var sql = jn.ToSql() + ev.WhereExpression;