我目前有一个WinForms应用程序,我正在转换为ASP,并且很快意识到缓存数据的重要性。
在我的WinForms应用程序中,我创建了表单级变量Dictionary(of String, List(of String))
,我在Form_Load()
事件中使用从SQL Server数据库中提取的数据填充,以将2个链接的组合框关联在一起(和在我的程序中用于各种其他事情)。 WinForms应用程序的优点在于,这个变量在整个程序中对我来说是可用的,因为它是一个表单级变量,并且随着表单一起死亡。
现在,我正在尝试在我的ASP项目中获得相同类型的功能,因此我将其设置为属性并将其与缓存关联,如下所示:
Private ReadOnly Property MyDict As Dictionary(Of String, List(Of String))
Get
Dim dict As Dictionary(Of String, List(Of String))
dict = Cache("MyDict")
If dict Is Nothing Then
... QUERY DB AND POPULATE DICTIONARY ...
Cache("MyDict") = dict
End If
Return dict
End Get
End Property
我的第一个(也是最重要的)问题是我是否正确地执行此操作,或者我是否只是不理解缓存 - 实际上,这是我对ASP的第一次尝试,它证明相当令人生畏。
我的下一个问题是,如果我这样做,我如何/在哪里声明缓存的生命周期?我注意到,当我重新运行我的程序时,缓存的数据仍然可以从之前的运行中获得...我希望它在页面的生命周期内可用,但是一旦它关闭就不可用(如果可能的话。
第三,任何好的建议/链接/技巧都会非常感激!!
感谢!!!
PS - 我知道我的代码是VB,但是C#的答案也很好,因为这更像.Net概念级而不是语言特定的。感谢。
答案 0 :(得分:1)
缓存方法有点问题,并且可能过于复杂,无法实现您的目标。关于设置缓存生命周期的问题是为什么这是非最佳方法的一些症状。使用更传统的方法来设置相关的下拉菜单可以省去很多麻烦。
来自WinForms开发的ASP.NET中最难习惯的事情之一就是你正在努力解决的问题 - 服务器端数据在帖子之间“丢失”的事实。
在WinForms应用程序中,您可以将DataTable声明为全局变量,只需单击按钮或更改下拉列表的值,就不会影响DataTable的事实。人口稠密。它就在那里因为桌子“存在”直到表格处理完毕。对于ASP.NET,服务器端对象仅在页面处理期间的回发期间存在,并且需要在回发时重新创建。
抽象的大部分魔法都是通过ViewState完成的,它可以在回发之间保存页面上对象的状态。例如,如果您在代码中查询数据库,将结果保存到DataTable,然后将该DataTable绑定到DataGrid,则ViewState将保存DataGrid的内容。在下一个回发中,DataGrid的内容可用,但绑定到DataGrid的初始DataTable不再存在。
尝试使用缓存来模仿对象的持久生命周期,使ASP.NET应用程序像WinForms应用程序一样,是对该技术的滥用。它本身并不是 糟糕 ,并且 并非不可能 ,它只是使用了错误的方法。它基本上是使用错误的工具来完成工作。
您可以更好地搜索如何执行特定任务的示例,而不是尝试执行此操作。在你的情况下,你想要search for "Cascading Drop-Down List ASP.NET"或类似的东西。有几个选项,但这个选项与任何选项一样好:http://www.aspsnippets.com/Articles/Creating-Cascading-DropDownLists-in-ASP.Net.aspx
另外,如果您还不熟悉它,那么在编写.NET应用程序时理解ASP.NET Page Lifecycle非常重要。 ASP.NET开发中没有任何一个组件可以引发新的ASP.NET开发人员,而不是理解页面生命周期。理解这一点对我们来说将比你能发挥的任何其他东西更有帮助,它与你提出的问题直接相关。
答案 1 :(得分:1)
首先,在Windows窗体中,您有1个进程,其中您拥有1个主表单实例(我不会讨论辅助表单,或者在您创建主要的多个实例时完全可以接受的情况形式)。
在ASP.NET中,您有1个进程,其中存在许多“主”页面实例。 有人可能会说,每个不同的用户都可以访问您的Web应用程序。 这是部分正确的:“主”页面实例的瞬时数量可能大于活动用户的数量(我们谈论的不是MVC的ASP.NET)。
您仍然可以全局访问,就像您以前在WinForms中一样。唯一的区别是:
在WinForms中,因为一般来说主要表单是唯一的,你可以将它用作东西的全局容器。在ASP.NET的情况下你不能这样做,因为主页不仅有一个全局实例(例如,即使在同一个会话的情况下,来自同一个浏览器,刷新页面很可能会创建一个新的Page实例。要测试:实现该页面的其他隐式公共无参数构造函数并使用断点检查出来)
通常很危险(特别是如果你不知道你做得很好)要使所有不同的线程处理来自许多浏览器的请求,所有线程都访问一些独特的共享内存。 / p>
我个人并不完全同意以下想法,但通常情况下,初学者应该使用相当多余的 SELECT 命令轰炸数据库。
为了能够提供一个不会轰炸数据库的简单解决方案,我会简化您的问题:假设您不需要缓存。您只需要从数据库中读取一堆内容,并同意在重新启动Web应用程序之前再也不会读取它的事实。一旦阅读,该信息应该可以在Web应用程序的所有角落中使用。
如果是这种情况,那么就会有一个简单的解决方案:
使用众所周知的“全局应用程序类”(Global.asax)及其“Application_Start”方法,以便在应用程序启动时收到通知(只需将其添加到项目中,就像任何源文件一样,'' ll在Add New Item对话框中找到它
使用全局HttpApplicationState类,它类似于Dictionary,并支持在ASP.NET应用程序中共享全局信息
public class Global : System.Web.HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
// .. read the database here
HttpContext.Current.Application["SOME_KEY"] = "ANY OBJECT";
// .. etc
}
通过这种方式,您可以从ASP.NET应用程序的任何位置读取您在全局HttpApplicationState实例中编写的内容,如下所示:
public partial class WebForm2 : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
object obj = this.Context.Application["SOME_KEY"];
// ...etc...
}
}
关于重新运行您的应用:大多数情况下,Web服务器(尤其是IIS,但也包括ASP.NET Development Server)不会停止。只要您想要“重新运行”应用程序(如果它已停止),它就会启动。但是,当您停止调试(单击Visual Studio中的“停止”按钮)时,您正在执行全部。您从Web服务器的进程中分离出来并保持运行状态。
当你“重新运行”应用程序时,如果Web服务器已经运行(某些时候ASP.NET开发服务器崩溃,IIS崩溃了但不经常崩溃)你只是“重新附加IDE的调试器”到网络服务器,你发现一切都是一样的。
还有更多:如果你重建(强制,万一没有必要重建),网络服务器不会停止,但它会丢弃你的应用程序(它在一个独立的地方运行) AppDomain)并重新加载新程序集并再次启动它。如果您记录Global.asax“Application_Started”方法,则可以看到所有这些内容。
修改强>
这是一种安全的缓存方式(虽然经过优化可以让许多读者同时访问某些全局数据,但仍然会减慢速度 - 拥有缓存是一种奢侈,我能说什么: ))。
首先写一个这样的类:
public sealed class SafeCache<T> {
private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly Func<T> expensiveReader;
private readonly TimeSpan lease;
private DateTime lastRead;
private T data;
public SafeCache(TimeSpan lease, Func<T> expensiveReader) {
this.lease = lease;
this.expensiveReader = expensiveReader;
this.data = expensiveReader();
this.lastRead = DateTime.UtcNow;
}
public T Data {
get {
this.rwLock.EnterReadLock();
try {
if (DateTime.UtcNow - this.lastRead < this.lease)
return this.data;
} finally {
this.rwLock.ExitReadLock();
}
this.rwLock.EnterUpgradeableReadLock();
try {
if (DateTime.UtcNow - this.lastRead < this.lease)
return this.data;
else {
this.rwLock.EnterWriteLock();
try {
this.data = expensiveReader();
this.lastRead = DateTime.UtcNow;
return this.data;
} finally {
this.rwLock.ExitWriteLock();
}
}
} finally {
this.rwLock.ExitUpgradeableReadLock();
}
}
}
}
然后使用Global.asax创建它的实例并将其放在HttpApplicationState全局实例中,使用某个任意键:
public class Global : System.Web.HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
HttpContext.Current.Application["SOME_KEY"] = new SafeCache<SomeRecord[]> (
lease: TimeSpan.FromMinutes(10),
expensiveReader: () => {
// .. read the database here
// and return a SomeRecord[]
// (this code will be executed for the first time by the ctor of SafeCache
// and later on, with every invocation of the .Data property getter that discovers
// that 10 minutes have passed since the last refresh)
}
);
// .. etc
}
你也可以创建一个像Helper这样的小帮手:
public static class Helper {
public static SomeRecord[] SomeRecords {
get {
var currentContext = HttpContext.Current;
if (null == currentContext) // return null or throw some clear Exception
var cache = currentContext.Application["SOME_KEY"] as SafeCache<SomeRecord[]>;
return cache.Data;
}
}
}
当然,无论何时需要,都可以使用它:
public partial class WebForm2 : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
SomeRecord[] records = Helper.SomeRecords;
// ...etc...
}
}
END OF EDIT