【问题标题】:Silly behaviour with SQLite and EF6SQLite 和 EF6 的愚蠢行为
【发布时间】:2019-11-29 18:42:27
【问题描述】:

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

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

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,它可以按预期工作。

【问题讨论】:

  • SQLite 不支持 GUID,您必须将值存储为字符串或 BLOB。 GUID 必须存储为字符串或 BLOB。不过,您还没有提供表模式,因此很难重现任何内容。您是否尝试在关闭应用程序后读取 SQLite 文件? EF 不与数据库本身通信,它使用 ADO.NET 和配置的提供程序
  • I can't beleave I'm the first person running into that problem, 这是一个非常强烈的指标,表明问题出在代码中,而不是库中。之前有关于GUID in SQLite 的讨论。 GUID 是可怕的键,尤其是在像 SQLite 这样的嵌入式数据库中。它们占用的空间是 64 位长度的 2 或 4 倍,除非您使用顺序 GUID 算法,否则它们会导致碎片
  • 您也没有提到您使用 SQLite.CodeFirst。是不是因为配置错误或内存中的连接字符串,每个 DbContext 实例都会创建一个 new 数据库?您是否尝试在运行程序时或在程序完成后从数据库中读取?如果您配置预写日志记录,您可以让多个应用程序使用 SQLite 客户端应用程序从同一个数据库中读取数据
  • 看起来以前有关于 SQLite、GUID 和 EF like this one from 2014 的问题。当时,问题是由 SQLite 自己的 ADO.NET 提供程序中的错误引起的。您使用的是哪个提供商?你的连接字符串是什么样的?
  • 问题是因为 SQLite 无法处理 GUID,所以 GUID 正在被转换为 BLOB。这会导致从 LINQ 生成的 SQL 查询失败。这就解释了为什么将它全部加载到内存中,然后查询它的工作原理,因为一旦加载到内存中,它就可以正确比较 GUID

标签: c# entity-framework sqlite


【解决方案1】:

关于该错误的链接让我想到了使用连接字符串添加“BinaryGUID=True;”。

而且比预期的效果好。谢谢。

【讨论】:

    【解决方案2】:

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

    我知道 SQLite 中的 Guid 用法。但是因为我想处理外键,所以我认为使用 Guids 而不是 DB 生成的键更容易。

    查看表格显示,Guid 按预期存储为 16 字节 BLOB,DB ar 中的字节对应于我使用的 Guid。

    这是该表的表创建语句:

    CREATE TABLE "Entities" ([Id] uniqueidentifier NOT NULL PRIMARY KEY, [Name] nvarchar)

    是的,我在关闭应用程序后阅读了数据库。第二个代码示例是我使用的那个。我在那里描述了,我第一次使用块它使用 Find 方法找到条目,但不是使用 Linq 并且只有当我之前获取整个表时(参见我的 cmets)。在第二个使用块中没有读取所有条目之前没有找到。

    我知道,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" />
    

    【讨论】:

    • 更新问题。不要在答案中发布额外的 cmets。答案旨在实际回答问题
    【解决方案3】:

    问题在于 SQLite DB 提供程序将 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 可以正确比较并选择正确的。

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

    【讨论】:

      猜你喜欢
      • 2011-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-15
      • 1970-01-01
      • 2021-07-07
      • 2011-08-24
      相关资源
      最近更新 更多