LINQ to SQL:单例还是using,最佳实践

7
在使用 LINQ to SQL(在 ASP.NET MVC 应用程序中)时,最好的做法是创建 DataContext 的“单例”,如下所示:
partial class db
{
    static db _db = new db(global::data.Properties.Settings.Default.nanocrmConnectionString, new AttributeMappingSource());

    public static db GetInstance()
    {
        return _db;
    }
}

或者在using中需要时检索新实例:

using (db _db = new db())
{
    ...
}

using的使用会对代码产生一些限制,因此我更喜欢使用单例模式。这种做法是否奇怪?

更新:
我使用单例模式的原因解释如下:

public class UserGroupRepository
{
    public static IQueryable<Group> RolesFor(string username)
    {
        User user = UserRepository.WithUsername(username);

        return from g in db.GetInstance().Groups
                join ug in db.GetInstance().UsersGroups on g.Id equals ug.GroupId
                where ug.UserId == user.Id
                select g;
    }
}

我有这个方法。由于它返回IQueryable - 我可以继续组合查询而不执行它,因此仅返回惰性结果。
如果我使用using重写相同的代码 - 我将无法返回IQueryable(因为数据库将被释放并且IQueryable也将丢失),我会将其更改为List。现在,此方法将返回“庞大”的List,我将在先前的函数中过滤数据。希望我已经描述得足够详细了。

出于好奇,使用“using”会产生什么限制?也许有一种不错的方法可以绕过它们... - LorenVS
@LorenVS:我敢打赌“限制”是因为我是新手,所以我相信使用using也有优雅的解决方案;-) - zerkms
关于您的更新,我在我的帖子中添加了一次编辑。 - LorenVS
@zerkms,从你对我的和其他帖子的回复来看,我觉得你似乎认为Linq2Sql数据上下文是一个昂贵的对象...这不是事实,数据上下文是(通常)可以快速创建和处理的廉价对象。与检索数据的实际过程相比,创建数据上下文对象的过程非常小。 - LorenVS
是的,谢谢解释 :-) 我真的以为datacontext会产生创建到数据库的开销(这是昂贵的)。如果是错误的 - 那么就不需要单例模式;-) - zerkms
添加另一个编辑以进一步阐明数据上下文和 SQL 连接之间的关系。 - LorenVS
3个回答

5

Linq to Sql数据上下文不是线程安全的,只应在单个线程的上下文中使用。使用单例模式不仅违反标准的Linq2Sql实践,而且如果您的应用程序在任何严重负载下运行,将导致严重问题。

编辑:

针对您对using块的限制,尝试将RolesFor方法实现为扩展方法:

public static IQueryable<Group> GetUserRoles(this Database db, string username)
{
        return from g in db.GetInstance().Groups
                join ug in db.GetInstance().UsersGroups on g.Id equals ug.GroupId
                where ug.UserId == user.Id
                select g;
}

这将允许您在任何地方使用using块来调用您的方法:
using(Database db = createContext())
{
     IQueryable<Group> queryable = db.GetUserRoles("MyUsername");
     // from here on you can query the queryable object
     var groups = (from g in queryable
                   where g.Name == "MyRole"
                   select g).ToList();
}

编辑2

针对您关于为每个数据上下文实例打开另一个与sql服务器的连接的评论。创建数据上下文不会打开与sql服务器的连接,但是每个实际操作都会打开连接。无论您创建1个还是4个数据上下文,如果您对数据库执行4个操作,则会打开4个sql连接。但是,请记住,.NET使用sql服务器连接池,因此每个操作不需要创建全新的SqlConnection,而只需要从连接池检索现有的SqlConnection并重新打开连接。


这样做虽然可行,但浪费了 db 类,并且在概念上是错误的。 - zerkms
1
@zerkms,请澄清您的意思,哪些方面在概念上是错误的?浪费db类指的是什么? - LorenVS
1
@zerkms,你可以将扩展方法转换为普通方法,并将其作为参数传递给Db对象,然后将该方法放入您的usergrouprepository中。 - LorenVS
1
@zerkms,如果您非常担心功能的外观,那么您唯一真正正确的选择就是创建一个实现IDisposable和IQueryable<T>的类,并包含一个Db类的实例... 这样的类将在内部创建适当的IQueryable,并公开IQueryable <T>的方法。然后,您可以在using语句中使用该类(您的类的dispose方法将调用db对象的dispose方法,这是完全安全的)。 - LorenVS
1
@zerkms,问题不在于你的单例模式不是线程安全的。你的单例模式可以正常工作。问题在于实际的DataContext对象不是线程安全的。你的单例模式确保只创建一个DataContext,无论线程条件如何(它做得很好),但是当多个线程访问相同的Linq2Sql DataContext时,仍然存在问题。 - LorenVS
显示剩余8条评论

2
Linq to SQL希望您每个操作都创建一个上下文。实际上,数据加载选项只能设置执行第一个查询,因此,如果要进行加载提示,则必须以这种方式执行。但是,在三层架构中,您将遇到这样的问题:来自一个数据上下文的对象无法真正与来自不同上下文的对象一起使用。
解决这个问题真的很麻烦,所以我们为Web内容做了一个请求一个上下文的方法,而对于Windows服务等,则采用了线程本地方法。

是的,正如Hightechrider上面提到的那样,http-request-context可以解决这个“麻烦”,但对我来说(我敢打赌对你也是),它看起来像一个肮脏的hack,不是吗? - zerkms
我仍然觉得按请求的竞赛方法有点不好,特别是考虑到Web开发人员以异步方式检索数据的倾向...这可能不是您网站的常规做法,但如果有人决定在将来添加异步检索,则会出现多线程错误...在这种情况下,我认为最好的选择是将Linq2Sql对象转换为自己的业务对象... - LorenVS

0

你可能想要将上下文的生命周期管理为单个Web请求的范围,并且在该Web请求周期的生命周期内仅有一个上下文。

可以通过谷歌搜索“web scoped objectcontext”或“objectcontext lifetime”(或l2s的datacontext)来了解更多信息。

例如:http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx

在MVC2中,您可以将上下文管理代码放在基本控制器类中。


不,这是常见做法。请参见https://dev59.com/xnVC5IYBdhLWcg3wvT7g - Ian Mercer
如果我为每个请求创建一个数据上下文存储(并将它们存储在字典中,如“每个HTTP请求共享一个ObjectContext实例”所述),那么它们将如何被处理?请求完成后,所有HttpContext.Current.Items(甚至HttpContext.Current)都会自动进行垃圾回收吗? - zerkms
你可以在你的基础控制器类或控制器工厂中实例化一个(如果不需要HttpContext,这比使用HttpContext更容易)。它们将在请求完成后的某个时候被垃圾回收。 - Ian Mercer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接