用Entity Framework开发项目过程中,发现linq查询返回的结果,与SQL查询出的结果不一致。
问题表现为:SQL返回了我们期望的结果,但是LINQ查询出来的结果确是一些重复记录。
这种问题一般是对数据库视图进行查询时发生,问题产生的原因及现象如下:
1)使用.net 的linq to sql,或linq to entities 生成的实体对象,出于为提高效率等原因,会对每个对象自动判断出一些列做为实体对象的Key(EntityKey),
这个Key就是相当于我们表中的主键,查询结果时,会根据这个Key列的值进行判断,假设数据库中有视图View1有3条记录如下:
====View1=====
列1(key列) 列2 列3
1 a1 a1
1 a2 a2
3 a3 a3
2)假设此View1的实体对象中,列1判断为Key列(标识列),如果当前我们有一个如下的查询,SQL语句如下:
SELECT 列1,列2,列3 FROM [View1]
显然,查询出来正确的结果为:
====SQL返回正确的结果=====
列1(key列) 列2 列3
1 a1 a1
1 a2 a2
3 a3 a3
3)但是如果你用LINQ语句去查:var objs = db.entity.select(v=>v);//entity为实体对象
得出的结果为:
====LINQ返回错误的结果=====
列1(key列) 列2 列3
1 a1 a1
1 a1 a1 (这行是错误的)
3 a3 a3
问题分析:
我们会发现,第2条记录与第一条记录是一样的,原因就在于实体对象中的Key列即列1的值相同(在我们的例子中都为1),
那么LINQ返回第一条数据时,没有问题,正常返回;
返回第二条时,判断Key列的值,发现都为1,那么就认为第二条数和第一条数据是一样的,所以直接返回第一行记录的值,做为第二条记录。
第三条记录,key列不同,取回正确数据。
如果LINQ的查询还附加排序也会影响LINQ查询的结果,总之会返回不同的错误结果
解决方法:
1.找出视图中可以用来唯一标识该行记录的那些列,在edmx中将它们设为实体键(Key列),在我的项目中还给这个字段加了唯一键约束,防止LINQ查询结果出错。
通过上面分析,我们就可以明白为什么出现这种问题都是在使用视图的时候,因为如果是表的话,那么基本上主键会被判断为实体对象的Key,显示不会出现数据重复问题。
但是视图就不一样,特别是一些GroupBy语句创建的视图,.net底层自动判定的EntityKey,就会有问题,一般都是随便找一个或者几个列,GroupBy 语句的视图中,
基本上没有哪一个或者哪几个字段能做为记录唯一标识,那么我们只能给记录生成标识列来解决。
比如有一个视图View2的创建语句如下:
select col1,col2,col3,count(col4) as col4 from [table] group by col1,col2,col3
此时为了避免错误的结果,有些朋友,就把视图中col1,col2,col3的列都设为实体键,这样的确可以避免一些问题,有时候列太多了,全设为实体键,不是一个好办法。
但是,我们可以给记录自动生成列,就是使用 row_number() 方法,比如可以使用如下语句创建视图:
select [index]=row_number() over (order by ID), col1,col2,col3,count(col4) as col4 from [table] group by col1,col2,col3
然后将index列设为实体键就OK了。
2.使用EF执行SQL语句的方法 DbContext.Database.SqlQuery<T>(视图名),也可以解决问题,效率就不用我说,通过SQL Server Profiler跟踪测试一下就知道了。
本文参考了TobeorNot的经验加补充,如有更好的方法,请指教。