IQueryable< T> .Where()不创建正确的SQL [英] IQueryable<T>.Where() doesn't create proper SQL

查看:115
本文介绍了IQueryable< T> .Where()不创建正确的SQL的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个简单的代码:

  public class AirportRepository:Repository< 
{
public IQueryable< Airport> GetByCountry(Entity country)
{
IQueryable< Airport> airports = GetAll()。Where(a => a.CountryId.Equals(country.Id));

返回机场;
}
}

em>将创建以下SQL:

  SELECT * FROM Airport WHERE CountryID =? 

...但它会创建以下SQL:

  SELECT * FROM Airport 

...然后在客户端执行Where()部分,这在我的情况下是非常昂贵的。



发生了什么? >

UPDATE:这里是基本Repository类的样子:

  public abstract class Repository< T> :IRepository< T>其中T:Entity,new()
{
protected SimpleSQLManager SQLManager = DatabaseManager.Instance.SQLManager;

public IQueryable< T> GetAll()
{
IQueryable< T> all = SQLManager.Table< T>()。AsQueryable();

return all;
}
}

UPDATE#2:我不能共享代码后面SQLManager,但是通过其代码挖掘,我可以看到,表是一个IEnumerable。这是问题吗?



UPDATE#3:这里是相关的(我希望)SQLManager代码:

  ///< summary> 
///返回由给定类型表示的表的可查询接口。
///< / summary>
///< return>
///一个可查询的对象,能够翻译Where,OrderBy和Take
///查询到本地SQL。
///< / returns>
public TableQuery< T> Table< T>()其中T:new()
{
Initialize(false);

return _db.Table< T>();
}

public class TableQuery< T> :IEnumerable< T>其中T:new()
{
public SQLiteConnection Connection {get;私人集合}

public TableMapping Table {get;私人集}

表达式_where;
List< Ordering> _orderBys;
int? _限制;
int? _抵消;

类排序
{
public string ColumnName {get;组; }

public bool Ascending {get;组; }
}

TableQuery(SQLiteConnection conn,TableMapping table)
{
Connection = conn;
Table = table;
}

public TableQuery(SQLiteConnection conn)
{
Connection = conn;
Table = Connection.GetMapping(typeof(T));
}

public TableQuery< T> Clone()
{
var q = new TableQuery< T> (Connection,Table);
q._where = _where;
if(_orderBys!= null){
q._orderBys = new List< Ordering> (_orderBys);
}
q._limit = _limit;
q._offset = _offset;
return q;
}

public TableQuery< T> where(Expression< Func< T,bool> predExpr)
{
if(predExpr.NodeType == ExpressionType.Lambda){
var lambda =(LambdaExpression)predExpr;
var pred = lambda.Body;
var q = Clone();
q.AddWhere(pred);
return q;
} else {
throw new NotSupportedException(Must be a predicate);
}
}

public TableQuery< T> Take(int n)
{
var q = Clone();
q._limit = n;
return q;
}

public TableQuery< T> Skip(int n)
{
var q = Clone();
q._offset = n;
return q;
}

public TableQuery< T> OrderBy< U> (表达式< Func< T,U>> orderExpr)
{
return AddOrderBy& (orderExpr,true);
}

public TableQuery< T> OrderByDescending< U> (表达式< Func< T,U>> orderExpr)
{
return AddOrderBy& (orderExpr,false);
}

protected TableQuery< T> AddOrderBy U(Expression< T,U> orderExpr,bool asc)
{
if(orderExpr.NodeType == ExpressionType.Lambda){
var lambda =(LambdaExpression )orderExpr;
var mem = lambda.Body as MemberExpression;
if(mem!= null&&(mem.Expression.NodeType == ExpressionType.Parameter)){
var q = Clone();
if(q._orderBys == null){
q._orderBys = new List< Ordering> ();
}
q._orderBys.Add(new Ordering {
ColumnName = mem.Member.Name,
Ascending = asc
});
return q;
} else {
throw new NotSupportedException(Order By不支持:+ orderExpr);
}
} else {
throw new NotSupportedException(Must be a predicate);
}
}

protected void AddWhere(Expression pred)
{
if(_where == null){
_where = pred;
} else {
_where = Expression.AndAlso(_where,pred);
}
}

protected SQLiteCommand GenerateCommand(string selectionList)
{
var cmdText =select+ selectionList +from \+ Table.TableName +\;
var args = new List< object> ();
if(_where!= null){
var w = CompileExpr(_where,args);
cmdText + =where+ w.CommandText;
}
if((_orderBys!= null)&&(_orderBys.Count> 0)){
var t = string.Join(,,_orderBys.Select o =>\+ o.ColumnName +\+(o.Ascending?:desc))。
cmdText + =order by+ t;
}
if(_limit.HasValue){
cmdText + =limit+ _limit.Value;
}
if(_offset.HasValue){
if(!_limit.HasValue){
cmdText + =limit -1;
}
cmdText + =offset+ _offset.Value;
}
return Connection.CreateCommand(cmdText,args.ToArray());
}

protected class CompileResult
{
public string CommandText {get;组; }

public object Value {get;组; }
}

protected 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);

