【问题标题】:Entity framework and local cache of database tables实体框架和数据库表的本地缓存
【发布时间】:2017-06-09 16:47:30
【问题描述】:

我正在开发一个使用实体框架的应用程序,但我遇到了一些性能问题。 想象一个带有一些组合框和数据网格的用户界面(C#、WPF)。每次在组合框中选择一个值时,它都会更改数据在网格中显示的条件。 看起来实体框架在缓存方面没有我想象的那么灵活。由于条件的变化,底层 sql 总是会略有不同(= 没有 EF 缓存),并且每次单元格更新都会导致对数据库的请求。

有什么方法可以在本地缓存表(使用有效的导航属性)并仍然使用 linq 进行选择等,而不会对数据库产生任何请求?

  • 这些表很小,所以我预计不会出现任何性能问题。 (一世 猜测本地排序等。否则可能是一个问题,因为没有使用数据库索引。)
  • 我不需要对表的写入权限,所以如果 有一种简单的方法可以制作表格的深层副本并分离 从数据库连接可能没问题。
  • 我不允许安装任何第三方工具。

也许实体框架从一开始就是一个糟糕的选择,但是使用那些生成的类和linq真的很方便,而不是手动编写大量的类和sql。 (我仍然需要实现一些缓存。)

【问题讨论】:

  • 我认为你应该展示一些你的代码。在将 Queryable 提供给组合框和数据网格(有效地从 EF linq 切换到内存 linq)之前,这听起来像是 Queryable 上的一个简单 ToList() 可能对解决您的问题大有帮助。
  • EntityFramework(EF) 一旦你得到对象就不必使用了。它只是一个用于获取数据的对象模型层。 LINQ 与 EF 是分开的,因为 EF 将数据库对象化以进行数据检索。您可以简单地设置一个存储库模式,您可以在其中在设定的持续时间内执行检索方法并缓存这些对象。我可以很容易地做到 var ls = new List{ "a", "b", "c", "d", "e"}。 ls.Distinct().Skip(2).Take(3).ToList().ForEach(x => Console.WriteLine($"我有 {x}")); EF与Linq无关,它的用途,LINQ是查询扩展方法的框架,EF是数据检索
  • 另外,您在这里一针见血:“但使用那些生成的类真的很方便”。 T4 模板(生成这些类的内容)是 EF 真正闪耀恕我直言的地方。如果需要,您也可以覆盖编写代码的代码也生成生成的数据。 EG:假设我有一个表 colors: 它只存储一个 id 和值 'red'、'blue'、'yellow'。检索这有点愚蠢,所以我什至可以破解 T4 以生成枚举生成的对象,这些对象是数据库中的内容。可能不适合生产,因为数据库更改需要重新运行 T4 模板。
  • Paul-Jan,这对于组合框来说没问题,但对于网格来说并不容易。单元格中的条目根据其他表中的值以不同方式组合来自多个表的数据,因此很难在单个查询中获取所有列的数据。 (但可能并非不可能。)
  • Djangojazz,问题是即使我已经检索了所有条目,当我遵循导航属性时,它似乎会触发数据库请求,例如“tableA.ToList().Where(someCondition)” 不会调用数据库,如果我已经在缓存中有 tableA 条目,但类似“tableA.ToList().navigationPropertyRefTableB.Where(someCondition).something” 似乎总是即使所有请求的数据已经被重新接收,也会导致对数据库的调用。 (我尝试在任何重要的地方使用“Include”和“ToList”。)

标签: c# wpf entity-framework linq caching


【解决方案1】:

有什么办法可以在本地缓存表吗?

这是DbContext默认执行的操作,您可以通过一种简单的方式使用该功能。这是要遵循的基本模式:

context.Products.Where(p => <some intial condion>).Load();
var dataSource = context.Product.Local.Where(p => <some flexible condition>);

请注意,在第 2 行中使用了 Localcollection。这是一个DbSetproperty,它从上下文的缓存中返回实体。

具有有效的导航属性

Load()statement 加载的任何相关实体都将通过关系修复自动相互连接。所以如果Product 有一个集合Components,你可以这样做:

context.Components.Where(c => <some intial condion>).Load();

如果这会加载上面加载的产品的所有组件,您会看到他们的 Components 集合现在已填充。

结合这两个步骤的另一种方法是:

context.Products.Where(p => <some intial condion>)
       .Include(p => p.Components)
       .Load();

如果有很多相关表,您必须在单个Load 语句和Load 语句与Include 之间找到平衡,因为一个语句中的许多Includes 可能会影响性能。

仍然使用 linq 进行选择

如上图:灵活的条件。

不对数据库产生任何请求

如果您始终只处理Local 集合,则这些语句将永远不会查询数据库。但是,寻址导航属性仍可能导致延迟加载。如果你这样做......

context.Products.Where().Load();
context.Components.Where().Load();

...这确实会填充product.Components 集合,但不会将它们标记为已加载,而context.Products.Include(p =&gt; p.Components) 会。所以在第一种情况下,寻址product.Components 将触发延迟加载。同样,处理根本不加载实体的导航属性也会触发延迟加载。因此,要绝对确保不会触发任何数据库交互,您应该禁用延迟加载,或者通过 ...

context.Configuration.LazyLoadingEnabled = false;

...或...

context.Configuration.ProxyCreationEnabled = false;

后一个选项强制 EF 创建不能延迟加载的简单 POCO 对象。

因此,使用这些技术,您可以将上下文用作连接实体的本地缓存。这是上下文应该是短暂的规则的一个例外。

注意事项

关系修复doesn't work for many-to-many associations。假设Product和Manufacturer之间存在m:n关系,那么……

context.Products.Where().Load();
context.Manufacturers.Where().Load();

... 不会填充 product.Manufacturers 和 manufacturer.Products。多对多关联应该由Include加载:

context.Products.Where()
       .Include(p => p.Manufacturers)
       .Load();

【讨论】:

  • 如何将实体标记为已加载?我希望我的模型支持延迟加载,但希望能够预先加载数据并且不再触发延迟加载。当我这样做时,当我访问“product.Components”(如你所说)时,它会触发延迟加载。那么我该如何解决呢?另外......有什么可以解决同样的问题,但在多对多的关系中?我正在使用 EF Core 3.x
  • 您不能将实体标记为已加载。当 EF 加载导航属性时,它们被标记为由 EF 加载,或者(并且专门)由Include、Load(导航属性)或延迟加载。您唯一能做的就是关闭延迟加载。
【解决方案2】:

让我稍微了解一下,因为我也有我在 WPF 中工作并遵循 MVVM 模式的生产应用程序。你可能不会,如果你不知道我在说什么,我建议你这样做。假设我有一个包含人员表的数据库表,它只有三列:PersonId、FirstName、LastName。我目前只有两行,我的名字和我妻子的名字。我只想检索一次数据,但稍后我可能想更改它。这当然是一个简化的例子:

XAML:

<StackPanel>
    <DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
      <DataGrid.Columns>
        <DataGridTextColumn Header="PersonId" Binding="{Binding PersonId}" />
        <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" />
        <DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
      </DataGrid.Columns>
    </DataGrid>
    <TextBox Text="{Binding Text}" />
    <Button Command="{Binding CommandGetFirstName}" Height="30" Content="Get By First Name Above" />
</StackPanel>

这是使用 MVVM 绑定的,所以我的 MainViewModel 是这样的:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WPFCSharpTesting
{
  public class MainWindowViewModel : INotifyPropertyChanged
  {
    private string _text;

    public string Text
    {
      get { return _text; }
      set
      {
        _text = value;
        OnPropertyChanged(nameof(Text));
      }
    }

    private ObservableCollection<tePerson> _people;

    public ObservableCollection<tePerson> People
    {
      get { return _people; }
      set
      {
        _people = value;
        OnPropertyChanged(nameof(People));
      }
    }

    private readonly List<tePerson> _allPeople;


    public MainWindowViewModel()
    {
      Text = "Brett";       
      using (var context = new TesterEntities())
      {
        _allPeople = context.tePerson.ToList();
      }

      People = new ObservableCollection<tePerson>(_allPeople);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(String info)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
    }

    DelegateCommand _CommandGetFirstName;

    public ICommand CommandGetFirstName
    {
      get
      {
        if (_CommandGetFirstName == null)
        {
          _CommandGetFirstName = new DelegateCommand(param => this.CommandGetByFirstNameExecute());
        }
        return _CommandGetFirstName;
      }
    }

    private void CommandGetByFirstNameExecute()
    {
      var newitems = _allPeople.Exists(x => x.FirstName == Text) ? _allPeople.Where(x => x.FirstName == Text)?.ToList() : _allPeople;
      People = new ObservableCollection<tePerson>(newitems);
    }

这里的关键是我的构造函数中发生的事情。我正在使用一个只读变量 _allPeople,它是私有的,并将我想要稍后操作的信息存储在那里。一旦 _allPeople 有了数据,我就不需要再次触摸“上下文”来访问数据库。我现在可以和 _allPeople 一起玩,因为它是根据我的需要分离出来的。当我想向前端 WPF 公开用户看到的内容时,他们将看到一个可观察的集合,我可以根据需要从我的缓存设置中更新。这是一个超级简单的过度简化。通常,许多开发人员最终会做一个完整的存储库模式,他们有一个或多个项目相关的项目仅用于存储数据和执行 CRUD 操作。恕我直言,这通常是首选方法,因为您可以根据需要拼凑其他东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-12
    • 1970-01-01
    • 2018-10-29
    • 2013-10-02
    • 1970-01-01
    • 2012-12-13
    • 2015-04-24
    相关资源
    最近更新 更多