实体框架和数据库表的本地缓存

时间:2017-01-24 17:56:40

标签: c# wpf entity-framework linq caching

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

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

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

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

2 个答案:

答案 0 :(得分:8)

  

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

这是DbContext默认执行的操作,您可以轻松使用该功能。以下是基本模式:

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

请注意,在第2行中使用了Local集合。这是一个DbSet属性,它从上下文缓存中返回实体。

  

具有工作导航属性

Load()语句加载的任何相关实体将通过 relationship fixup 自动相互连接。因此,如果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 => p.Components)会这样做。因此,在第一种情况下,寻址product.Components将触发延迟加载。类似地,解决实体未加载的导航属性也将触发延迟加载。因此,为了确保不会触发任何数据库交互,您应该通过...

禁用延迟加载
context.Configuration.LazyLoadingEnabled = false;

......或......

context.Configuration.ProxyCreationEnabled = false;

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

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

一个警告

关系修正doesn't work for many-to-many associations。假设m:nProduct之间存在Manufacturer关系,那么......

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

...不会填充product.Manufacturersmanufacturer.Products。应该通过Include加载多对多关联:

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

答案 1 :(得分:0)

让我稍微了解一下,因为我还有我在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拥有数据,我就不需要触及&#39;上下文&#39;再次打到数据库。我现在可以和_allPeople一起玩,因为它自己的收藏已经分离了我需要的东西。当我想向我的前端WPF公开用户看到的内容时,他们会看到一个可观察的集合,我可以根据需要从我的缓存设置中更新。这是一个超简单的简单过程。通常,许多开发人员最终会执行整个存储库模式,其中他们有一个或多个项目,仅用于存储数据和执行CRUD操作。这通常是恕我直言的首选方法,因为您可以根据需要拼凑其他东西。