//如果任何一边是一个参数并且为null,那么特别处理另一边(对于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];

for(var i = 0; 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 + + args [0] .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? Convert.ChangeType(valr.Value,ty):null
};
} else if(expr.NodeType == ExpressionType.MemberAccess){
var mem =(MemberExpression)expr;

if(mem.Expression.NodeType == ExpressionType.Parameter){
//
//这是我们的表的一列,只输出列名
//
return new CompileResult {CommandText =\+ mem.Member.Name +\};
} else {
object obj = null;
if(mem.Expression!= null){
var r = CompileExpr(mem.Expression,queryArgs);
if(r.Value == null){
throw new NotSupportedException(成员访问无法编译表达式);
}
if(r.CommandText ==?){
queryArgs.RemoveAt(queryArgs.Count - 1);
}
obj = r.Value;
}

//
//获取成员值
//
对象val = null;

if(mem.Member.MemberType == MemberTypes.Property){
var m =(PropertyInfo)mem.Member;
val = m.GetValue(object,null);
} else if(mem.Member.MemberType == MemberTypes.Field){
var m =(FieldInfo)mem.Member;
val = m.GetValue(obj);
} else {
throw new NotSupportedException(MemberExpr:+ mem.Member.MemberType.ToString());
}

//
//为枚举工作特别的魔法
//
if(val!= null&& val is System。 Collections.IEnumerable&&!(val is string)){
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(Can not compile:+ expr.NodeType.ToString());
}

///< summary>
///编译其中一个参数为null的BinaryExpression。
///< / summary>
///< param name =parameter>非空参数< / param>
protected string CompileNullBinaryExpression(BinaryExpression expression,CompileResult参数)
{
if(expression.NodeType == ExpressionType.Equal)
return(+ parameter.CommandText + );
else if(expression.NodeType == ExpressionType.NotEqual)
return(+ parameter.CommandText +不是?
else
throw new NotSupportedException(无法编译Null-BinaryExpression with type+ expression.NodeType.ToString());
}

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和;
} else if(n == ExpressionType.Or){
return或;
} else if(n == ExpressionType.OrElse){
return或;
} else if(n == ExpressionType.Equal){
return=;
} else if(n == ExpressionType.NotEqual){
return!=;
} else {
throw new System.NotSupportedException(Can not get SQL for:+ n.ToString());
}
}

public int Count()
{
return GenerateCommand(count(*))ExecuteScalar< int> ();
}

public IEnumerator< T> GetEnumerator()
{
return GenerateCommand(*)。ExecuteQuery< T> ().GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}


解决方案

问为什么它是创建错误SQL。让我们一步步走。



LINQ非常强大,但有时会误导开发者。



.Where(a => a.CountryId.Equals(country.Id))是有效的和有效的语法,按国家过滤机场(我建议重写 .CountryId == country.id ?更好的可读性)。



现在你不能看到在后台LINQ发生了什么,因为它都取决于你调用其中上的实际对象。正如其他人所指出的,在 GetAll()中调用 AsQueryable 是原因。



GetAll()执行SQL查询,不过滤,以便它将返回所有 Airport s,然后在内存中过滤结果集。



更糟糕的是,您的 SQLManager 一个ORM的粗略实现。您必须与您的软件架构师讨论实现 Entity Framework NHibernate (但我不会推荐第二个)这是支持LINQ的适当的ORM。使用它们,您会发现查询将被正确过滤。


I have this simple code:

public class AirportRepository : Repository<Airport>
{
    public IQueryable<Airport> GetByCountry(Entity country)
    {
        IQueryable<Airport> airports = GetAll().Where( a => a.CountryId.Equals(country.Id) );

        return airports;
    }
}

...which I thought would create the following SQL:

SELECT * FROM Airport WHERE CountryID = ?

...but it creates the following SQL:

SELECT * FROM Airport

...and then do the Where() part on the client side, which is very costly in my case.

What is going on?

UPDATE: Here's what the base Repository class look like:

public abstract class Repository<T> : IRepository<T> where T : Entity, new()
{
    protected SimpleSQLManager SQLManager = DatabaseManager.Instance.SQLManager;

    public IQueryable<T> GetAll()
    {
        IQueryable<T> all = SQLManager.Table<T>().AsQueryable();

        return all;
    }
}

UPDATE #2: I can't share the code "behind" SQLManager, but after digging through its code, I can see that Table is an IEnumerable. Is that the problem?

UPDATE #3: Here's th relevant (I hope) SQLManager code:

    /// <summary>
    /// Returns a queryable interface to the table represented by the given type.
    /// </summary>
    /// <returns>
    /// A queryable object that is able to translate Where, OrderBy, and Take
    /// queries into native SQL.
    /// </returns>  
    public TableQuery<T> Table<T>() where T : new()
    {
        Initialize(false);

        return _db.Table<T>();
    }

public class TableQuery<T> : IEnumerable<T> where T : new()
{
    public SQLiteConnection Connection { get; private set; }

    public TableMapping Table { get; private set; }

    Expression _where;
    List<Ordering> _orderBys;
    int? _limit;
    int? _offset;

    class Ordering
    {
        public string ColumnName { get; set; }

        public bool Ascending { get; set; }
    }

    TableQuery (SQLiteConnection conn, TableMapping table)
    {
        Connection = conn;
        Table = table;
    }

    public TableQuery (SQLiteConnection conn)
    {
        Connection = conn;
        Table = Connection.GetMapping (typeof(T));
    }

    public TableQuery<T> Clone ()
    {
        var q = new TableQuery<T> (Connection, Table);
        q._where = _where;
        if (_orderBys != null) {
            q._orderBys = new List<Ordering> (_orderBys);
        }
        q._limit = _limit;
        q._offset = _offset;
        return q;
    }

    public TableQuery<T> Where (Expression<Func<T, bool>> predExpr)
    {
        if (predExpr.NodeType == ExpressionType.Lambda) {
            var lambda = (LambdaExpression)predExpr;
            var pred = lambda.Body;
            var q = Clone ();
            q.AddWhere (pred);
            return q;
        } else {
            throw new NotSupportedException ("Must be a predicate");
        }
    }

    public TableQuery<T> Take (int n)
    {
        var q = Clone ();
        q._limit = n;
        return q;
    }

    public TableQuery<T> Skip (int n)
    {
        var q = Clone ();
        q._offset = n;
        return q;
    }

    public TableQuery<T> OrderBy<U> (Expression<Func<T, U>> orderExpr)
    {
        return AddOrderBy<U> (orderExpr, true);
    }

    public TableQuery<T> OrderByDescending<U> (Expression<Func<T, U>> orderExpr)
    {
        return AddOrderBy<U> (orderExpr, false);
    }

    protected TableQuery<T> AddOrderBy<U>(Expression<Func<T, U>> orderExpr, bool asc)
    {
        if (orderExpr.NodeType == ExpressionType.Lambda) {
            var lambda = (LambdaExpression)orderExpr;
            var mem = lambda.Body as MemberExpression;
            if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) {
                var q = Clone ();
                if (q._orderBys == null) {
                    q._orderBys = new List<Ordering> ();
                }
                q._orderBys.Add (new Ordering {
                    ColumnName = mem.Member.Name,
                    Ascending = asc
                });
                return q;
            } else {
                throw new NotSupportedException ("Order By does not support: " + orderExpr);
            }
        } else {
            throw new NotSupportedException ("Must be a predicate");
        }
    }

    protected void AddWhere(Expression pred)
    {
        if (_where == null) {
            _where = pred;
        } else {
            _where = Expression.AndAlso (_where, pred);
        }
    }

    protected SQLiteCommand GenerateCommand(string selectionList)
    {
        var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\"";
        var args = new List<object> ();
        if (_where != null) {
            var w = CompileExpr (_where, args);
            cmdText += " where " + w.CommandText;
        }
        if ((_orderBys != null) && (_orderBys.Count > 0)) {
            var t = string.Join (", ", _orderBys.Select (o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray ());
            cmdText += " order by " + t;
        }
        if (_limit.HasValue) {
            cmdText += " limit " + _limit.Value;
        }
        if (_offset.HasValue) {
            if (!_limit.HasValue) {
                cmdText += " limit -1 ";
            }
            cmdText += " offset " + _offset.Value;
        }
        return Connection.CreateCommand (cmdText, args.ToArray ());
    }

    protected class CompileResult
    {
        public string CommandText { get; set; }

        public object Value { get; set; }
    }

    protected 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];

            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 {
                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 ? Convert.ChangeType (valr.Value, ty) : null
            };
        } else if (expr.NodeType == ExpressionType.MemberAccess) {
            var mem = (MemberExpression)expr;

            if (mem.Expression.NodeType == ExpressionType.Parameter) {
                //
                // This is a column of our table, output just the column name
                //
                return new CompileResult { CommandText = "\"" + mem.Member.Name + "\"" };
            } 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 (mem.Member.MemberType == MemberTypes.Property) {
                    var m = (PropertyInfo)mem.Member;
                    val = m.GetValue (obj, null);                       
                } else if (mem.Member.MemberType == MemberTypes.Field) {
                    var m = (FieldInfo)mem.Member;
                    val = m.GetValue (obj);                     
                } else {
                    throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType.ToString ());
                }

                //
                // Work special magic for enumerables
                //
                if (val != null && val is System.Collections.IEnumerable && !(val is string)) {
                    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>
    protected 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());
    }

    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 "and";
        } else if (n == ExpressionType.AndAlso) {
            return "and";
        } else if (n == ExpressionType.Or) {
            return "or";
        } else if (n == ExpressionType.OrElse) {
            return "or";
        } else if (n == ExpressionType.Equal) {
            return "=";
        } else if (n == ExpressionType.NotEqual) {
            return "!=";
        } else {
            throw new System.NotSupportedException ("Cannot get SQL for: " + n.ToString ());
        }
    }

    public int Count ()
    {
        return GenerateCommand("count(*)").ExecuteScalar<int> ();           
    }

    public IEnumerator<T> GetEnumerator ()
    {
        return GenerateCommand ("*").ExecuteQuery<T> ().GetEnumerator ();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
    {
        return GetEnumerator ();
    }
}

解决方案

You asked why it is creating "wrong" SQL. Let's go step by step.

LINQ is very powerful but sometimes can mislead developers.

.Where(a => a.CountryId.Equals(country.Id)) is valid and working syntax to filter airports by country (can I suggest to rewrite a.CountryId == country.id? Better readability).

Now you can't see what happens in the backstage of LINQ, because it all depends on the actual object you are calling Where on. As pointed out by others, call to AsQueryable in your GetAll() is the cause.

GetAll() executes the SQL query, without filtering, so that it will return all Airports, and then the result set is filtered in memory.

Worse, your SQLManager seems to be a rough implementation of an ORM. You must discuss with your software architect about implementing Entity Framework or NHibernate (but I wouldn't recommend the second anymore) that are proper ORMs supporting LINQ. With them, you will find that the queries will be filtered correctly.

这篇关于IQueryable&lt; T&gt; .Where()不创建正确的SQL的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