使用 DbContext Set<T>() 而不是在上下文中公开

Posted

技术标签:

【中文标题】使用 DbContext Set<T>() 而不是在上下文中公开【英文标题】:Using DbContext Set<T>() instead of exposing on the context 【发布时间】:2012-11-22 13:11:51 【问题描述】:

执行以下操作有什么不同:

public class UsersContext : DbContext

    public DbSet<User> Users  get; set; 

相对于使用上下文的Set&lt;T&gt; 方法:

public class UsersContext : DbContext



var db = new UsersContext();
var users = db.Set<User>();

这些有效地做同样的事情,给我一组用户,但除了你没有通过属性公开该组之外,还有什么大的不同吗?

【问题讨论】:

@abatishchev msdn.microsoft.com/en-us/library/gg696521(v=vs.103).aspx 没有 Set 方法 【参考方案1】:

为方便起见添加了Users属性,因此您无需记住所有表是什么以及对应的类是什么,您可以使用Intellisense查看上下文设计的所有表互动。最终结果在功能上等同于使用Set&lt;T&gt;

【讨论】:

用户可能只是一个实例,而在上下文中,我想知道 Set 方法,也返回单个实例? 你想要哪一个?您可以使用 System.Linq.Dynamic。 db.Set().Single("Id = @0", someId);只要主键是 User 对象上的 Id 或您查询的任何对象。【参考方案2】:

使用 Code-First 迁移时,前一种方法会为您带来好处,因为会自动检测到新实体。否则,我很确定它们是等价的。

【讨论】:

我没想到迁移!【参考方案3】:

这就是我设置通用 dbSet 的方式,效果很好

DbContext context = new MyContext();
DbSet<T> dbSet = context.Set<T>();

它是更明确的东西的通用版本,例如

DbContext context = new MyContext();
DbSet<User> dbSet = context.Set<User>();

无论哪种方式,它们都是相同的(当TUser 时)

【讨论】:

错误...好吗?我明白它是如何工作的。我想知道有什么区别/限制。 @Dismissile - 没有限制或差异,除了您可以通过使用泛型定义并传入类型来节省一些重复代码。 Set 应该始终返回单个实例以说明没有限制或差异,对吧? 查看反编译的源代码,DbSet 将是每个 DbContext 实例的同一个实例(例如,如果您多次调用 context.Set(),您将获得相同的引用DbSet,假设“上下文”从未改变实例)。这是在 DbContext 内部的 InternalContext 中完成的(它使用 Type 作为键保存一个内部字典)。【参考方案4】:

一个区别是 Set 方法接受任何类型,包括非实体,它不会抛出异常,只返回该类型的空集。所以如果你输入了错误的类名,你可能会搞砸。

【讨论】:

【参考方案5】:

我认为有一些不同。 让我使用问题中的示例。 假设我想做一个基于 User.FirstName 和 User.LastName 的 Any(用户表有更多字段)

方法一:UsersContext.Users.Any(u =&gt; u.FirstName.ToLower() == userObj.FirstName &amp;&amp; u.LastName.ToLower() == userObj.LastName);

方法二:(UsersContext.Set(typeof(User)) as IQueryable&lt;User&gt;).Any(u =&gt; u.FirstName.ToLower() == userObj.FirstName &amp;&amp; u.LastName.ToLower() == userObj.LastName);

我在 sql profiler 中检查了 Method1 中触发的查询是:

    exec sp_executesql N'SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[User] AS [Extent1]
    WHERE (((LOWER([Extent1].[FirstName])) = (LOWER(@p__linq__0))) AND ((LOWER([Extent1].[LastName])) = @p__linq__1)
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[User] AS [Extent2]
    WHERE (((LOWER([Extent2].[FirstName])) = (LOWER(@p__linq__0))) AND ([Extent2].[LastName] = @p__linq__1)
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]',@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'jack',@p__linq__1=N'saw'

来自方法2:

    SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName], 
[Extent1].[Email] AS [Email], 
.......other fields......
FROM [dbo].[Users] AS [Extent1]

该表有 40000 条记录,方法 1 大约需要 20 毫秒,而方法 2 大约需要 3500 毫秒。

【讨论】:

Set 可以采用泛型参数。尝试 Set().Any( ... ) @Dismissile 我试过 Set().Any() 并且它具有相同的行为。 那你在做一些很奇怪的事情,因为这三种方法都为我产生了相同的精确查询。 @Dismissile 你是对的。我发现了奇怪的部分。在 Method2 的情况下,即 Set().Any( ... ),我正在传递像 ValidationMethod&lt;User&gt;(u =&gt; u.FirstName.ToLower() == userObj.FirstName &amp;&amp; u.LastName.ToLower() == userObj.LastName) 这样的表达式,这个实习生会做 Set&lt;User&gt;().Any( .. expression passed as param.. ) 不确定确切的原因。仍在寻找它。会更新。 我正在跟踪问题here【参考方案6】:

我认为两种方法之间没有这样的区别,除了 Set&lt;User&gt;() 更适合实现像 Repository 模式这样的数据访问模式,因为 Set&lt;T&gt;() 方法的通用性。

【讨论】:

是的,我知道他们在哪里使用通用存储库,但我很好奇它是否有任何缺点。

以上是关于使用 DbContext Set<T>() 而不是在上下文中公开的主要内容,如果未能解决你的问题,请参考以下文章

如何使用autofac实现多个DbContext的注入

如何在 EF 中的 DbContext 上使用 Find 或 Find<T>?

EF执行savechanges失败然后直接返回页面的处理办法

DbContext通用数据仓储类

DbContext运行时动态附加上一个dbset

EntityFramework 4.1 DbContext 选择添加带有附加字符的 CAST 列