2012-04-09 85 views
2

我有一个小项目,我正在运行MVC3。 我使用LINQ从数据库中获取数据。 我使用与MVC3附带的预制示例相同的架构设计构建了我的项目。 在这样的项目中,应用程序被分割,在这个主题中,我想关注Model.cs文件。目前,我对每个控制器都有一个,所以作为一个例子,我有一个HighscoreController.cs和一个HighscoreModels.cs。在模型类中,我定义了一个Service类,该类引用了datacontext以及一些使用此datacontext查询数据库的方法。 现在我遇到了这些方法中的一些正在执行相同查询的问题,因此我想让访问数据库的中心点,所以我想我会实现存储库模式,所以我做了。 因此,而不是在服务类具有一个DataContext的引用我现在有这样的资源库的引用:MVC,存储库模式和DataLoadOptions

private IRepository _repository; 

    public HighscoreService() 
     : this(new Repository()) 
    { } 

    public HighscoreService(IRepository repository) 
    { 
     _repository = repository; 
    } 

现在数据库中调用存储库中进行处理和存储库从服务类中使用通过_repository参考。

我的仓库是建立这样的:

public class Repository : IRepository 
    { 
    private MyDataContext _dataContext; 

    public Repository() 
    { 
     _dataContext = new MyDataContext(); 
    } 

    public Member MemberByName(string memberName) 
    { 
     Member member = CompiledQueries.MemberByName(_dataContext, memberName); 
     return member; 
    } 
    } 

当我尝试结合使用DataLoadOptions这个仓库模式我所面临的问题出现。

因为当您使用dataloadoptions时,在应用新的dataloadoptions之前,您一定没有对datacontext进行过查询。由于我的存储库在所有方法中都重用了数据上下文,这根本不起作用。 我一直在尝试2件事,一种是通过using语句重新创建每个方法中的datacontext,以确保每次刷新datacontext。但是,当我从存储库中取回结果返回到我的模型中并且范围在存储库模式内部运行时,随着using语句结束,我遇到了问题,这意味着结果不能用于例如。 .Count()或.ToList()是因为为我提供数据的datacontext已终止。我还尝试了另一种解决方案,它在整个存储库中使用相同的datacontext,但在每个使用dataloadoptions的方法中创建一个新实例。这感觉非常肮脏;) 所以任何人都可以给我一个关于如何使用DataLoadOptions与存储库模式的建议?并避免我刚刚描述的问题。或者我应该不使用dataloadoptions并选择另一种方式来做到这一点?我使用DataLoadOptions的原因是我想从相关表中获取一些数据。

作为一个小问题:在上面的代码示例中,您可以看到我已将CompiledQueries放入自己的.cs文件中。这是一个糟糕的设计?在MVC应用程序中将编译查询放在哪里有任何指导原则吗?

感谢您的阅读并希望我的问题有一些答案;)提前致谢。如果您需要更多信息,只需询问。

+0

我不是专家,但我认为将数据上下文保留在“请求范围”中的想法可能有所帮助。这样,每个HTTP请求都会创建一个新的上下文,这可能会消除您的一些问题。像Ninject这样的IoC容器可以帮助解决这个问题。虽然它涉及实体框架,但以下文章可能会有所帮助:http://buildstarted.com/2010/08/24/dependency-injection-with-ninject-moq-and-unit-testing/ – ngm 2012-04-09 13:13:22

+0

注入您的存储库并进行设置按照WebRequest的生活方式。目前还不确定存储库为您做了什么,除了添加无用的抽象层。 – CrazyCoderz 2012-04-09 20:45:00

回答

0

我绝不是DataLoadOptions的专家,但是从您的问题以及我读到的内容来看,似乎您需要使用它来进行急切的加载。参考:

“因为当您使用dataloadoptions时,在应用新的dataloadoptions之前,您不能在datacontext上进行以前的查询。”

..对我来说这听起来像是一个缺点或设计缺陷DataLoadOptions(我个人使用实体框架,而不是LINQ to SQL)。虽然我认为按照ngm和CrazyCoderz的前两条评论中提供的每个HTTP请求提供单个数据上下文是个不错的主意,但我认为这不会解决您的问题。如果您希望在单个HTTP请求中重复使用单个数据上下文,只要您执行第一个查询,就好像您将无法将数据上下文中的DataLoadOptions设置为新值。

