IQueryable <t> .Where()没有创建正确的SQL

时间:2015-07-24 11:05:31

标签: c# sql database

我有这个简单的代码:

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

        return airports;
    }
}

...我以为会创建以下SQL:

SELECT * FROM Airport WHERE CountryID = ?

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

SELECT * FROM Airport

...然后在客户端执行Where()部分,这在我的案例中非常昂贵。

发生了什么事?

UPDATE:以下是基本Repository类的内容:

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:我无法在SQLManager后面共享代码,但在深入研究其代码之后,我可以看到Table是一个IEnumerable。这是问题吗?

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

    /// <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 ();
    }
}

2 个答案:

答案 0 :(得分:2)

你问为什么它会创建“错误的”SQL。让我们一步一步走。

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

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

现在你无法看到LINQ后台发生了什么,因为它取决于你所调用的实际对象Where。正如其他人所指出的那样,致电AsQueryable中的GetAll()是原因。

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

更糟糕的是,您的SQLManager似乎是ORM的粗略实现。您必须与您的软件架构师讨论实现Entity FrameworkNHibernate(但我不会再推荐第二个),这些是支持LINQ的正确ORM。有了它们,您会发现查询将被正确过滤。

答案 1 :(得分:1)

我的解决方法是创建另一个获取谓词的GetAll()方法,然后将该方法用于不同的GetBySomething()方法;

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;
    }

    public IQueryable<T> GetAll(Expression<Func<T, bool>> predicate)
    {
        IQueryable<T> all = SQLManager.Table<T>().Where(predicate).AsQueryable();

        return all;
    }
}

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

        return airports;
    }
}

这有效,但我认为它不是很漂亮?