具有基于代码的条件的 Entity Framework 视图





4.00/5 (2投票s)
描述了如何为 Entity Framework 创建基于代码的视图。
引言
数据库中的视图非常出色。它允许在实际数据模型上建立一层抽象,并允许快速重用大量使用的搜索查询。 虽然 Entity Framework 支持使用视图,但有时您希望能够在代码本身中编写视图。 当代码对数据进行某种形式的加密时(如果您不能使用数据库加密),这尤其有用。
本文介绍如何设置具有条件查询的基本视图。
背景
本文假定您具备 Entity Framework 的基本知识以及如何运行基本查询。
快速浏览 LinqKit 也很实用 (https://github.com/scottksmith95/LINQKit),因为它用于完整项目中的“Or
”语句。
强烈建议掌握 表达式。
Using the Code
编写视图的最大困难是条件。 毕竟,视图只不过是
var query = context.Students.Include(s=>s.Courses.Select(c=>c.Course));
幸运的是,System.Linq.Expression
类在这里发挥作用,它提供了我们几乎需要的所有逻辑。 使用它,我们得到以下内容作为创建条件的基础
var result = Expression.Lambda<Func<T, bool>>
(Expression.LessThan(selector.Body, Expression.Constant(value)), selector.Parameters);
selector.body
是对象特定属性的选择器。 例如,s=> s.Name
value
是我们正在搜索的值。selector.Parameters
是任何参数,以防使用参数表达式。 出于本文的目的,我们没有明确使用它。 (虽然Expression
类可能在底层使用它)。
对于数字和布尔值,这一切都很好,但这对于 string
或 DateTime
值并不真正有效。
对于 DateTime
值,您需要做一些技巧,因为 DateTime
中的数据比仅 Date
中的数据更多(顾名思义)。 像这样
DateTime s1 = DateTime.Now; DateTime s2 = DateTime.Now.AddMilliseconds(1);
s1 == s2 => false;
由于 DateTime
比较是在刻度级别进行的,因此比较几乎总是失败(尤其是在它们通常来自用户填写的网站时)。
解决此问题的最佳方法是执行以下操作
Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>
(Expression.GreaterThanOrEqual(selector.Body, Expression.Constant(value)), selector.Parameters);
DateTime d2 = ((DateTime)(object)value).AddDays(1);
result = result.And(Expression.Lambda<Func<T, bool>>
(Expression.LessThan(selector.Body, Expression.Constant(d2)), selector.Parameters));
这将通过在 Today
和 Tomorrow
之间进行检查来执行 DateTime
值的“IsEqual
”检查(给定值),其中 today
是给定的日期值。 当然,如果在 Date
级别进行比较,则此示例很有用,如果时间也起作用,您可能需要增强此逻辑。
对于 string
,这甚至更复杂,因为 string
的 SmallerThen
和 GreaterThen
并不真正起作用。 例如,以下
string s1 = "J";
string s2 = "ABC";
Which is smaller? Also s1< s2 doesn't work at all.
因此,我们应该为 string
添加一些额外的逻辑
if (typeof(DataType) == typeof(string))
{
result = Expression.Lambda<Func<T, bool>>(
Expression.LessThanOrEqual(
Expression.Call(
selector.Body, typeof(string).GetMethod("CompareTo", new[] { typeof(string) }),
Expression.Constant(value))
, Expression.Constant(0, typeof(int))
)
, selector.Parameters);
}
这允许表达式调用 CompareTo
方法(即使在访问数据库时)。 我们还必须对 GreaterThan
和 OrEqual
变体执行相同的操作。
将它们放在一起,您将获得以下函数来构建条件
/// <summary>
/// Builds a conditional Expression based upon the comparison type passed
/// </summary>
/// <typeparam name="T">The class the conditional expression is for</typeparam>
/// <typeparam name="DataType">The type of data to be compared upon</typeparam>
/// <param name="selector">Expression to retrieve the Field to compare on</param>
/// <param name="value">The value to compare to</param>
/// <param name="comparison">The comparison type to use</param>
/// <param name="expressions">Any additional expression to add (using AND)
/// to this generated expression.</param>
/// <returns>A conditional expression</returns>
public Expression<Func<T, bool>> BuildCondition<T, DataType>
(Expression<Func<T, DataType>> selector, DataType value, ComparisonTypes comparison,
params Expression<Func<T, bool>>[] expressions)
{
Expression<Func<T, bool>> result = null;
switch (comparison)
{
case ComparisonTypes.Equals:
default:
if (typeof(DataType) == typeof(DateTime))
{
result = Expression.Lambda<Func<T, bool>>
(Expression.GreaterThanOrEqual(selector.Body,
Expression.Constant(value)), selector.Parameters);
DateTime d2 = ((DateTime)(object)value).AddDays(1);
result = result.And(Expression.Lambda<Func<T, bool>>
(Expression.LessThan(selector.Body, Expression.Constant(d2)),
selector.Parameters));
}
else
{
result = Expression.Lambda<Func<T, bool>>
(Expression.Equal(selector.Body, Expression.Constant(value)),
selector.Parameters);
}
break;
case ComparisonTypes.NotEquals:
if (typeof(DataType) == typeof(DateTime))
{
result = Expression.Lambda<Func<T, bool>>(Expression.LessThan
(selector.Body, Expression.Constant(value)), selector.Parameters);
DateTime d2 = ((DateTime)(object)value).AddDays(1);
result = result.Or(Expression.Lambda<Func<T, bool>>
(Expression.GreaterThanOrEqual(selector.Body, Expression.Constant(d2)),
selector.Parameters));
}
else
{
result = Expression.Lambda<Func<T, bool>>(Expression.NotEqual
(selector.Body, Expression.Constant(value)), selector.Parameters);
}
break;
case ComparisonTypes.SmallerThan:
if (typeof(DataType) == typeof(string))
{
result = Expression.Lambda<Func<T, bool>>(
Expression.LessThan(
Expression.Call(
selector.Body, typeof(string).GetMethod
("CompareTo", new[] { typeof(string) }),
Expression.Constant(value))
, Expression.Constant(0, typeof(int))
)
, selector.Parameters);
}
else
{
result = Expression.Lambda<Func<T, bool>>(Expression.LessThan
(selector.Body, Expression.Constant(value)), selector.Parameters);
}
break;
case ComparisonTypes.SmallerOrEquals:
if (typeof(DataType) == typeof(DateTime))
{
DateTime d2 = ((DateTime)(object)value).AddDays(1);
result = Expression.Lambda<Func<T, bool>>(Expression.LessThan
(selector.Body, Expression.Constant(d2)), selector.Parameters);
}
else if (typeof(DataType) == typeof(string))
{
result = Expression.Lambda<Func<T, bool>>(
Expression.LessThanOrEqual(
Expression.Call(
selector.Body, typeof(string).GetMethod
("CompareTo", new[] { typeof(string) }),
Expression.Constant(value))
, Expression.Constant(0, typeof(int))
)
, selector.Parameters);
}
else
{
result = Expression.Lambda<Func<T, bool>>(Expression.LessThanOrEqual
(selector.Body, Expression.Constant(value)), selector.Parameters);
}
break;
case ComparisonTypes.GreaterThan:
if (typeof(DataType) == typeof(DateTime))
{
DateTime d2 = ((DateTime)(object)value).AddDays(1);
result = Expression.Lambda<Func<T, bool>>(Expression.GreaterThanOrEqual
(selector.Body, Expression.Constant(d2)), selector.Parameters);
}
else if (typeof(DataType) == typeof(string))
{
result = Expression.Lambda<Func<T, bool>>(
Expression.GreaterThan(
Expression.Call(
selector.Body, typeof(string).GetMethod("CompareTo", new[]
{ typeof(string) }),
Expression.Constant(value))
, Expression.Constant(0, typeof(int))
)
, selector.Parameters);
}
else
{
result = Expression.Lambda<Func<T, bool>>(Expression.GreaterThan
(selector.Body, Expression.Constant(value)), selector.Parameters);
}
break;
case ComparisonTypes.GreaterOrEquals:
if (typeof(DataType) == typeof(string))
{
result = Expression.Lambda<Func<T, bool>>(
Expression.GreaterThanOrEqual(
Expression.Call(
selector.Body, typeof(string).GetMethod("CompareTo",
new[] { typeof(string) }),
Expression.Constant(value))
, Expression.Constant(0, typeof(int))
)
, selector.Parameters);
}
else
{
result = Expression.Lambda<Func<T, bool>>(Expression.GreaterThanOrEqual
(selector.Body, Expression.Constant(value)), selector.Parameters);
}
break;
case ComparisonTypes.Like:
result = Expression.Lambda<Func<T, bool>>(Expression.Call
(selector.Body, "Contains", null,
Expression.Constant(value)), selector.Parameters);
break;
}
if (expressions != null)
{
foreach (var exp in expressions) result = result.And(exp);
}
return result.Expand();
}
这允许构建您想要的几乎任何条件,并且应该涵盖大多数(如果不是全部)情况。
您可以按如下方式使用它
this.BuildCondition<Student, string>(s => s.Name, this.Value, this.Comparison);
将它们放在一起(使用出色的 linqkit),您可以使用
var query = context.Students.Include(s=>s.Courses.Select(c=>c.Course));
var condition = this.BuildCondition<Student, string>(s => s.Name, this.Value, this.Comparison);
var pr = PredicateBuilder.New<T>();
pr.And(condition); //You can also use the Or here, depending on what you want to achieve.
query = query.AsExpandable().Where(pr.Expand());
var result = query.ToList();
有了它,一个具有条件的完整工作视图。 好吧,要使其可重用于您的设置,还需要做更多的工作。 请参阅附加的项目以获取更多信息和完全正常工作的示例。 DB 是在配置中构建的,因此只需从 Visual Studio 中的 Package Manager Console 运行“update-database
”。
关注点
非常烦人的是 Linq 不支持“Or
”语句。 幸运的是,LinqKit 提供了一种解决方法。 除此之外,这为使用 Entity Framework 创建您自己的基于代码的视图提供了一个良好的开端。 也就是说,建议尽可能使用普通视图,因为这更容易优化性能。
历史
- 第一个版本,包含如何创建条件的基本描述
- 将网站的链接设为实际链接
- 更新了 Expression 的链接以指向该类而不是概念
- 一些小的文本增强,添加了正确的
<code>
标签 - 添加了到源项目的链接。