【问题标题】:Unexpected behavior in entity framework实体框架中的意外行为
【发布时间】:2014-09-16 01:41:15
【问题描述】:

我遇到了我认为实体框架的一个非常奇怪的情况。基本上,如果我直接使用 sql 命令更新一行,当我通过 linq 检索该行时,它没有更新的信息。有关详细信息,请参阅下面的示例。

首先我创建了一个简单的数据库表

CREATE TABLE dbo.Foo (
    Id int NOT NULL PRIMARY KEY IDENTITY(1,1),
    Name varchar(50) NULL
) 

然后我创建了一个控制台应用程序来将一个对象添加到数据库中,使用 sql 命令对其进行更新,然后检索刚刚创建的对象。这里是:

public class FooContext : DbContext
{

    public FooContext() : base("FooConnectionString")
    {

    }

    public IDbSet<Foo> Foo { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Foo>().ToTable("Foo");
        base.OnModelCreating(modelBuilder);
    }

}

public class Foo
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Program
{
    static void Main(string[] args)
    {

        //setup the context
        var context = new FooContext();

        //add the row
        var foo = new Foo()
        {
            Name = "Before"
        };
        context.Foo.Add(foo);
        context.SaveChanges();

        //update the name
        context.Database.ExecuteSqlCommand("UPDATE Foo Set Name = 'After' WHERE Id = " + foo.Id);

        //get the new foo
        var newFoo = context.Foo.FirstOrDefault(x => x.Id == foo.Id);

        //I would expect the name to be 'After' but it is 'Before'
        Console.WriteLine(string.Format("The new name is: {0}", newFoo.Name));
        Console.ReadLine();

    }

}

底部的写入行打印出“之前”,但我希望它打印出“之后”。奇怪的是,如果我运行分析器,我会看到 sql 查询运行,如果我自己在管理工作室中运行查询,它会返回“After”作为名称。我正在运行 sql server 2014。

有人可以帮我理解这里发生了什么吗?

更新:

它将转到 FirstOrDefault 行上的数据库。请参阅 sql profiler 的附加屏幕截图。

所以我的问题真的是这样的:

1) 如果它正在缓存,它不应该不去数据库吗?这是 EF 中的错误吗?

2) 如果它要去数据库并消耗资源,EF 不应该更新对象。

【问题讨论】:

    标签: c# linq entity-framework entity-framework-6


    【解决方案1】:

    FooContext 包括更改跟踪和缓存,因此从查询返回的内存中对象与您之前添加的实例相同。调用 SaveChanges() 会清除上下文,而 FooContext 不知道数据库中发生的更改。

    这通常是一件好事——不会为每个操作都进行昂贵的数据库调用。

    在您的示例中,尝试从新的FooContext 进行相同的查询,您应该会看到“After”。

    更新

    回答您更新的问题,是的,您是对的。我之前错过了你使用FirstOrDefault()。如果您使用context.Find(foo.Id),正如我错误地假设的那样,那么将不会有任何查询。

    至于为什么内存中的对象没有更新以反映数据库的变化,除了推测之外,我需要做一些研究来做任何事情。也就是说,这是我的猜测:

    • 数据库上下文的一个实例不能返回多个相同实体的实例。在一个工作单元内,我们必须能够依赖上下文返回实体的相同实例。否则,我们可能会根据不同的条件进行查询,并获得代表相同概念实体的 3 个对象。到那时,上下文如何处理对其中任何一个的更改?如果将其中两个的名称更改为不同的值,然后调用 SaveChanges() 怎么办 - 会发生什么?
    • 鉴于上下文最多跟踪每个实体的单个实例,为什么 EF 不能在执行查询时更新该实体?如果存在未决的内存更改,EF 甚至可以丢弃该更改,因为它知道这些更改。
      • 我认为部分答案是在大型实体和大型结果集中对所有列进行区分会导致性能过高。
      • 我认为答案的很大一部分是它执行一个简单的 SELECT 语句不应该有可能导致整个系统的副作用。实体可能会按某些属性的值进行分组或循环,并在不确定的时间更改该属性的值,因此 SELECT 查询非常不合理。

    【讨论】:

    • 是的,它是缓存,也可以在同一上下文中使用context.Foo.AsNoTracking().FirstOrDefault
    • 杰伊,谢谢你的回答。我想我的后续问题是这样的:我知道它有一个缓存对象,但在它之外可能发生了一些变化。如果您已经访问数据库并运行了一个查询,为什么不使用新的内存对象更新缓存对象?
    • @zgirod 第二个查询(获取)根本不会进入数据库,因为您正在请求上下文已经知道的项目(通过 Id)。另一方面,如果您对 EF 对象进行了另一项更改并尝试保存更改,由于乐观并发和在 EF 之外进行的更改,您会遇到异常:msdn.microsoft.com/en-us/data/JJ592904.aspx
    • @Jay 请查看更新后的问题。它将进入 FirstOrDefault 行上的数据库。我在运行 sql profiler 时看到了查询。这就是我觉得很困惑的地方。对我来说,EF要么不应该去数据库,因为它被缓存,要么去数据库并更新对象。在这种情况下,EF 将访问数据库但不更新对象。奇怪!
    • @Jay 你这个男人!谢谢,希望我能多次投票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-05
    • 1970-01-01
    • 2023-04-02
    相关资源
    最近更新 更多