EntityFramework实现了IQueryable接口,所以它支持 Linq的Queryable查询。Queryable在查询时可以先将Linq表达式转换成SQL,然后在与数据库查询。这种查询方式可以提高查询效率,因为不用每次将数据库种整个表都查询下来。我们应该尽量“延迟”到使用数据时才调用EntityFramework的ToList()方法,因为ToList的时候EntityFramework会从数据库中查询数据,之后都是的操作都是在数据库查询结果中进行。
在使用Linq查询时,使用的都是对象,设置条件时我们都是通过对对象的属性进行查询。如:
db.Students.Where(s => s.Name =="Kyle" && s.Class == "First")
翻译的SQL语句如下:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[Class] AS [Class]
FROM [dbo].[Students] AS [Extent1]
WHERE (N'Kyle' = [Extent1].[Name]) AND(N'First' = [Extent1].[Class])
有时要需要忽略一些条件,可能不需要Name的条件,或者不需要Class的条件,如果是使用SQL语句查询时,我们可以很容易的决定是否要在SQL字符串中插入相应条件的字符串。但是使用Linq时,查询使用的条件是由于使用的时对象表达式来决定的,我们无法向操作字符串一样操作表达式。为了达到忽略条件的目的我们可以使用一个或表达式来忽略某个条件。如下:
db.Students.Where(s =>(string.IsNullOrEmpty(s.Name)|| s.Name == "Kyle")
&&(string.IsNullOrEmpty(s.Class) || s.Class == "First"))
翻译的SQL如下:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[Class] AS [Class]
FROM [dbo].[Students] AS [Extent1]
WHERE (([Extent1].[Name] IS NULL) OR ((CAST(LEN([Extent1].[Name]) AS int)) = 0)
OR (N'Kyle' = [Extent1].[Name])) AND (([Extent1].[Class] IS NULL)
OR (( CAST(LEN([Extent1].[Class]) AS int)) =0)OR (N'First' = [Extent1].[Class]))
Linq翻译的SQL语句也变得更加复杂,这并不是很好的办法,SQL的条件变得更复杂,查询效率也会降低。这种方式使用限制也会很大,只适合And合并的条件。
实际上Linq查询可以使用多个Where查询,查询后增加的Where里面的条件会和之前的条件And。如下:
varquery = db.Students.Where(s =>s.Name == "Kyle");
query.Where(s =>s.Class == "First");
翻译的SQL语句如下:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[Class] AS [Class]
FROM [dbo].[Students] AS [Extent1]
WHERE (N'Kyle' = [Extent1].[Name]) AND(N'First' = [Extent1].[Class])
这个SQL与我们最开始的结果完全一样
以上都是And条件查询,如果有Or条件查询可以如下实现:
varquery = db.Students.Where(s =>s.Name == "Kyle");
query = query.Where(s =>s.Class == "First");
query.Union(db.Students.Where(s=>s.Name == "Ace"));
翻译的SQL语句如下:
SELECT
[Distinct1].[C1] AS [C1],
[Distinct1].[C2] AS [C2],
[Distinct1].[C3] AS [C3],
[Distinct1].[C4] AS [C4]
FROM ( SELECT DISTINCT
[UnionAll1].[ID] AS [C1],
[UnionAll1].[Name] AS [C2],
[UnionAll1].[Age] AS [C3],
[UnionAll1].[Class] AS [C4]
FROM (SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[Class] AS [Class]
FROM [dbo].[Students] AS [Extent1]
WHERE (N'Kyle' = [Extent1].[Name])AND (N'First' = [Extent1].[Class])
UNION ALL
SELECT
[Extent2].[ID] AS [ID],
[Extent2].[Name] AS [Name],
[Extent2].[Age] AS [Age],
[Extent2].[Class] AS [Class]
FROM [dbo].[Students] AS [Extent2]
WHERE N'Ace' = [Extent2].[Name]) AS[UnionAll1]
) AS[Distinct1]
从翻译的SQL语句可以看出最终的
使用了两个Select查询,然后UNIONALL将两个查询合并,这种效率并不高。
Linq在转换成SQL语句是实际上使用的表达式树进行转换的,表达式树也Linq的核心。使用表达式树查询可以让我们如同写SQL语句一样自行组装我们的条件,非常灵活,最终转换的SQL语句也非常简洁。但是如果直接使用表达式树,代码会变得很复杂,一点都不简洁。这里我会介绍一种封装表达式树的方法,让表达式树非常容易使用。
表达式合并
这里简单介绍一下lambda表达式在查询中的使用,后续会有文章详细介绍表达式树。
一些简单的lambda表达式是可以直接转换成表达式树的,所以赋值一个表达式可以如下:
Expression
表达式树是可以合并,可以是OR也可以是And,如下:
Expression>FunX1 = x => x < 10;
Expression>FunX2 = y => y > 5;
Expression> fun = Expression.Lambda>
(Expression.AndAlso(FunX1.Body, FunX2.Body), FunX1.Parameters[0], FunX2.Parameters[0]);
以上表达式合并后,两个参数x,y分开的,合并后的表达式fun的lambda表达式为
(x, y)=> x < 10 && y > 5
实际上我们能希望合并后得到表达式的lambda表达式为
x =>x < 10 && x > 5
为了实现这个功能,我们需要创建一个表达式参数newParam,然后替换两个式子中的x,y参数这样我们可以得到两个都使用同一个参数newParam的表达式,这样合并后就只有一个参数。为了实现这个功能我们需要创建一个替换参数的类,如下:
internal class ExpressionParameterReplacer : ExpressionVisitor
{
public ExpressionParameterReplacer()
{
var newParam = Expression.Parameter(typeof(int), "NewParam");
this.ParameterExpression = newParam;
}
public ParameterExpression ParameterExpression { get; private set; }
public Expression Replace(Expression expression)
{
return this.Visit(expression);
}
protected override Expression VisitParameter(ParameterExpression _)
{
return this.ParameterExpression;
}
}
使用该类合并表达式方法如下:
Expression> FunX1 = x => x < 10;
Expression> FunX2 = y => y > 5;
var expParam = new ExpressionParameterReplacer();
Expression> fun = Expression.Lambda>
(Expression.AndAlso(expParam.Replace(FunX1.Body), expParam.Replace(FunX2.Body)), expParam.ParameterExpression);
以上表达式合并后,两个参数x,y分开的,合并后的表达式fun的lambda表达式为
x =>x < 10 && x > 5
条件表达式生成类
为了使得表达式参数替换类更加通用可以使用了模板,这样可以适用于各种参数,如下:
//该类用来替换参数,使得两个表达式参数一致
//例如:x=>x>3 和 x=>x<10,通过该类使得两个Lambda合并后x为同一个参数,即 x=> x>3 && x<10
internal class ExpressionParameterReplacer : ExpressionVisitor
{
public ExpressionParameterReplacer()
{
var newParam = Expression.Parameter(typeof(T), "NewParam");
this.ParameterExpression = newParam;
}
public ParameterExpression ParameterExpression { get; private set; }
public Expression Replace(Expression expression)
{
return this.Visit(expression);
}
protected override Expression VisitParameter(ParameterExpression _)
{
return this.ParameterExpression;
}
}
为了更加方便的生成表达式,能够很容易的使用Or或者And合并表示,可以创建一个类,同样需要使用模板:
internal class ConditionExpressionHelper
{
Expression> queryExp;
ExpressionParameterReplacer newParam;
public ConditionExpressionHelper(bool intilValue)
{
newParam = new ExpressionParameterReplacer();
queryExp = Expression.Lambda>(Expression.Constant(intilValue), newParam.ParameterExpression);
}
public ConditionExpressionHelper(Expression> intilExp)
{
newParam = new ExpressionParameterReplacer();
var tempExp = newParam.Replace(intilExp.Body);
queryExp = Expression.Lambda>(tempExp, newParam.ParameterExpression);
}
public Expression> QueryExp
{
get { return queryExp; }
}
public void AndExpression(Expression> condition)
{
var conditionExp = newParam.Replace(condition.Body);//使newParam 替换condition表达式参数
//使用新参数构建新表达式
queryExp = Expression.Lambda>(Expression.AndAlso(queryExp.Body, conditionExp), newParam.ParameterExpression);
}
public void OrExpression(Expression> condition)
{
var conditionExp = newParam.Replace(condition.Body);
queryExp = Expression.Lambda>(Expression.OrElse(queryExp.Body, conditionExp), newParam.ParameterExpression);
}
}
使用表达式树查询
最后给一个使用上面两个类进行查询的例子:
class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Class { get; set; }
}
class MyContex : DbContext
{
public const string connectstr = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=EFConditionQuery;Integrated Security=True;";
public DbSet Students { get; set; }
public MyContex() :
base(connectstr)
{
}
}
class Program
{
static void Main(string[] args)
{
using (var db = new MyContex())
{
//db.Students.Add(new Student() { Name = "Kyle", Age=18, Class="First" });
//db.Students.Add(new Student() { Name = "Kyle2", Age = 16, Class = "First" });
//db.Students.Add(new Student() { Name = "Kyle3", Age = 38, Class = "First" });
//db.Students.Add(new Student() { Name = "Kyle4", Age = 16, Class = "Second" });
//db.Students.Add(new Student() { Name = "Kyle5", Age = 18, Class = "Second" });
//db.Students.Add(new Student() { Name = "Ace", Age = 28, Class = "Second" });
//db.Students.Add(new Student() { Name = "Ace2", Age = 28, Class = "Third" });
//db.Students.Add(new Student() { Name = "Ace3", Age = 18, Class = "Third" });
//db.SaveChanges();
var condition = new ConditionExpressionHelper(false);
condition.OrExpression(t => t.Name == "Kyle");
condition.AndExpression(t => t.Age == 18);
condition.OrExpression(t => t.Name == "Ace");
var query = db.Students.Where(condition.QueryExp);
Console.WriteLine($"Query SQL:\n {query.ToString()}");
foreach (var item in query.ToList())
{
Console.WriteLine($"ID={item.ID}, Name={item.Name}, Age={item.Age}, Class={item.Class}");
}
}
}
}
运行结果如下:
从结果中我们能看到生成的SQL非常简洁。