本文地址:http://www.cnblogs.com/QLeelulu/archive/2008/09/19/1294469.html
本文作者:Q.Lee.lulu
本文首发博客园 ,4MVC同步更新。本文示例基于ASP.NET MVC framework (Codeplex Preview 5) 。
关于db4o:
db4o是一种纯对象数据库,相对于传统的关系数据库+ORM,db4o具有以下好处:
1)以存对象的方式存取数据(废话~~,不过你考虑一下完全以对象的方式去考虑数据的存取对传统的数据库设计思维来说是多么大的颠覆);
2)无需数据库服务器,只需要一个数据文件,且dll大小仅为300多k,非常适合作为嵌入式数据库;
3)提供Query By Sample, Native Query和Simple Object DataBase Access(SODA)三种方式进行数据查询,操作简便且功能强大,和sql说byebye。
以上为引用别人的介绍,目前最新的版本为7.5,支持LINQ语法。要使用db4o只需要在项目中引入db4o的DLL就可以,并不需要安装服务器端(类似于Access的文件型数据库)。
ASP.NET MVC的TempData用于在各个控制器Action间传输一些临时的数据,相信大家都看过“在ASP.NET页面间传值的方法有哪几种”这个面试题,TempData的作用差不多就是这样。TempData默认是使用Session来存储临时数据的,虽然TempData中存放的数据只一次访问中有效,一次访问完后就会删除了的。但很多朋友还是对于将数据存放在Session表示担心,毕竟Session的资源宝贵啊。
使用db4o来存储TempData中的数据是一个不错的选择。之前看到有说用db4o来做中间层数据缓存,当时不是很明白,现在放到这里来想一想,就阔然开朗了。
ASP.NET MVC提供了一个ITempDataProvider的接口:
只要实现这两个方法,就可以实现我们的TempDataProvider了。ASP.NET MVC Preview 5 默认还提供了一个CookieTempDataProvider,在Microsoft.Web.Mvc命名空间下。
在这里我们先建一个TempObject的类来表示临时数据,因为TempData是跟用户对应的(想想Session),所以在这个对象中需要一个标识符标识当然用户。在这里我们就使用SessionId来标识吧。TempObject类的代码如下:
TempObject
/**//// <summary>
/// 用于保存临时数据的临时对象
/// </summary>
public class TempObject
{
private TempObject()
{
CreateTime = DateTime.Now;
}
public TempObject(string sessionId) : this()
{
SessionId = sessionId;
}
/**//// <summary>
/// 用于标识当前用户的SessionId
/// </summary>
public string SessionId
{
get;
set;
}
/**//// <summary>
/// 要保存的TempData
/// </summary>
public IDictionary<string, object> TempObjects
{
get;
set;
}
/**//// <summary>
/// 对象的创建时间
/// </summary>
public DateTime CreateTime
{
get;
set;
}
}
然后我们实现ITempDataProvider的两个方法,代码有注释,就不说了。代码如下:
public
class
db4oTempDataProvider : ITempDataProvider
{
/**//// <summary>
/// 实现接口的获取TempData方法
/// </summary>
/// <param name="controllerContext"></param>
/// <returns></returns>
public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
{
HttpContextBase httpContext = controllerContext.HttpContext;
if (httpContext.Session == null)
{
throw new InvalidOperationException("db4oTempDataProvider: SessionStateDisabled");
}
//如果用户的Session没有任何数据,则每次请求的SessionId都是一个新的ID。
//只好出此下策,在Session附加一点数据。
httpContext.Session["db4oTempDataProvider"] = 1;
//使用SessionId来标识当前用户
string sessionId = httpContext.Session.SessionID;
//从db4o数据库中取出对象
TempObject temp = Db4oHelper.GetTempObject(sessionId);
// 清理垃圾数据。想想你就会明白为什么会有垃圾数据了.
// 当前时间的30分钟以前创建的数据都认为是垃圾数据。
// 我也不知道多少时间合适,反正Session的默认过期时间是30分钟。
Db4oHelper.CleanUp();
if (temp != null && temp.TempObjects != null)
{
//取出临时数据后,将数据删除,即TempData数据只被访问一次即删除
Db4oHelper.DelTempObject(sessionId);
return temp.TempObjects as Dictionary<string, object>;
}
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
/**//// <summary>
/// 实现接口的保存TempData方法
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="values"></param>
public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
{
HttpContextBase httpContext = controllerContext.HttpContext;
if (httpContext.Session == null)
{
throw new InvalidOperationException("db4oTempDataProvider: SessionStateDisabled");
}
TempObject temp = new TempObject(httpContext.Session.SessionID);
temp.TempObjects = values;
//将TempData保存到数据库
Db4oHelper.SaveTempObject(temp);
}
}
附Db4oHelper的代码:
Db4oHelper
/**//// <summary>
/// db4o数据库操作帮助类.
/// </summary>
public static class Db4oHelper
{
private static IObjectContainer oc = null;
private static object _lock = new object();
static Db4oHelper()
{
db();
}
/**//// <summary>
/// 打开数据库
/// </summary>
/// <returns></returns>
public static IObjectContainer db()
{
try
{
if (oc == null || oc.Ext().IsClosed())
{
lock (_lock)
{
if (oc == null || oc.Ext().IsClosed())
{
oc = Db4oFactory.OpenFile(dbConfig(), AppDomain.CurrentDomain.BaseDirectory + "\\App_Data\\tempData.db4o");
}
}
}
return oc;
}
catch (Exception e)
{
throw new Exception("打开db4o数据出错,原因:" + e.Message);
}
}
private static IConfiguration dbConfig()
{
IConfiguration c = Db4oFactory.NewConfiguration();
c.ObjectClass(typeof(object)).CascadeOnDelete(true);
return c;
}
/**//// <summary>
/// Close database connection
/// </summary>
public static void close()
{
if (oc != null)
oc.Close();
}
/**//// <summary>
/// 获取TempData
/// </summary>
/// <param name="sessionId"></param>
/// <returns></returns>
public static TempObject GetTempObject(string sessionId)
{
TempObject temp = new TempObject(sessionId);
return oc.Query<TempObject>(delegate(TempObject t)
{
return t.SessionId == sessionId;
}).FirstOrDefault<TempObject>();
}
/**//// <summary>
/// 将TempData保存到数据库
/// </summary>
/// <param name="temp"></param>
public static void SaveTempObject(TempObject temp)
{
DelTempObject(temp.SessionId);
oc.Store(temp);
oc.Commit();
}
/**//// <summary>
/// 删除指定SeddionId的TempData
/// </summary>
/// <param name="sessionId"></param>
public static void DelTempObject(string sessionId)
{
IList<TempObject> lists = oc.Query<TempObject>(delegate(TempObject t)
{
return t.SessionId == sessionId;
});
foreach (TempObject t in lists)
{
oc.Delete(t);
}
oc.Commit();
}
/**//// <summary>
/// 清理垃圾数据。当前时间的30分钟以前创建的数据都当是垃圾数据。
/// 我也不知道多少时间合适,反正Session的默认过期时间是30分钟。
/// </summary>
public static void CleanUp()
{
IList<TempObject> lists = oc.Query<TempObject>(delegate(TempObject t)
{
return t.CreateTime < DateTime.Now.AddMinutes(-30);//清理30分钟前的垃圾数据
});
foreach (TempObject t in lists)
{
oc.Delete(t);
}
oc.Commit();
}
}
到这里我们已经实现了自己的TempDataProvider,但是怎么替换掉系统默认的SessionStateTempDataProvider呢?我们可以在Controller中进行替换,Controller提供了一个TempDataProvider的属性。我们写一个BaseController,在这里进行替换,然后让所后的Controller都继承自这个Controller就可以了。
public
class
BaseController : Controller
{
public
BaseController()
{
//
将当前的TempDataProvider 修改为 db4oTempDataProvider。
this
.TempDataProvider
=
new
db4oTempDataProvider();
}
}
或许还有其他的更好的方法可以对全局进行替换?可以在Application_Start中进行这个操作?如果你知道,麻烦告诉我。 ^_^
完了测试了一下,貌似性能不咋的。也罢,大家当作是学习一下怎样实现一个TempDataProvider。不知是否我的数据库操作部分有问题?还是文件数据库本身读写慢?
对于db4o,有几个问题想请教一下:
1、不知道并发处理能力如何?
2、在这里用的单实例打开连接,并且操作完了都不关闭连接,不知道这样处理是否合适?或者该怎样才能更好的管理这个数据库连接,才能更好的处理并发状况?
泛型的RedirectToAction方法
写过ASP.NET MVC代码的朋友应该发现,在Action中如果想要Redirect到另一个Action去,则我们或许会写如下的代码:
//
跳转到HomeController中的Index Action
return
RedirectToAction(
"
Index
"
,
"
Home
"
);
或者
//
跳转到HomeController中的Index Action
return
RedirectToRoute(
new
{ controller
=
"
Home
"
, action
=
"
Index
"
});
(或者你有其他好的办法我不知道?)
可以看到这里Controller和Action的名称都是用字符串来写的,对于代码维护和重构,这是不可想象的。所以反射了一下M$的代码,写了一个泛型的RedirectToAction方法,我们可以把这个方法写到前面提到的BaseController中去:
/**/
/// <summary>
/// 重定向到指定的Controller中的Action
/// </summary>
public
RedirectToRouteResult RedirectToAction
<
T
>
(Expression
<
Action
<
T
>>
action)
where
T : Controller
{
MethodCallExpression body = action.Body as MethodCallExpression;
if (body == null)
{
throw new InvalidOperationException("Expression must be a method call");
}
if (body.Object != action.Parameters[0])
{
throw new InvalidOperationException("Method call must target lambda argument");
}
string name = body.Method.Name;
string str2 = typeof(T).Name;
if (str2.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
{
str2 = str2.Remove(str2.Length - 10, 10);
}
RouteValueDictionary values = LinkBuilder.BuildParameterValuesFromExpression(body) ?? new RouteValueDictionary();
values.Add("controller", str2);
values.Add("action", name);
return new RedirectToRouteResult(values);
}
使用示例:
return
RedirectToAction
<
HomeController
>
(h
=>
h.Index());
这个返回的是一个RedirectToRouteResult,跟我们平时WebForm中的Redirect()一样是重定向到另一个URL,产生一个新的请求的。所以如果你有一个Action为:
public
ActionResult Cpu(CPU cpu)
{
ViewData[
"
Cpu
"
]
=
cpu;
return
View();
}
那么你不可以如下这样调用:
return
RedirectToAction
<
HomeController
>
(h
=>
h.Cpu(cpu));
如果你需要这样做,你应该写一个ModelBinder,并重写你要传递的参数对象(在这里是CPU)的ToString()方法。具体请参见leven(神鱼)和重典的Blog。
完。Enjoy!最后附上DEMO代码:TempDataProviderDemo.rar