问题 是DbSet 。本地使用的东西需要特别小心吗?


几天来,我一直在努力从存储库中检索我的实体(DbContext)。

我试图在原子动作中保存所有实体。因此,不同的实体一起代表了对我有价值的东西。如果所有实体都是“有效”,那么我可以将它们全部保存到数据库中。实体“a”已存储在我的存储库中,需要检索以“验证”实体“b”。

这就是问题出现的地方。我的存储库依赖于 DbSet<TEntity> 适用于Linq2Sql的类(Include() 导航属性,例如)。但是, DbSet<TEntity> 不包含处于“已添加”状态的实体。

所以我(据我所知)有两个选择:

  • 使用 ChangeTracker 查看哪些实体可用,并根据它们将它们查询到一个集合中 EntityState
  • 使用 DbSet<TEntity>.Local 属性。

ChangeTracker 似乎需要一些额外的努力来让它以一种方式工作,以便我可以使用Linq2Sql Include() 导航属性例如

DbSet<TEntity>.Local 对我来说似乎有点奇怪。它可能只是名字。我只是读了一些表现不佳的东西(比DbSet <>本身慢)。不确定这是否是虚假陈述。

具有重要EntityFramework经验的人能否对此有所了解?什么是“明智”的道路?或者我看到鬼魂,我应该总是使用 .Local 属性?

使用代码示例更新


出了什么问题的一个例子

    public void AddAndRetrieveUncommittedTenant()
    {
        _tenantRepository = new TenantRepository(new TenantApplicationTestContext());

        const string tenantName = "testtenant";

        // Create the tenant, but not call `SaveChanges` yet until all entities are validated 
        _tenantRepository.Create(tenantName);

        //
        // Some other code
        //

        var tenant = _tenantRepository.GetTenants().FirstOrDefault(entity => entity.Name.Equals(tenantName));

        // The tenant will be null, because I did not call save changes yet,
        // and the implementation of the Repository uses a DbSet<TEntity>
        // instead of the DbSet<TEntity>.Local.
        Assert.IsNotNull(tenant);

        // Can I safely use DbSet<TEntity>.Local ? Or should I play 
        // around with DbContext.ChangeTracker instead?
    }

我想如何使用我的一个例子 Repository

在我的 Repository 我有这个方法:

    public IQueryable<TEntity> GetAll()
    {
        return Context.Set<TEntity>().AsQueryable();
    }

我以这种方式在业务代码中使用它:

    public List<Case> GetCasesForUser(User user)
    {
        return _repository.GetAll().
            Where(@case => @case.Owner.EmailAddress.Equals(user.EmailAddress)).
            Include(@case => @case.Type).
            Include(@case => @case.Owner).
            ToList();
    }

这主要是我更喜欢坚持的原因 DbSet 像变量。我需要灵活性 Include 导航属性。如果我用的话 ChangeTracker 我检索一个实体 List,这不允许我在以后的时间点延迟加载相关的实体。

如果这接近难以理解的崩溃* t,那么请告诉我,以便我可以改进这个问题。我迫切需要一个答案。

很多提前!


7386
2018-02-26 16:59


起源

即使你可能不相信它提供了价值,提供代码仍然可以为试图回答的人提供背景。你能添加链接到你的位置吗? read something that it is not performing very well? - Ryan Gates
你能否告诉我们你想要达到的目标的更多细节?您想要的具有验证的代码示例也很有用。 - Kirill Bestemyanov
我将用一些代码示例更新问题。这是关于表演的帖子(stackoverflow.com/questions/12223291/...)。我知道你对此不太确定 - bas
第一个修复可能是制作 Create 方法返回新实体。 - Gert Arnold


答案:


如果您希望能够“轻松”对DbSet发出查询并让它找到新创建的项目,那么您需要在创建每个实体后调用SaveChanges()。如果您使用“工作单元”样式方法来处理持久性实体,这实际上没有问题,因为您可以让工作单元将UoW中的所有操作包装为DB事务(即,当UoW创建新的TransactionScope时创建,并在UoW完成时调用Commit())。使用此结构,更改将发送到DB,并且对DbSet可见,但对其他UoW不可见(以您使用的任何隔离级别为模)。

如果你不想要这个开销,那么你需要修改你的代码以在适当的时候使用Local(这可能涉及查看Local,然后如果你没找到你的话就发出一个针对DbSet的查询正在寻找)。在这些情况下,DbSet上的Find()方法也非常有用。它将在Local或DB中按主键查找实体。因此,如果您只需要通过主键查找项目,这非常方便(并且还具有性能优势)。


12
2017-12-06 18:34





正如Terry Coatta所提到的,如果你不想先保存记录,最好的办法是检查两个来源。

例如:

public Person LookupPerson(string emailAddress, DateTime effectiveDate)
{
    Expression<Func<Person, bool>> criteria = 
        p =>
            p.EmailAddress == emailAddress &&
            p.EffectiveDate == effectiveDate;

    return LookupPerson(_context.ObjectSet<Person>.Local.AsQueryable(), criteria) ?? // Search local
           LookupPerson(_context.ObjectSet<Person>.AsQueryable(), criteria); // Search database
}

private Person LookupPerson(IQueryable<Person> source, Expression<Func<Person, bool>> predicate)
{
    return source.FirstOrDefault(predicate);
}

4
2017-09-22 23:06





对于那些追随者,我遇到了一些类似的问题,并决定尝试.Concat方法。我没有进行过广泛的性能测试,所以有了比我更多的知识的人应该随意加入。

本质上,为了将功能正确地分解成更小的块,我最终得到了一种情况,其中我有一个方法不知道当前UoW中对同一方法的连续或先前调用。所以我这样做了:

var context = new MyDbContextClass();
var emp = context.Employees.Concat(context.Employees.Local).FirstOrDefault(e => e.Name.Contains("some name"));

0
2018-05-15 18:29



这看起来像一个简洁而不太突兀的解决方法。不幸的是它对我不起作用。我的谓词有点复杂,我打来电话 Where() 代替 FirstOrDefault()。我在访问结果集合时得到了这个: An exception of type 'System.NotSupportedException' occurred in EntityFramework.dll but was not handled in user code Additional information: Unable to create a constant value of type 'Employee'. Only primitive types or enumeration types are supported in this context. 因此,请分别查询两个集合。 - AronVanAmmers
不完全确定,但我认为这会在某些情况下严重降低性能,因为它会将所有员工拉出数据库,将其转换为对象,然后针对它们运行查询。你(我们)真的需要一个能做的系统:context.Employees.Where(predicate).Concat(context.Employees.Local.Where(predictae)) - C.List