SQLite和EF6的愚蠢行为

时间:2019-11-29 08:11:48

标签: c# entity-framework sqlite

我无法相信我是遇到这个问题的第一人,但是在网上找不到任何类似的讨论。

这是简单的完整代码示例:

using SQLite.CodeFirst;
using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace ConsoleApp1
{
  class Program
  {
    static void Main(string[] args)
    {
      Entity entity = new Entity();
      Guid id = entity.Id;
      using (var context = new MyDbContext())
      {
        context.Entities.Add(entity);
        context.SaveChanges();

        // this finds an entry
        var item = context.Entities.Find(id);
      }

      using (var context = new MyDbContext())
      {
        // here it returns null
        var item = context.Entities.Find(id);
      }
    }
  }

  public class MyDbContext : DbContext
  {
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
      Database.SetInitializer(new SqliteDropCreateDatabaseWhenModelChanges<MyDbContext>(modelBuilder));
    }

    public MyDbContext() : base("MyConnection") {}

    public DbSet<Entity> Entities { get; set; }
  }

  public class Entity
  {
    [Key]
    public Guid Id { get; set; } = Guid.Parse("D46D98F3-C262-468A-9C28-83D81080CF18");

    public string Name { get; set; } = "Test";
  }
}

该问题已在代码中标记。第一个“查找”返回新添加的条目。

但是获取上下文的新实例时,找不到该条目。

即使我第二次运行该应用程序,也跳过了将条目添加到表中的代码,它也找不到该项目。问题似乎不是“查找”方法,因为T尝试了其他几个具有相同结果的linq语句。

当我首先在搜索之前从表中获取所有项目时,它就可以使用“查找”,而不可以使用linq。

以下是示例:

        using (var context = new MyDbContext())
        {
            // this returns all items
            var allItems = context.Entities.ToArrayAsync().Result;
            // this finds the item
            var item1 = context.Entities.Find(id);
            // this doesn't find the item
            var item2 = context.Entities.Where(x => x.Id == id).FirstOrDefault();
        }

        using (var context = new MyDbContext())
        {
            // this doesn't find the item
            var item1 = context.Entities.Find(id);
            // this also doesn't find the item
            var item2 = context.Entities.Where(x => x.Id == id).FirstOrDefault();
        }

有人有解释吗?将键更改为string或int而不是GUID,它可以按预期工作。

3 个答案:

答案 0 :(得分:1)

该bug上的链接使我想到了使用连接字符串添加“ BinaryGUID = True;”的想法。

比预期的要好。谢谢。

答案 1 :(得分:0)

好的,我会尝试回答一些言论。

我了解SQLite中的Guid使用情况。但是因为我想处理外键,所以我认为使用Guid代替DB生成的键更容易。

看一下表,发现Guid按预期存储为16字节BLOB,并且DB ar中的字节与我使用的Guid相对应。

这是该表的表创建语句:

创建表“实体”([Id] uniqueidentifier NOT NULL PRIMARY KEY,[Name] nvarchar)

是的,我已经在关闭应用程序后阅读了数据库。第二个代码示例是我为此使用的示例。在这里,我描述了我的第一个用法块,它使用Find方法查找条目,但不使用Linq查找条目,并且前提是我之前才获取整个表(请参阅我的评论)。不会像在第二个用法块中那样先读取所有条目,找不到任何内容。

我知道EF使用ADO.NET,但是EF“生成” SQL语句以查询数据库,并且在Find和linq之间可能存在某种错误/区别,并且某些内容被缓存了,因为它似乎可以正常工作我以前拿过整张桌子。生成SQL语句是EF的一部分,而不是CodeFirst。因此,我认为该问题与CF无关。即使使用现有的数据库,在没有CF的情况下也具有相同的行为。

那里不是内存数据库,我在一个应用程序中使用相同的连接字符串在两个块中运行它们。而且,如果我使用数据库浏览器更改“名称”字段,我也会读取更改。

我发布了整个代码进行测试。可能有人花了一些时间并可以重现该行为。连接字符串是通用的:

<add name="MyConnection" connectionString="Data Source=c:\test\test.s3db" providerName="System.Data.SQLite.EF6" />

答案 2 :(得分:0)

问题在于SQLite数据库提供程序将GUID转换为Blob。然后,此Blob作为字节数组而不是字符串存储在数据库中。然后,当您尝试直接使用LINQ查询数据库时,问题就会显现出来,因为

context.Entities.Where(x => x.Id == id).FirstOrDefault()

获取翻译为

SELECT Key, Name
FROM Entity
WHERE Entity.Key = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

因此它正在将字符串文字与字节数组进行比较,这显然会返回错误的结果。将所有条目加载到内存中,然后对其进行查询,将导致GUID从其字节数组表示形式转换为实际的GUID,LINQ可以正确比较并选择正确的GUID。

此问题的解决方案是在连接字符串中添加BinaryGUID=True部分。