实体框架在使用 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.InternalQuery1.GetEnumerator()\r\n bei System.Data.Entity.Internal.Linq.InternalSet1.GetEnumerator()\r\n 北 System.Data.Entity.Infrastructure.DbQuery1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator()\r\n bei System.Collections.Generic.List1..ctor(IEnumerable1 collection)\r\n bei System.Linq.Enumerable.ToList[TSource](IEnumerable1 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 =&gt; h.HouseId == "HouseId")/context.Houses.Find("HouseId"),SQL 分析器可以很容易地显示 b>向数据库生成两个 SQL 查询(通过Any/Single/First/Where),而Find 只会产生一个 查询

以上是关于实体框架在使用 Find 方法时抛出异常的主要内容,如果未能解决你的问题,请参考以下文章

VS2015 在添加新的实体数据模型时抛出异常

Java 异常 重写方法时抛出异常

如果 ETag 不匹配,如何使用 ETag 在插入时抛出异常(除非它是 *)

在继承中重写方法时抛出异常的问题

测试期间的Spring Boot JPA事务 - 不会在插入时抛出密钥违例异常

JMockit 期望 API:如何在方法/构造函数调用时抛出异常