实体框架在使用 Find 方法时抛出异常
Posted
技术标签:
【中文标题】实体框架在使用 Find 方法时抛出异常【英文标题】:Entity Framework throws exception when using Find-method 【发布时间】:2015-11-16 22:12:43 【问题描述】:我有一个简单的网络应用程序,它允许用户上传 2 个包含某些数据的 .csv 文件。为了持久化数据,我在两个不同的Import-methods
中使用了Entity Framework
。
第一个导入方法
public void ImportOne(string path)
StreamReader sr = new StreamReader(path);
using (var db = new ContextEv("RndContext"))
db.Database.ExecuteSqlCommand("DELETE FROM TableA");
db.Database.ExecuteSqlCommand("DELETE FROM TableB");
while (!sr.EndOfStream)
string[] data = sr.ReadLine().Split(';');
string houseId = data[0];
House house = new House()
HouseId = houseId,
;
House dummy = db.Houses.Find(houseId);
if (!dummy.HouseId.Equals(house.HouseId))
db.Houses.Add(house);
此行失败:House dummy = db.Houses.Find(houseId);
,但有以下异常:
其中一个主键值的类型与类型不匹配 在实体中定义。请参阅内部异常 详细信息。\r\n参数名称:keyValues
InnerException 的ErrorContext:
关键字“AS”,第 1 行,第 22 列
InnerException 的错误描述:
查询语法无效。
好的,我检查了类型是否真的是这里的问题。不过我没发现有什么不对。
关于它的“有趣”之处在于,我在另一个 Import 方法中使用了相同的 Find 方法,并且它毫无例外地工作!
using (var db = new ContextEv("RndContext"))
db.Database.ExecuteSqlCommand("DELETE FROM TableC");
db.Database.ExecuteSqlCommand("DELETE FROM TableD");
StreamReader sr = new StreamReader(path);
while (!sr.EndOfStream)
string[] data = sr.ReadLine().Split(';');
string houseId = data[5];
House house = db.Houses.Find(houseId);
...
...
db.SaveChanges();
我不确定您真正需要哪个代码来回答我的问题,但如果有人要求提供特定代码,我很乐意发布更多信息。
更新 1 对 user89861 的回答
'db.Houses.ToList().Find(h => h.HouseId == houseId)' 抛出一个 'System.NullReferenceException' 类型的异常
" 贝 System.Data.Entity.Internal.Linq.InternalQuery
1.GetEnumerator()\r\n bei System.Data.Entity.Internal.Linq.InternalSet
1.GetEnumerator()\r\n 北 System.Data.Entity.Infrastructure.DbQuery1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator()\r\n bei System.Collections.Generic.List
1..ctor(IEnumerable1 collection)\r\n bei System.Linq.Enumerable.ToList[TSource](IEnumerable
1 source)"
【问题讨论】:
houseId
的值是多少?是你所期望的吗?
因为Find()
接受object key
作为参数,你可以向它传递任何东西,但首先你必须确保主键的类型确实与你的参数类型相同.您是否尝试过先将houseId
转换为int
(假设您的主键是int
)?
HouseId 是 nvarchar(128) 并且从 .csv 返回一个字符串。所以我认为数据类型是正确的。如前所述,在一种方法中它可以在另一种方法中运行,但它不能......而且它是同一行代码。
@David 但是当您单步执行代码时它是否具有实际值?它可能是空/空
复制过去有问题的行line 1, column 22
【参考方案1】:
如果未找到结果,Find() 将返回 null,因此您应该在访问其成员之前验证变量 house:
House house = db.Houses.Find(obj => return obj.HouseId == houseId;);
if (house == null) continue; //go to next iteration
【讨论】:
这对我不起作用,因为.Find
不接受 Lambda,它只接受对象列表。因此,您必须直接传递 houseId,即 var house = db.Houses.Find(houseId);
【参考方案2】:
我设法修复了这个奇怪的错误。在上下文构造器中,我只是添加了
public ContextEv(string dbName) : base("name=" + dbName)
//Database.SetInitializer(new DropCreateDatabaseAlways<ContextEv>());
但起初我不得不手动删除每个表,因为上面的代码并没有真正做到这一点。在让它运行一次之后,我不得不注释代码并再次运行,以便表格出现(为什么,我真的不知道......也许你们中的一些人知道)。
非常感谢大家的帮助!从你的回答中我真的学到了一些东西。
【讨论】:
【参考方案3】:.find()
方法不是强类型的,它使用对象参数,因此如果传递给它的参数的数据类型不兼容(例如,你传递一个整数值为预期)。
一般来说,我通常更喜欢使用.Where()
和/或.FirstOrDefault()
而不是.Find()
,因为您必须指定字段名称,例如
var usr=db.Employees.Where(
x => x.FirstName=="James" && x.LastName=="Bond").FirstOrDefault();
或者你可以直接写:
var usr=db.Employees.FirstOrDefault(
x => x.FirstName=="James" && x.LastName=="Bond");
如果没有找到记录,两者都返回 Null
。
它使代码更清晰,便于以后审查和可能需要的数据模型更改 - 考虑是否必须将字段添加到复合主键,例如上面示例中的出生日期:在这种情况下很容易看到您必须将其添加到 .Where
语句中。
由于它是强类型的,它还允许您通过右键单击并选择“转到定义”来使用智能感知来查找查询中涉及的字段的正确数据类型。
所有这些好处使故障排除变得更加容易。此外,.Where
和.FirstOrDefault
比.Find
更通用,因为它们支持多种序列(查看here at SO 以获得更详细的解释)并且不限于主键。
缺点是.Any()
、.Single()
、.First()
、.Where()
(以及它们的...OrDefault()
挂件)正在生成 SQL 查询,因此会以类似于您使用即席查询或调用存储过程。
但是,如果您不搜索主键,您必须使用它们。 LinqPad 工具很好地展示了 EF 如何将此类查询转换为 SQL 代码(假设 Northwind
作为示例数据库):
SELECT TOP (1)
[Extent1].[EmployeeID] AS [EmployeeID], -- primary key (PK)
[Extent1].[LastName] AS [LastName], [Extent1].[FirstName] AS [FirstName],
[Extent1].[Title] AS [Title], [Extent1].[TitleOfCourtesy] AS [TitleOfCourtesy],
[Extent1].[BirthDate] AS [BirthDate], [Extent1].[HireDate] AS [HireDate],
[Extent1].[Address] AS [Address], [Extent1].[City] AS [City],
[Extent1].[Region] AS [Region],
[Extent1].[PostalCode] AS [PostalCode], [Extent1].[Country] AS [Country],
[Extent1].[HomePhone] AS [HomePhone], [Extent1].[Extension] AS [Extension],
[Extent1].[Photo] AS [Photo], [Extent1].[Notes] AS [Notes],
[Extent1].[ReportsTo] AS [ReportsTo], [Extent1].[PhotoPath] AS [PhotoPath]
FROM [dbo].[Employees] AS [Extent1]
WHERE (N'James' = [Extent1].[FirstName]) AND (N'Bond' = [Extent1].[LastName])
在这里您可以看到尽可能限制结果集中的字段是有意义的,否则生成的查询将返回您不感兴趣的值并返回不必要的大量数据。
.Find()
方法仅适用于 EmployeeID
,因为这是主键 (PK)。对于查询中涉及的所有其他字段,您不能使用.Find()
,而必须使用其他查询方法(.Where()
、.Single()
、.First()
或.Any()
)。
在您的特定情况下,它看起来像(请注意,您应该只在需要时创建一个新对象,因此我已将其移至 if
语句):
string houseId = data[0];
House dummy = db.Houses.FirstOrDefault(x=>x.HouseId==houseId);
if (dummy==null)
House house = new House()
HouseId = houseId
;
db.Houses.Add(house);
但请注意,在这种情况下,可以使用.Any()
进一步优化:
string houseId = data[0];
if (!db.Houses.Any(x => x.HouseId == houseId))
House house = new House()
HouseId = houseId,
;
db.Houses.Add(house);
如果您无论如何都不需要从数据库中检索对象,这样可以避免返回不必要的数据(如前所述)。
【讨论】:
.Find()
尝试在访问数据库之前从 EF 内部缓存中检索实体。它不同于使用.Where()
/.FirstOrDefault()
。 .Any()
正在从数据库中检索对象,因此那里没有太多优化。
我有不同的信息,请read this 在 *** 关于缓存查询结果。有关缓存的更多详细信息,请查看here at MSDN。
请阅读this。也许我的术语具有误导性。 从 EF 内部缓存中检索实体 我的意思是检索由上下文跟踪的实体,如文档中所述
我认为这取决于您使用的是哪个 EF 版本以及哪个数据上下文。我指的是 EF 版本 6 和 DbContext。关于缓存查询我想引用,取自here:“当您通过其主键查询实体时,DbContext 将首先尝试从其一级缓存中检索它,然后默认从数据库中查询它。 "而“查询”表示任何 Linq 查询。
我也指的是EF版本6和DbContext
。恐怕那篇文章可能写错了:一个简单的控制台程序,有两个不同的DbContext
实例,两个 连续调用context.Houses.Single(h => h.HouseId == "HouseId")
/context.Houses.Find("HouseId")
,SQL 分析器可以很容易地显示 b>向数据库生成两个 SQL 查询(通过Any
/Single
/First
/Where
),而Find
只会产生一个 查询以上是关于实体框架在使用 Find 方法时抛出异常的主要内容,如果未能解决你的问题,请参考以下文章
如果 ETag 不匹配,如何使用 ETag 在插入时抛出异常(除非它是 *)