16

我们在我们的业务解决方案中使用 EF 5.0 作为我们选择的 ORM,以 n 层方式构建,所有内容都解耦,并使用 ninject 构建了一个很好的组合根。

最近,我们一直在构建一个在底层使用分区的数据库,并且我们在DATE列上有一些重要的索引。

在 Sql Server 2008 上正确声明了这些列。我们还在 EF 映射中添加了正确的数据类型,以及HasColumnType("Date")指令。

尽管如此,当通过 Linq to Entities 查询表时,我们过滤日期的参数是创建类型的DateTime2,甚至列也在DateTime2查询中被强制转换,因此类型与参数匹配。

这种行为有几个问题。首先,如果我告诉 EF 引擎数据库上的列是DATE它为什么要把它转换成DateTime2?

其次,这种转换使数据库忽略索引,因此不使用分区。我们每个物理分区有一年,如果我问一个日期范围,比如说 2013 年 2 月到 2013 年 3 月,扫描应该只发生在一个物理分区上。如果手动使用正确的数据类型,它会正常工作,DATEDateTime2会扫描所有分区的强制转换,从而大大降低性能。

现在,我确定我错过了一些东西,因为如果 Microsoft ORM 在 Microsoft Sql Server 上不能很好地工作,那将是相当愚蠢的。

我一直找不到任何关于 EF 如何在查询中使用正确数据类型的文档,所以我在这里问。任何帮助将不胜感激。

谢谢。

4

3 回答 3

4

.NET 和 SQL server 中 DateTime 类型的范围是不同的。

.NET DateTime 范围是:0000-Jan-01 到 9999-Dec-31 SQL DateTime 范围是:1900-Jan-01、2079-Jun-06

为了匹配范围,EF 将您的 .NET DateTime 转换为与 .NET DateTime 范围具有相同范围的 SQL 服务器 DateTime2 类型。

我认为您的问题仅在您具有未分配并通过 EF 传递给 SQL Server 的日期属性时发生。当日期未指定特定值时,默认为 DateTime.Min,即 0000-Jan-01,这会导致转换为 DateTime2。

我认为您可以使您的 DateTime 属性为空-> DateTime?或编写一个助手来转换您的 DateTime.Min 以满足 SQL DateTime 范围。

希望这会有所帮助。

于 2013-05-21T13:33:22.643 回答
4

我不相信这在实体框架中是可能的。 此请求的增强功能可能会满足您的需求。 此 MSDN 页面显示 SQL Server 类型和 CLR 类型之间的映射。请注意,date它支持并映射到DateTime,但由于几种 SQL 类型映射到相同的 CLR 类型,EF 显然选择了一种 SQL 类型作为 CLR 类型的首选等效项。

你能把你的选择代码包装在一个存储过程中吗?如果是这样,这似乎是一个合理的解决方案。您可以使用DbSet{T}.SqlQuery从执行 sp 实现对象。

代码示例

以下简短的控制台应用程序演示了该概念。请注意相关实体是如何成功延迟加载的。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;

namespace ConsoleApplication1
{
    [Table("MyEntity")]    
    public class MyEntity
    {
        private Collection<MyRelatedEntity> relatedEntities;

        [Key]
        public virtual int MyEntityId { get; set; }

        [DataType(DataType.Date)]
        public virtual DateTime MyDate { get; set; }

        [InverseProperty("MyEntity")]
        public virtual ICollection<MyRelatedEntity> RelatedEntities
        {
            get
            {
                if (this.relatedEntities == null)
                {
                    this.relatedEntities = new Collection<MyRelatedEntity>();
                }

                return this.relatedEntities;
            }
        }

        public override string ToString()
        {
            return string.Format("Date: {0}; Related: {1}", this.MyDate, string.Join(", ", this.RelatedEntities.Select(q => q.SomeString).ToArray()));
        }
    }

    public class MyRelatedEntity
    {
        [Key]
        public virtual int MyRelatedEntityId { get; set; }

        public virtual int MyEntityId { get; set; }

        [ForeignKey("MyEntityId")]
        public virtual MyEntity MyEntity { get; set; }

        public virtual string SomeString { get;set;}
    }

    public class MyContext : DbContext
    {
        public DbSet<MyEntity> MyEntities
        {
            get { return this.Set<MyEntity>(); }
        }
    }

    class Program
    {
        const string SqlQuery = @"DECLARE @date date; SET @date = @dateIn; SELECT * FROM MyEntity WHERE MyDate > @date";

        static void Main(string[] args)
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());

            using (MyContext context = new MyContext())
            {
                context.MyEntities.Add(new MyEntity
                    {
                        MyDate = DateTime.Today.AddDays(-2),
                        RelatedEntities =
                        {
                            new MyRelatedEntity { SomeString = "Fish" },
                            new MyRelatedEntity { SomeString = "Haddock" }
                        }
                    });

                context.MyEntities.Add(new MyEntity
                {
                    MyDate = DateTime.Today.AddDays(1),
                    RelatedEntities =
                        {
                            new MyRelatedEntity { SomeString = "Sheep" },
                            new MyRelatedEntity { SomeString = "Cow" }
                        }
                });

                context.SaveChanges();
            }

            using (MyContext context = new MyContext())
            {
                IEnumerable<MyEntity> matches = context.MyEntities.SqlQuery(
                    SqlQuery,
                    new SqlParameter("@dateIn", DateTime.Today)).ToList();

                // The implicit ToString method call here invokes lazy-loading of the related entities.
                Console.WriteLine("Count: {0}; First: {1}.", matches.Count(), matches.First().ToString());
            }

            Console.Read();
        }
    }
}
于 2013-05-22T20:04:47.450 回答
2

我没有解决办法。我从未见过DateTime涉及 .NET 参数的 LINQ-to-Entites 查询在 SQL 查询中使用了除 .NET 之外的参数类型datetime2(7)。我怀疑你能摆脱它。只是试图解释为什么它是这样的:

假设您有一个具有SomeNumbertype属性的实体int。对于这样的查询,您期望得到什么结果:

....Where(e => e.SomeNumber >= 7.3)....

SomeNumber可能是所有实体8或更大。如果(浮点十进制)参数7.3将被转换为int存储在数据库中的类型,您必须决定如何舍入7.3- 到7(将导致错误的结果)或8?好的,你可以说,因为我的查询说>=并且我知道数据库中的类型是一个整数,四舍五入8必须是正确的。如果我会使用<=,那么四舍五入7必须是正确的。如果我要使用==,哦……我根本不能四舍五入,否则我知道结果一定是空的,我可以直接把这个Where子句翻译成false。并且!=true. 但参数 of7.0是一种特殊情况。ETC....

好吧,这个例子中的困境有一个简单的解决方案:首先在客户端通过使用int参数(78)来决定你想要什么。

解决方案DateTime并不那么简单,因为 .NET 没有Date类型。带DateTime参数的查询将始终具有以下形式...

DateTime dateTime = new DateTime(2013, 5, 13, 10, 30, 0);
....Where(e => e.SomeDateTime >= dateTime)....

...如果SomeDateTime存储date在 SQL Server 中,您又会遇到舍入困境。我必须投到2013.05.13or2013.05.14吗?对于上面的查询,客户肯定会期望所有实体的日期为 14 日及以后。

好吧,你可以做得很聪明,比如:如果我的DateTime参数的时间部分是午夜,则转换为日期部分。如果我使用>=cast to next day 等等,等等……或者你总是可以 cast to datetime2(7). 然后查询的结果总是正确的,并且正如(.NET)客户端所期望的那样。正确...但可能使用次优索引。

于 2013-05-14T00:27:35.097 回答