熟悉Asp.net Forums(以下简称ANF)的朋友可以知道在ANF中数据库访问是使用数据库工厂,通过重写AspNetForums.Components.ForumsDataProvider,在数据层中使用AspNetForums.Data.ForumsSqlDataProvider最终实现数据读取的。那么.Text这样做究竟有什么好处呢?我们认为,在 ANF中的数据读取类中是这样访问数据库的:
private PostSet GetModeratedPostsByForumId(int forumId)
{
// return all of the forums and their total and daily posts
// Create Instance of Connection and Command Object
using( SqlConnection myConnection = GetSqlConnection() ) {
SqlCommand myCommand = new SqlCommand(databaseOwner + ".forums_GetModeratedPostsByForumId", myConnection);
// Mark the Command as a SPROC
myCommand.CommandType = CommandType.StoredProcedure;
// Pass sproc parameters
myCommand.Parameters.Add("@UserName", SqlDbType.Int).Value = forumId;
// Execute the command
myConnection.Open();
SqlDataReader dr = myCommand.ExecuteReader();
PostSet postSet = new PostSet();
while (dr.Read())
postSet.Posts.Add ( PopulatePostFromIDataReader(dr) );
dr.Close();
myConnection.Close();
return postSet;
}
}
{
// return all of the forums and their total and daily posts
// Create Instance of Connection and Command Object
using( SqlConnection myConnection = GetSqlConnection() ) {
SqlCommand myCommand = new SqlCommand(databaseOwner + ".forums_GetModeratedPostsByForumId", myConnection);
// Mark the Command as a SPROC
myCommand.CommandType = CommandType.StoredProcedure;
// Pass sproc parameters
myCommand.Parameters.Add("@UserName", SqlDbType.Int).Value = forumId;
// Execute the command
myConnection.Open();
SqlDataReader dr = myCommand.ExecuteReader();
PostSet postSet = new PostSet();
while (dr.Read())
postSet.Posts.Add ( PopulatePostFromIDataReader(dr) );
dr.Close();
myConnection.Close();
return postSet;
}
}
大家可以看到,有一个PopulatePostFromIDataReader(dr)[代码如下]
public static Post PopulatePostFromIDataReader(IDataReader dr)
{
Post post = new Post();
// Populate Post
//
post.PostID = Convert.ToInt32(dr["PostID"]);
post.ParentID = Convert.ToInt32(dr["ParentID"]);
post.FormattedBody = Convert.ToString(dr["FormattedBody"]);
post.Body = Convert.ToString(dr["Body"]);
post.ForumID = Convert.ToInt32(dr["ForumID"]);
post.PostDate = Convert.ToDateTime(dr["PostDate"]);
post.PostLevel = Convert.ToInt32(dr["PostLevel"]);
post.SortOrder = Convert.ToInt32(dr["SortOrder"]);
post.Subject = Convert.ToString(dr["Subject"]);
post.ThreadDate = Convert.ToDateTime(dr["ThreadDate"]);
post.ThreadID = Convert.ToInt32(dr["ThreadID"]);
post.Replies = Convert.ToInt32(dr["Replies"]);
post.Username = Convert.ToString(dr["Nickname"]);
post.IsApproved = Convert.ToBoolean(dr["IsApproved"]);
// post.AttachmentFilename = (string) dr["AttachmentFilename"];
// post.AttachmentSize = Convert.ToInt32(dr["AttachmentSize"]);
//增加附件下载次数 by venjiang 20040927
//post.AttachmentDownloads = Convert.ToInt32(dr["AttachmentDownloads"]);
post.IsLocked = Convert.ToBoolean(dr["IsLocked"]);
post.ValuableLevel = Convert.ToInt32(dr["ValuableLevel"]);
post.ValueGroupID = Convert.ToInt32(dr["ValueGroupID"]);
post.Views = Convert.ToInt32(dr["TotalViews"]);
post.HasRead = Convert.ToBoolean(dr["HasRead"]);
post.UserHostAddress = (string) dr["IPAddress"];
post.PostType = (PostType) dr["PostType"];
post.EmoticonID = (int) dr["EmoticonID"];
// 是否用户收藏 by venjiang 2004/12/15
try
{
post.IsFavorited = (bool) dr["IsFavorited"];
}
catch
{
}
// 是否用户跟踪主题
try
{
post.IsTracked = (bool) dr["UserIsTrackingThread"];
}
catch
{
}
try
{
post.EditNotes = (string) dr["EditNotes"];
}
catch
{
}
try
{
post.ThreadIDNext = (int) dr["NextThreadID"];
post.ThreadIDPrev = (int) dr["PrevThreadID"];
}
catch
{
}
if (post.PostType == PostType.Poll)
{
if (dr["FormattedVoteOptions"] != DBNull.Value)
{
post.FormattedVoteOptions = Convert.ToString(dr["FormattedVoteOptions"]);
}
if (dr["VoteOptions"] != DBNull.Value)
{
post.VoteOptions = Convert.ToString(dr["VoteOptions"]);
}
}
// IP真实地理位置 by venjiang 2005/01/24
try
{
post.UserIPLocation = Convert.ToString(dr["IPLocation"]);
}
catch
{
}
// Populate User
//
post.User = PopulateUserFromIDataReader(dr);
return post;
}
{
Post post = new Post();
// Populate Post
//
post.PostID = Convert.ToInt32(dr["PostID"]);
post.ParentID = Convert.ToInt32(dr["ParentID"]);
post.FormattedBody = Convert.ToString(dr["FormattedBody"]);
post.Body = Convert.ToString(dr["Body"]);
post.ForumID = Convert.ToInt32(dr["ForumID"]);
post.PostDate = Convert.ToDateTime(dr["PostDate"]);
post.PostLevel = Convert.ToInt32(dr["PostLevel"]);
post.SortOrder = Convert.ToInt32(dr["SortOrder"]);
post.Subject = Convert.ToString(dr["Subject"]);
post.ThreadDate = Convert.ToDateTime(dr["ThreadDate"]);
post.ThreadID = Convert.ToInt32(dr["ThreadID"]);
post.Replies = Convert.ToInt32(dr["Replies"]);
post.Username = Convert.ToString(dr["Nickname"]);
post.IsApproved = Convert.ToBoolean(dr["IsApproved"]);
// post.AttachmentFilename = (string) dr["AttachmentFilename"];
// post.AttachmentSize = Convert.ToInt32(dr["AttachmentSize"]);
//增加附件下载次数 by venjiang 20040927
//post.AttachmentDownloads = Convert.ToInt32(dr["AttachmentDownloads"]);
post.IsLocked = Convert.ToBoolean(dr["IsLocked"]);
post.ValuableLevel = Convert.ToInt32(dr["ValuableLevel"]);
post.ValueGroupID = Convert.ToInt32(dr["ValueGroupID"]);
post.Views = Convert.ToInt32(dr["TotalViews"]);
post.HasRead = Convert.ToBoolean(dr["HasRead"]);
post.UserHostAddress = (string) dr["IPAddress"];
post.PostType = (PostType) dr["PostType"];
post.EmoticonID = (int) dr["EmoticonID"];
// 是否用户收藏 by venjiang 2004/12/15
try
{
post.IsFavorited = (bool) dr["IsFavorited"];
}
catch
{
}
// 是否用户跟踪主题
try
{
post.IsTracked = (bool) dr["UserIsTrackingThread"];
}
catch
{
}
try
{
post.EditNotes = (string) dr["EditNotes"];
}
catch
{
}
try
{
post.ThreadIDNext = (int) dr["NextThreadID"];
post.ThreadIDPrev = (int) dr["PrevThreadID"];
}
catch
{
}
if (post.PostType == PostType.Poll)
{
if (dr["FormattedVoteOptions"] != DBNull.Value)
{
post.FormattedVoteOptions = Convert.ToString(dr["FormattedVoteOptions"]);
}
if (dr["VoteOptions"] != DBNull.Value)
{
post.VoteOptions = Convert.ToString(dr["VoteOptions"]);
}
}
// IP真实地理位置 by venjiang 2005/01/24
try
{
post.UserIPLocation = Convert.ToString(dr["IPLocation"]);
}
catch
{
}
// Populate User
//
post.User = PopulateUserFromIDataReader(dr);
return post;
}
可以发现,在ANF中,是直接在数据库层来实现数据读取到对象之间的转换。如果我们需要Access或者Oracle 的数据库时,DataReader的实体转换要重新写过。而在.Text中,使用了IDbProvider,这样做的好处就是把实体封装单独抽象出来,然后不管什么数据库,当我们需要再重新写时,只需要得到IDataReader.
接下来是使用数据读取操作的讲解:
Dottext.Framework.Data. IDTOProvider 定义了关于DTO的接口,这个接口涉及到的对象机器操作有:
Dottext.Framework.Data. IDTOProvider 定义了关于DTO的接口,这个接口涉及到的对象机器操作有:
Entry(也就是blog中发表的文章,其实体、对象的声明在Components目录下的Entry.cs中)[需要注意的是该类继承了IblogIdentifier 接口,并且声明了[Serializable]属性,几乎定义的实体类型都类似该类]
Links 收藏的链接
Categories 类别,注意blog的系统分类和每一个博客的分类一起存储的,通过-1的blog来区分系统定义的分类。
Stats 统计信息
Configuration 配置类
KeyWords blog关键字
Images 相册
Archives 文章归档
ScheduledEvents 调度事件
Logger 日志对象
Rate 点击统计
Security 身份验证
MailNotify 邮件
IblogIdentifier 接口是规定了该类必须要有一个归属哪一个blogID的,这容易理解,因为无论是文章还是统计信息、个人连结、收藏、相夹都属于私人的。
除了实体类外,还实现了相应实体类的收集类。对于数据绑定来说,很多人喜欢采用实体收集类来代替DataSet等,我也是这类人,在Framework 2.0中则可以使用List<Entry>。这些实体收集类也标记了[Serializable]属性,也可以利用序列化进行配置.
关于这些实体类的操作,dottext定义了IDTOProvider的接口,来定义对DTO的操作,具体在Dottext.Framework.Data目录下,这个接口需要其他具体类实现,但是体现了作者的设计思路:就是不把实现根据体的数据层死死捆绑,这个可以为我们借鉴。
为了进一步实现自己的思路,dottext还特意夹了一个数据层的抽象,IDbProvider ,此接口实现了对于各个DTC实体的数据访问,但是都是通过定义返回IdataReader和DataSet来实现对于具体数据库的封装。Dottext的作者在书写代码时候,做了大量的分类注释。关于数据层的具体实现,我的这个版本是基于SQL server的,所有具体的数据操作在Data目录下的SqlDataProvider.cs,这个类实现了IdbProvider,但是我们看到的几乎都是存储过程调用,而dottext的Sql版本大约110多个,所以要仔细阅读这些数据层访问细节,会花费很多时间。但是理清了这些头绪,我们可以知道如何去阅读甚至去实现修改了。
那系统是如何实现数据访问的灵活配置呢?这个举一个例子,发表文章的操作,最终的操作是落在admin\ UserControls\ EntryEditor.ascx上(详细分析可能后面会补充),其中的代码如下:
private void UpdatePost()
{
if (Page.IsValid)
{
BlogConfig config = Utilities.currentConfig;
string successMessage = Constants.RES_SUCCESSNEW;
Entry entry = new Entry(EntryType);
try
{
entry.Title = txbTitle.Text;
//entry.Body = Globals.StripRTB(Utilities.CheckIsIE55() | Utilities.CheckIsMozilla() ? ftbBody.Text : txbBody.Text, Request.Url.Host);
//entry.Body=Globals.StripRTB(Request.Form["Editor_Edit_FCKEditor"],config.Host);
entry.Body = Globals.StripRTB(this.FCKEditor.Value, config.Host);
entry.Tags = txbTags.Text;
if (isPostBlocked(entry.Title, entry.Body))
{
this.Messages.ShowError(Constants.RES_BLOCKWORDINPOST);
return;
}
else
{
entry.IP = Context.Request.UserHostAddress;
entry.IsActive = ckbPublished.Checked;
entry.SourceName = Globals.GetUserIpAddress(Context);
//end
entry.Author = config.Author;//Config.CurrentBlog().Author;
entry.Email = config.Email;//Config.CurrentBlog().Email;
entry.SourceUrl = txbSourceUrl.Text;
entry.Description = txbExcerpt.Text;
entry.TitleUrl = txbTitleUrl.Text;
entry.AllowComments = chkComments.Checked;
entry.DisplayOnHomePage = chkDisplayHomePage.Checked;
entry.IncludeInMainSyndication = chkMainSyndication.Checked;
entry.SyndicateDescriptionOnly = chkSyndicateDescriptionOnly.Checked;
Response.Cookies.Add(new HttpCookie("dtb_SynDesc", chkSyndicateDescriptionOnly.Checked.ToString()));
entry.IsAggregated = chkIsAggregated.Checked;
entry.EntryName = txbEntryName.Text.Replace("-", "_");
entry.BlogID = config.BlogID;//Config.CurrentBlog(Context).BlogID;
entry.OriginUrl = "";
//是否原创
switch (rblOri.SelectedValue)
{
case "ori":
{
entry.IsOriginAuthor = true;
entry.IsTranslation = false;
break;
}
case "copy":
{
entry.IsOriginAuthor = false;
entry.IsTranslation = false;
break;
}
case "tran":
{
entry.IsTranslation = true;
entry.IsOriginAuthor = false;
break;
}
default:
{
entry.IsOriginAuthor = true;
entry.IsTranslation = false;
break;
}
}
string[] Categories = this.CategoryArray(CategoryList);
string[] GlobalCategories = this.GlobalCategoryArray();
entry.Categories = Categories;
entry.GlobalCategories = GlobalCategories;
//entry.CategoryTags = Entries.MergeArrays(Categories, GlobalCategories);
if (PostID > 0)
{
successMessage = Constants.RES_SUCCESSEDIT;
entry.DateCreated = EntryDateUpdated;
entry.DateUpdated = DateTime.Now;//BlogTime.CurrentBloggerTime;
entry.EntryID = PostID;
Entries.Update(entry, Categories, GlobalCategories, config);
if (entry.EntryName == "")
{
Dottext.Common.Data.Cacher.RemoveEntryCache(Context, PostID);
}
else
{
Dottext.Common.Data.Cacher.RemoveEntryCache(Context, entry.EntryName);
}
}
else
{
entry.DateCreated = DateTime.Now;//BlogTime.CurrentBloggerTime;
PostID = Entries.Create(entry, Categories, GlobalCategories, config);
}
Categories = null;
GlobalCategories = null;
string errMsg = " 添加文章时出现错误。";
if (PostID > 0)
{
//BindList();
this.Messages.ShowMessage(successMessage);
this.ResetPostEdit(false);
if (entry.PostType == PostType.BlogPost)
{
Response.Redirect("~/PostList.aspx", true);
}
else
{
Response.Redirect("~/ArticleList.aspx", true);
}
}
else
{
this.Messages.ShowError(Constants.RES_FAILUREEDIT
+ errMsg );
}
}
}
finally
{
//Results.Collapsible = false;
entry = null;
}
}
}
{
if (Page.IsValid)
{
BlogConfig config = Utilities.currentConfig;
string successMessage = Constants.RES_SUCCESSNEW;
Entry entry = new Entry(EntryType);
try
{
entry.Title = txbTitle.Text;
//entry.Body = Globals.StripRTB(Utilities.CheckIsIE55() | Utilities.CheckIsMozilla() ? ftbBody.Text : txbBody.Text, Request.Url.Host);
//entry.Body=Globals.StripRTB(Request.Form["Editor_Edit_FCKEditor"],config.Host);
entry.Body = Globals.StripRTB(this.FCKEditor.Value, config.Host);
entry.Tags = txbTags.Text;
if (isPostBlocked(entry.Title, entry.Body))
{
this.Messages.ShowError(Constants.RES_BLOCKWORDINPOST);
return;
}
else
{
entry.IP = Context.Request.UserHostAddress;
entry.IsActive = ckbPublished.Checked;
entry.SourceName = Globals.GetUserIpAddress(Context);
//end
entry.Author = config.Author;//Config.CurrentBlog().Author;
entry.Email = config.Email;//Config.CurrentBlog().Email;
entry.SourceUrl = txbSourceUrl.Text;
entry.Description = txbExcerpt.Text;
entry.TitleUrl = txbTitleUrl.Text;
entry.AllowComments = chkComments.Checked;
entry.DisplayOnHomePage = chkDisplayHomePage.Checked;
entry.IncludeInMainSyndication = chkMainSyndication.Checked;
entry.SyndicateDescriptionOnly = chkSyndicateDescriptionOnly.Checked;
Response.Cookies.Add(new HttpCookie("dtb_SynDesc", chkSyndicateDescriptionOnly.Checked.ToString()));
entry.IsAggregated = chkIsAggregated.Checked;
entry.EntryName = txbEntryName.Text.Replace("-", "_");
entry.BlogID = config.BlogID;//Config.CurrentBlog(Context).BlogID;
entry.OriginUrl = "";
//是否原创
switch (rblOri.SelectedValue)
{
case "ori":
{
entry.IsOriginAuthor = true;
entry.IsTranslation = false;
break;
}
case "copy":
{
entry.IsOriginAuthor = false;
entry.IsTranslation = false;
break;
}
case "tran":
{
entry.IsTranslation = true;
entry.IsOriginAuthor = false;
break;
}
default:
{
entry.IsOriginAuthor = true;
entry.IsTranslation = false;
break;
}
}
string[] Categories = this.CategoryArray(CategoryList);
string[] GlobalCategories = this.GlobalCategoryArray();
entry.Categories = Categories;
entry.GlobalCategories = GlobalCategories;
//entry.CategoryTags = Entries.MergeArrays(Categories, GlobalCategories);
if (PostID > 0)
{
successMessage = Constants.RES_SUCCESSEDIT;
entry.DateCreated = EntryDateUpdated;
entry.DateUpdated = DateTime.Now;//BlogTime.CurrentBloggerTime;
entry.EntryID = PostID;
Entries.Update(entry, Categories, GlobalCategories, config);
if (entry.EntryName == "")
{
Dottext.Common.Data.Cacher.RemoveEntryCache(Context, PostID);
}
else
{
Dottext.Common.Data.Cacher.RemoveEntryCache(Context, entry.EntryName);
}
}
else
{
entry.DateCreated = DateTime.Now;//BlogTime.CurrentBloggerTime;
PostID = Entries.Create(entry, Categories, GlobalCategories, config);
}
Categories = null;
GlobalCategories = null;
string errMsg = " 添加文章时出现错误。";
if (PostID > 0)
{
//BindList();
this.Messages.ShowMessage(successMessage);
this.ResetPostEdit(false);
if (entry.PostType == PostType.BlogPost)
{
Response.Redirect("~/PostList.aspx", true);
}
else
{
Response.Redirect("~/ArticleList.aspx", true);
}
}
else
{
this.Messages.ShowError(Constants.RES_FAILUREEDIT
+ errMsg );
}
}
}
finally
{
//Results.Collapsible = false;
entry = null;
}
}
}
这里的Entry属于DTO类型,在Components 下有解释。如果是第一次新发表的文章,那么会执行:
此时会执行:
静态方法最终调用:
我们主要集中看看
该语句执行的需要好好揣摩,DTOProvider 的声明在providers目录下,他有一个静态的声明构造函数
用于在静态调用该类的方法之前执行构造(这里相当于使用了单例模式)。此时会利用前民提到的配置体系获取DTOProvider配置。DTOProviderConfiguration 具有[XmlRoot("DTOProvider")]属性,从<BlogProviders>节获得的XML片断中得到DTOProviderConfiguration,而需要注意这里的DTOProviderConfiguration继承自一个抽象类BaseProvider。Config.Settings.BlogProviders 通过反序列化得到了具体的BlogProvider类,但是我们仅仅想获取DTOProvider的属性,而在我手中的版本该处的配置是Dottext.Framework.Data.DataDTOProvider(注意该类的实现了IDTOProvider接口)。DTOProviderConfiguration类型实际上是抽象类BaseProvider的具体实现,但是加上了[XmlRoot("DTOProvider")]属性(可以反序列化),这样将得到了一个provider类型。
dtoPC.Instance();会调用BaseProvider(注意是抽象类)中的
大家看到,这又是一个动态产生类型的方法,也是采用了反射原理。该类的属性ProviderType声明是这样的
可以看到,这是从配置文件中读取到的Type值,具体到我察看的工程值是:Dottext.Framework.Data.DataDTOProvider, Dottext.Framework这样会实例化DataDTOProvider类,而DataDTOProvider实现了IDTOProvider接口。通过这样的“复杂”的过程,DTOProvider静态构造了一个可以访问数据库层的接口(DTOProvider.Instance()语句)IDTOProvider,而IDTOProvider中正好可以实现了接口函数int Create(Entry entry, int[] CategoryIDs);这里相对于SQL数据层的细节如下(见得到的是实现类DataDTOProvider):
在这里,实现了对于Entry实体的实体化存储操作。其中的DbProvider又是值得关注的
看到没有,跟DTOProvider又是一样的静态化构造。通过序列化的到具体的DB存储操作实体对象Dottext.Framework.Data.SqlDataProvider, Dottext.Framework。在创建一个Entry对象的DB操作中有如下代码:
SqlDataProvider的InsertEntry操作细节如下:
看到否,这是一个具体的sql存储过程调用代码。就这样,利用配置文件我们指定了BlogProviders 的DTOProvider 和DbProvider 具体实例,而根据不同的配置,他们是可以替换成不同的实例,譬如可以将DB层换成Mysql.或者Orcal的具体表操作。
以上阅读,大家需要注意:
1、 静态构造函数
2、 Activator.CreateInstance(System.Type.GetType(this.ProviderType)); 这种利用反射创建对象实例的方法。
另外就是需要理解,dottext采用配置文件来动态指定DTO和DB操作的精巧设计(虽然有些让人懵头)。
遗憾的是,我发现博客园的版本似乎有问题,很多操作,他们直接操纵了数据库,这样可能会学让我这种不熟悉源版本的人会产生一定的误解。以上分析,希望能够排除大家的疑问,我可是熬了个通宵哦:)
Entries.Update(entry);
此时会执行:
public static int Create(Entry entry)
{
return Create(entry,null);
}
{
return Create(entry,null);
}
静态方法最终调用:
public static int Create(Entry entry, int[] CategoryIDs)
{
HandlerManager.PreCommit(entry, ProcessAction.Insert);
int result = DTOProvider.Instance().Create(entry, CategoryIDs);
if (result > 0)
{
HandlerManager.PostCommit(entry, ProcessAction.Insert);
}
return result;
}
{
HandlerManager.PreCommit(entry, ProcessAction.Insert);
int result = DTOProvider.Instance().Create(entry, CategoryIDs);
if (result > 0)
{
HandlerManager.PostCommit(entry, ProcessAction.Insert);
}
return result;
}
我们主要集中看看
int result = DTOProvider.Instance().Create(entry,CategoryIDs);
该语句执行的需要好好揣摩,DTOProvider 的声明在providers目录下,他有一个静态的声明构造函数
static DTOProvider()
{
DTOProviderConfiguration dtoPC = Config.Settings.BlogProviders.DTOProvider;
idto = (IDTOProvider)dtoPC.Instance();
}
{
DTOProviderConfiguration dtoPC = Config.Settings.BlogProviders.DTOProvider;
idto = (IDTOProvider)dtoPC.Instance();
}
用于在静态调用该类的方法之前执行构造(这里相当于使用了单例模式)。此时会利用前民提到的配置体系获取DTOProvider配置。DTOProviderConfiguration 具有[XmlRoot("DTOProvider")]属性,从<BlogProviders>节获得的XML片断中得到DTOProviderConfiguration,而需要注意这里的DTOProviderConfiguration继承自一个抽象类BaseProvider。Config.Settings.BlogProviders 通过反序列化得到了具体的BlogProvider类,但是我们仅仅想获取DTOProvider的属性,而在我手中的版本该处的配置是Dottext.Framework.Data.DataDTOProvider(注意该类的实现了IDTOProvider接口)。DTOProviderConfiguration类型实际上是抽象类BaseProvider的具体实现,但是加上了[XmlRoot("DTOProvider")]属性(可以反序列化),这样将得到了一个provider类型。
idto = (IDTOProvider)dtoPC.Instance();
dtoPC.Instance();会调用BaseProvider(注意是抽象类)中的
public object Instance()
{
return Activator.CreateInstance(System.Type.GetType(this.ProviderType));
}
{
return Activator.CreateInstance(System.Type.GetType(this.ProviderType));
}
大家看到,这又是一个动态产生类型的方法,也是采用了反射原理。该类的属性ProviderType声明是这样的
[XmlAttribute("type")]
public string ProviderType
{
get { return _type; }
set { _type = value; }
}
public string ProviderType
{
get { return _type; }
set { _type = value; }
}
可以看到,这是从配置文件中读取到的Type值,具体到我察看的工程值是:Dottext.Framework.Data.DataDTOProvider, Dottext.Framework这样会实例化DataDTOProvider类,而DataDTOProvider实现了IDTOProvider接口。通过这样的“复杂”的过程,DTOProvider静态构造了一个可以访问数据库层的接口(DTOProvider.Instance()语句)IDTOProvider,而IDTOProvider中正好可以实现了接口函数int Create(Entry entry, int[] CategoryIDs);这里相对于SQL数据层的细节如下(见得到的是实现类DataDTOProvider):
public int Create(Entry entry, int[] CategoryIDs)
{
if(entry.PostType == PostType.PingTrack)
{
return DbProvider.Instance().InsertPingTrackEntry(entry);// DbProvider稍后解释
}
FormatEntry(ref entry);
if(entry is CategoryEntry)
{
entry.EntryID = DbProvider.Instance().InsertCategoryEntry(((CategoryEntry)entry));
}
else
{
entry.EntryID = DbProvider.Instance().InsertEntry(entry);
if(CategoryIDs != null)
{
DbProvider.Instance().SetEntryCategoryList(entry.EntryID,CategoryIDs);
}
}
if(entry.EntryID > -1)// && Config.Settings.Tracking.UseTrackingServices)
{
entry.Link = Dottext.Framework.Configuration.Config.CurrentBlog().UrlFormats.EntryUrl(entry);
Config.CurrentBlog().LastUpdated = entry.DateCreated;
}
else
{
//we need to fail here to stop the PostCommits?
throw new BlogFailedPostException("Your entry could not be added to the datastore");
}
return entry.EntryID;
}
{
if(entry.PostType == PostType.PingTrack)
{
return DbProvider.Instance().InsertPingTrackEntry(entry);// DbProvider稍后解释
}
FormatEntry(ref entry);
if(entry is CategoryEntry)
{
entry.EntryID = DbProvider.Instance().InsertCategoryEntry(((CategoryEntry)entry));
}
else
{
entry.EntryID = DbProvider.Instance().InsertEntry(entry);
if(CategoryIDs != null)
{
DbProvider.Instance().SetEntryCategoryList(entry.EntryID,CategoryIDs);
}
}
if(entry.EntryID > -1)// && Config.Settings.Tracking.UseTrackingServices)
{
entry.Link = Dottext.Framework.Configuration.Config.CurrentBlog().UrlFormats.EntryUrl(entry);
Config.CurrentBlog().LastUpdated = entry.DateCreated;
}
else
{
//we need to fail here to stop the PostCommits?
throw new BlogFailedPostException("Your entry could not be added to the datastore");
}
return entry.EntryID;
}
在这里,实现了对于Entry实体的实体化存储操作。其中的DbProvider又是值得关注的
static DbProvider()
{
DbProviderConfiguration dpc = Config.Settings.BlogProviders.DbProvider;
dp = (IDbProvider)dpc.Instance();
dp.ConnectionString = dpc.ConnectionString;
}
{
DbProviderConfiguration dpc = Config.Settings.BlogProviders.DbProvider;
dp = (IDbProvider)dpc.Instance();
dp.ConnectionString = dpc.ConnectionString;
}
看到没有,跟DTOProvider又是一样的静态化构造。通过序列化的到具体的DB存储操作实体对象Dottext.Framework.Data.SqlDataProvider, Dottext.Framework。在创建一个Entry对象的DB操作中有如下代码:
entry.EntryID = DbProvider.Instance().InsertEntry(entry);
SqlDataProvider的InsertEntry操作细节如下:
public int InsertEntry(Entry entry)
{
SqlParameter[] p =
{
SqlHelper.MakeInParam("@Title", SqlDbType.NVarChar,255,entry.Title),
SqlHelper.MakeInParam("@TitleUrl", SqlDbType.NVarChar,255,DataHelper.CheckNull(entry.TitleUrl)),
SqlHelper.MakeInParam("@Text",SqlDbType.NText,0,entry.Body),
SqlHelper.MakeInParam("@SourceUrl",SqlDbType.NVarChar,200,DataHelper.CheckNull(entry.SourceUrl)),
SqlHelper.MakeInParam("@PostType",SqlDbType.Int,4,entry.PostType),
SqlHelper.MakeInParam("@Author",SqlDbType.NVarChar,50,DataHelper.CheckNull(entry.Author)),
SqlHelper.MakeInParam("@Email",SqlDbType.NVarChar,50,DataHelper.CheckNull(entry.Email)),
SqlHelper.MakeInParam("@Description",SqlDbType.NVarChar,500,DataHelper.CheckNull(entry.Description)),
SqlHelper.MakeInParam("@SourceName",SqlDbType.NVarChar,200,DataHelper.CheckNull(entry.SourceName)),
SqlHelper.MakeInParam("@DateAdded",SqlDbType.DateTime,8,entry.DateCreated),
SqlHelper.MakeInParam("@PostConfig",SqlDbType.Int,4,entry.PostConfig),
SqlHelper.MakeInParam("@ParentID",SqlDbType.Int,4,entry.ParentID),
SqlHelper.MakeInParam("@EntryName",SqlDbType.NVarChar,150,DataHelper.CheckNull(entry.EntryName)),
BlogIDParam,
SqlHelper.MakeOutParam("@ID",SqlDbType.Int,4)
};
NonQueryInt("blog_InsertEntry",p);
return (int)p[14].Value;
}
{
SqlParameter[] p =
{
SqlHelper.MakeInParam("@Title", SqlDbType.NVarChar,255,entry.Title),
SqlHelper.MakeInParam("@TitleUrl", SqlDbType.NVarChar,255,DataHelper.CheckNull(entry.TitleUrl)),
SqlHelper.MakeInParam("@Text",SqlDbType.NText,0,entry.Body),
SqlHelper.MakeInParam("@SourceUrl",SqlDbType.NVarChar,200,DataHelper.CheckNull(entry.SourceUrl)),
SqlHelper.MakeInParam("@PostType",SqlDbType.Int,4,entry.PostType),
SqlHelper.MakeInParam("@Author",SqlDbType.NVarChar,50,DataHelper.CheckNull(entry.Author)),
SqlHelper.MakeInParam("@Email",SqlDbType.NVarChar,50,DataHelper.CheckNull(entry.Email)),
SqlHelper.MakeInParam("@Description",SqlDbType.NVarChar,500,DataHelper.CheckNull(entry.Description)),
SqlHelper.MakeInParam("@SourceName",SqlDbType.NVarChar,200,DataHelper.CheckNull(entry.SourceName)),
SqlHelper.MakeInParam("@DateAdded",SqlDbType.DateTime,8,entry.DateCreated),
SqlHelper.MakeInParam("@PostConfig",SqlDbType.Int,4,entry.PostConfig),
SqlHelper.MakeInParam("@ParentID",SqlDbType.Int,4,entry.ParentID),
SqlHelper.MakeInParam("@EntryName",SqlDbType.NVarChar,150,DataHelper.CheckNull(entry.EntryName)),
BlogIDParam,
SqlHelper.MakeOutParam("@ID",SqlDbType.Int,4)
};
NonQueryInt("blog_InsertEntry",p);
return (int)p[14].Value;
}
看到否,这是一个具体的sql存储过程调用代码。就这样,利用配置文件我们指定了BlogProviders 的DTOProvider 和DbProvider 具体实例,而根据不同的配置,他们是可以替换成不同的实例,譬如可以将DB层换成Mysql.或者Orcal的具体表操作。
以上阅读,大家需要注意:
1、 静态构造函数
2、 Activator.CreateInstance(System.Type.GetType(this.ProviderType)); 这种利用反射创建对象实例的方法。
另外就是需要理解,dottext采用配置文件来动态指定DTO和DB操作的精巧设计(虽然有些让人懵头)。
遗憾的是,我发现博客园的版本似乎有问题,很多操作,他们直接操纵了数据库,这样可能会学让我这种不熟悉源版本的人会产生一定的误解。以上分析,希望能够排除大家的疑问,我可是熬了个通宵哦:)
以上文章摘自http://dev.csdn.net/develop/article/84/84530.shtm,作者shanhe ,我做了一些修改.
以下是数据库方式时会出现的一个小问题:
1. 在1.1版的SqlConnection中,微软曾经发布了一个 Bug,在服务器承受巨大压力的情况下,可能会有大开的sqlConnection没有关闭.解决办法:
就是用using(SqlConnection myConnection = GetSqlConnection()){}
当using的生命周期结束的时候会自动收回connection.
2.来自dudu的学习手册
1、"ArgumentException The SqlParameter with ParameterName '@EntryID' is already contained by another SqlParameterCollection."
2、"ArgumentException The SqlParameter with ParameterName '@ItemCount' is already contained by another SqlParameterCollection."
摘要:那为什么出现SqlParameterCollection使用同一个SqlParameter的情况?
罪魁祸首就是两个私有静态成员DefaultEntryQueryParameter、DefaultEntryParameters,私有静态成员被 类的所有实例共享。在SqlDataProvider的不同实例的生命周期中, 都共享这两个静态成员。当SqlDataProvider的多个实例同时执行command.Parameters.Add(p)操作时,如果都用到 DefaultEntryQueryParameter或DefaultEntryParameters,就会引发异常"...is already contained by another SqlParameterCollection."
解决这个问题的方法除了前面的每次调用DefaultEntryQueryParameter或
DefaultEntryParameters, 重新创建SqlParameter[],也可以将DefaultEntryQueryParameter与DefaultEntryParameters 变成非静态私有成员,但这种在\方法在多线程的情况下,也会出现同样的问题。最安全的方法就是每次使用SqlParameter,都重新创建 SqlParameter的实例。
这个bug一直存在.Text中,那为什么现在才发现?而且有很多.Text的网站为什么没有发现这个Bug?因为这个Bug只会出
现 在ExecuteReader中,所以即使发生异常,对系统没什么影响,只要重新刷新一下就行了。而且这个异常只会出现在SqlDataProvider 的多个实例同时执行command.Parameters.Add(p)操作时,同时发生的概率与网站的访问量有关。以前博客园很少出现这个异常,最近因 为博客园访问量变大,同时执行command.Parameters.Add(p)的概率变高了,所以异常出现的次数也变多了。
从这个Bug中,我们应该吸取两个教训:
1、慎用私有静态成员。
2、安全地使用SqlParameter,每次使用,每次新建。
以下是数据库方式时会出现的一个小问题:
1. 在1.1版的SqlConnection中,微软曾经发布了一个 Bug,在服务器承受巨大压力的情况下,可能会有大开的sqlConnection没有关闭.解决办法:
private PostSet GetModeratedPostsByForumId(int forumId)
{
// return all of the forums and their total and daily posts
// Create Instance of Connection and Command Object
using( SqlConnection myConnection = GetSqlConnection() ) {
SqlCommand myCommand = new SqlCommand(databaseOwner + ".forums_GetModeratedPostsByForumId", myConnection);
//数据库操作
.............................................................
while (dr.Read())
postSet.Posts.Add ( PopulatePostFromIDataReader(dr) );
dr.Close();
myConnection.Close();
return postSet;
}
}
{
// return all of the forums and their total and daily posts
// Create Instance of Connection and Command Object
using( SqlConnection myConnection = GetSqlConnection() ) {
SqlCommand myCommand = new SqlCommand(databaseOwner + ".forums_GetModeratedPostsByForumId", myConnection);
//数据库操作
.............................................................
while (dr.Read())
postSet.Posts.Add ( PopulatePostFromIDataReader(dr) );
dr.Close();
myConnection.Close();
return postSet;
}
}
就是用using(SqlConnection myConnection = GetSqlConnection()){}
当using的生命周期结束的时候会自动收回connection.
2.来自dudu的学习手册
.Text中SqlParameter引起的Bug
BUG:1、"ArgumentException The SqlParameter with ParameterName '@EntryID' is already contained by another SqlParameterCollection."
2、"ArgumentException The SqlParameter with ParameterName '@ItemCount' is already contained by another SqlParameterCollection."
摘要:那为什么出现SqlParameterCollection使用同一个SqlParameter的情况?
罪魁祸首就是两个私有静态成员DefaultEntryQueryParameter、DefaultEntryParameters,私有静态成员被 类的所有实例共享。在SqlDataProvider的不同实例的生命周期中, 都共享这两个静态成员。当SqlDataProvider的多个实例同时执行command.Parameters.Add(p)操作时,如果都用到 DefaultEntryQueryParameter或DefaultEntryParameters,就会引发异常"...is already contained by another SqlParameterCollection."
解决这个问题的方法除了前面的每次调用DefaultEntryQueryParameter或
DefaultEntryParameters, 重新创建SqlParameter[],也可以将DefaultEntryQueryParameter与DefaultEntryParameters 变成非静态私有成员,但这种在\方法在多线程的情况下,也会出现同样的问题。最安全的方法就是每次使用SqlParameter,都重新创建 SqlParameter的实例。
这个bug一直存在.Text中,那为什么现在才发现?而且有很多.Text的网站为什么没有发现这个Bug?因为这个Bug只会出
现 在ExecuteReader中,所以即使发生异常,对系统没什么影响,只要重新刷新一下就行了。而且这个异常只会出现在SqlDataProvider 的多个实例同时执行command.Parameters.Add(p)操作时,同时发生的概率与网站的访问量有关。以前博客园很少出现这个异常,最近因 为博客园访问量变大,同时执行command.Parameters.Add(p)的概率变高了,所以异常出现的次数也变多了。
从这个Bug中,我们应该吸取两个教训:
1、慎用私有静态成员。
2、安全地使用SqlParameter,每次使用,每次新建。