我们在我们的业务解决方案中使用EF 5.0作为我们的首选ORM,以n层方式构建,所有内容都解耦,并且使用ninject构建了一个很好的组合根。
最近,我们一直在构建一个使用下面的分区的数据库,我们在DATE
列上有一些重要的索引。
在Sql Server 2008上正确声明了列。我们还使用HasColumnType("Date")
指令在EF映射中添加了正确的数据类型。
但是,当通过Linq to Entities查询表时,我们过滤日期的参数是由类型DateTime2
创建的,甚至列也会在查询中强制转换为DateTime2
,因此类型匹配参数。
此行为有几个问题。首先,如果我告诉EF引擎数据库中的列是DATE
,为什么要将其转换为DateTime2
?
其次,此强制转换使数据库忽略索引,因此不使用分区。我们每个物理分区有一年,如果我问一个日期范围,比方说,2013年2月到2013年3月,扫描应该仅在一个物理分区上进行。如果手动使用正确的数据类型DATE
但是转换为DateTime2
,所有分区都会被扫描,从而大大降低了性能。
现在,我确定我错过了一些东西,因为微软ORM在Microsoft Sql Server上不能很好地运行是相当愚蠢的。
我一直无法找到有关EF如何在查询中使用正确数据类型的任何文档,所以我在这里问。任何帮助将不胜感激。
感谢。
答案 0 :(得分:4)
.NET和SQL Server中的DateTime类型的范围不同。
.NET DateTime范围是:0000-Jan-01到9999-Dec-31 SQL DateTime范围是:1900-Jan-01,2079-Jun-06
要匹配范围,EF会将.NET DateTime转换为SQL Server DateTime2类型,其范围与.NET DateTime范围相同。
我认为只有当您拥有未分配的日期属性并通过EF传递给SQL服务器时才会出现此问题。如果未使用特定值分配日期,则默认为DateTime.Min,即0000-Jan-01,这将导致转换为DateTime2。
我认为你可以使你的DateTime属性可以为空 - >约会时间?或编写帮助程序以转换DateTime.Min以满足SQL DateTime范围。
希望这有帮助。
答案 1 :(得分:4)
我不相信这在实体框架中是可行的。 This requested enhancement可能会做你需要的。 This MSDN page显示了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();
}
}
}
答案 2 :(得分:2)
我没有解决方案。我从未见过涉及.NET DateTime
参数的LINQ-to-Entites查询,该参数在datetime2(7)
以外的SQL查询中使用了参数类型。我怀疑你可以摆脱它。试着解释原因是什么:
假设您的实体具有SomeNumber
类型的属性int
。对于像这样的查询,您会得到什么结果:
....Where(e => e.SomeNumber >= 7.3)....
可能SomeNumber
为8
或更高的所有实体。如果(浮点十进制)参数7.3
将转换为存储在数据库中的类型int
,则必须决定如何将7.3
舍入到7
(将导致错误的结果)或8
?好的,你可以说,因为我的查询显示>=
而且我知道数据库中的类型是一个整数,舍入到8
必须是正确的。如果我使用<=
,那么舍入到7
必须正确。如果我会使用==
,哦...我根本不能进行回合,或者我知道结果必须为空,我可以直接将此Where
子句翻译为false
。并!=
到true
。但是7.0
的参数是一个特例。等....
嗯,这个例子中的两难问题有一个简单的解决方案:首先使用int
参数(7
或8
)确定客户端的内容。< / p>
DateTime
的解决方案并不那么简单,因为.NET没有Date
类型。具有DateTime
参数的查询将始终具有...
DateTime dateTime = new DateTime(2013, 5, 13, 10, 30, 0);
....Where(e => e.SomeDateTime >= dateTime)....
...如果SomeDateTime
在SQL Server中存储为date
,则会再次出现四舍五入的困境。我是否必须加注到2013.05.13
或2013.05.14
?对于上面的查询,客户端肯定会期望所有实体的日期都是14日及之后。
嗯,你可以聪明地做,如:如果DateTime
参数的时间部分是午夜,则转换为日期部分。如果我使用>=
施放到第二天,等等......或者你总是可以施放到datetime2(7)
。然后查询的结果总是正确的,并且(.NET)客户端期望它。正确......但也许索引使用次数不合适。