【问题标题】:Entity Framework throws exception when using Find-method实体框架在使用 Find 方法时抛出异常
【发布时间】: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.InternalSet
1.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

标签: c# .net asp.net-mvc entity-framework linq


【解决方案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 在 StackOverflow 关于缓存查询结果。有关缓存的更多详细信息,请查看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 只会产生一个 查询
    猜你喜欢
    • 1970-01-01
    • 2012-10-20
    • 2014-10-16
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 2016-10-08
    • 1970-01-01
    相关资源
    最近更新 更多