65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2018 年 7 月 14 日

CPOL

3分钟阅读

viewsIcon

11639

downloadIcon

74

描述了如何为 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 类可能在底层使用它)。

对于数字和布尔值,这一切都很好,但这对于 stringDateTime 值并不真正有效。

对于 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));

这将通过在 TodayTomorrow 之间进行检查来执行 DateTime 值的“IsEqual”检查(给定值),其中 today 是给定的日期值。 当然,如果在 Date 级别进行比较,则此示例很有用,如果时间也起作用,您可能需要增强此逻辑。

对于 string,这甚至更复杂,因为 stringSmallerThenGreaterThen 并不真正起作用。 例如,以下

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 方法(即使在访问数据库时)。 我们还必须对 GreaterThanOrEqual 变体执行相同的操作。

将它们放在一起,您将获得以下函数来构建条件

        /// <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> 标签
  • 添加了到源项目的链接。
© . All rights reserved.