我在vslive vegas上看到一个演示文稿,演示者提供了您提到的一种解决方案,在每个存储库方法中创建一个新的数据上下文。你需要做的是在之前调用ToList()或ToArray()来终止using语句并返回方法结果。

正如您所提到的,这会使对象在方法返回后无法预先加载到枚举中。不过,如果你已经拥有了执行查询和转换为ListCollectionArray,或其他一些具体IEnumerable,你并不需要访问Count()ToList()方法任何更长的时间。相反,您可以使用Array.LengthList.CountCollection.Count属性

还有什么阻止您在每个存储库方法中创建新的数据上下文?意思是,在执行存储库方法之后,你需要什么数据上下文,因为它已被处置而无法获取?

回复评论

对于第一个查询,你可以做到这一点?

public Member GetSomeRandomMember() 
{ 
    Member[] members = null; 
    using (var context = new MyDataContext()) 
    { 
     // execute the query to get the whole table 
     members = context.Members.ToArray(); 
    } 

    // do not need to query again 
    var totalRows = members.Length; 
    var skipThisMany = PerformRandomNumberComputation(totalRows); 
    return members.Skip(skipThisMany).FirstOrDefault(); 
} 

如果您的Members表有很多行,则可能不是最优的。在这种情况下,您希望执行2个查询 - 1个进行计数,第二个进行选择。你可以通过打开两个上下文来实现:

public Member GetSomeRandomMember() 
{ 
    using (var context1 = new MyDataContext()) 
     var totalRows = context1.Members.Count(); 

    var skipThisMany = PerformRandomNumberComputation(totalRows); 

    Member member = null; 
    using (var context2 = new MyDataContext()) 
     member = context2.Members.Skip(skipThisMany).FirstOrDefault(); 

    return member; 
} 

对于你的评论的第二部分,我不确定我得到你在说什么。数据和决策的变化给它的抓取都应该来在单个操作与单个上下文反正:

public void SaveMember(int id, string email, bool isSuspended) 
{ 
    using (var context = new MyDataContext()) 
    { 
     var member = context.Members.Single(m => m.Id == id); 
     member.Email = email; 
     member.IsSuspended = isSuspended; 
     context.SaveChanges(); // or whatever the linq to sql equivalent is 
    } 
} 

如果你想整个实体传递给库方法,你还是应该查询出来因此它被附加到正确的上下文中:

public void SaveMember(Member member) 
{ 
    var memberDto = member; 
    using (var context = new MyDataContext()) 
    { 
     member = context.Members.Single(m => m.Id == memberDto.Id); 
     member.Email = memberDto.Email; 
     member.IsSuspended = memberDto.IsSuspended; 
     context.SaveChanges(); // or whatever the linq to sql equivalent is 
    } 
} 
+0

感谢您的回答。现在我加载一个整个表格,然后对它进行计数,然后根据计数生成一个随机数字,然后在相同的加载结果中用于.Skip(randomNumber).FirstOrDefault()。所以,如果我不能懒惰地加载它,那么我不得不将它分成2个查询,一个用于获取计数,另一个用于获取我的实际行。我终止上下文的另一个问题是获取数据并对其进行更改并将其放回,因为当上下文终止时,它无法正确地将更改插入到之前获取的已加载结果中。 – emin 2012-04-10 05:53:15

+0

再次感谢您的答案和精彩的代码示例。我不确定我对第一部分的看法,打开了很多数据上下文,因为我不知道如何执行重要的操作。我想我希望能有一些更神奇的解决方案;)第二部分很有意义,我会确保我这样做。谢谢 – emin 2012-04-10 10:51:24

+0

@emin,虽然我不认为这是打开和处理2个数据上下文的理想选择,但您可能需要权衡一个选项与另一个选项的性能。通过执行第一个代码片段,你实际上是在db上执行'SELECT * FROM Members'。这将导致数据库将所有行返回到内存中。如果您的会员表有数千或数百万行,这将会很慢并且很昂贵。第二种选择不是'S​​ELECT COUNT(*)FROM Members',然后'SELECT * FROM Members WHERE ID = @ID',并且当表中有很多行时,它将始终具有更高的性能。 – danludwig 2012-04-10 16:21:26