代码下载:FormviewUse
花了两天的空闲时间coding这个东西。虽然不算一个完整的项目,但是它完全是一个架构,所谓的3层结构,它主要是介绍了一个控件Formview的使用,如果你没使用过Formview希望这篇文章里的知识能够让你在以后的项目里使用到formview,因为它确实是个比较好用的控件。可能你现在有一些生成3层结构代码的工具,或者你很熟悉3层结构了,尽管如此你还是可以在这里学到很多不一样的东西。至少是在framework2.0之后的知识。我的架构如下图:
是不是不大一样啊?????
数据访问层面这里不再需要去生成那么多无聊的SQL,修改时都会很麻烦,而如果使用Linq,就算是表结构的修改去我们数据访问层的影响都是微乎其微的。对于性能方面,请不要担忧,它不比SQL差多少,你可以使用Profiler去测试下。
如果你是个Linq的小白,那么建议你可以读下面这部分,但是如果你很熟悉Dlinq,Xlinq的话建议直接跳过这部分。
Part 1: Linq的学习资料:
Wikipedia:
Codeproject:[url=http://www.codeproject.com/KB/linq/]
LINQ Video: http://windowsclient.net/learn/videos_LINQ.aspx,
http://www.asp.net/learn/linq-videos/
LINQ Article by Anders: http://msdn.microsoft.com/en-us/library/bb308959.aspx
LINQ at MSDN:http://msdn.microsoft.com/en-us/library/bb397926.aspx
LINQ Project Home page at MSDN: http://msdn.microsoft.com/en-us/netframework/aa904594.aspx
LINQ FAQ: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=307705&SiteID=1
ScottGu's Blog:
Part1:http://weblogs.asp.net/scottgu/archive/2007/05/19/using-linq-to-sql-part-1.aspx
Part2:http://weblogs.asp.net/scottgu/archive/2007/05/29/linq-to-sql-part-2-defining-our-data-model-classes.aspx
Part3:http://weblogs.asp.net/scottgu/archive/2007/06/29/linq-to-sql-part-3-querying-our-database.aspx
Part4:http://weblogs.asp.net/scottgu/archive/2007/07/11/linq-to-sql-part-4-updating-our-database.aspx
Part5:http://weblogs.asp.net/scottgu/archive/2007/07/16/linq-to-sql-part-5-binding-ui-using-the-asp-linqdatasource-control.aspx
Part6:http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx
Part7:http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx
Part8:http://weblogs.asp.net/scottgu/archive/2007/08/27/linq-to-sql-part-8-executing-custom-sql-expressions.aspx
Part9:http://weblogs.asp.net/scottgu/archive/2007/09/07/linq-to-sql-part-9-using-a-custom-linq-expression-with-the-lt-asp-linqdatasource-gt-control.aspx
ScottGu的博客有中文翻译的:
http://blog.joycode.com/scottgu/ 或许能找到上面的文章。
Step by step...
Introducing LINQ – Part 1http://dotnetslackers.com/articles/csharp/IntroducingLINQ1.aspx
Introducing LINQ – Part 2http://dotnetslackers.com/articles/csharp/IntroducingLINQ2.aspx
Introducing LINQ – Part 3http://dotnetslackers.com/articles/csharp/IntroducingLINQ2.aspx
Introducing LINQ – Part 4http://dotnetslackers.com/articles/csharp/IntroducingLINQ4.aspx
Introducing LINQ – Part 5http://dotnetslackers.com/articles/csharp/IntroducingLINQ5.aspx
Part2 : 关于Business层面的不同。
不知道你是否用过Microsoft.Practices的EL,我这里使用的是Microsoft.Practices的Unity。关于Unity的介绍在codeplex上有它的opensource项目,如果你英文还好那你可以过去看看。我有写过如果使用它,文章地址:
http://blog.csdn.net/dujingjing1230/archive/2009/12/22/5055613.aspx
Omar在dropthings写的一段话告诉你Unity绝对颠覆了一些传统的coding.
使用Unity你不再需要在business layer和data access layer之间通过实例化那么多的类来实现两个层级的通讯,你只需要使用Resolve<interface>()来代替 new Classname(),它的灵活性也是比传统的不停地使用Instance要好很多。至少能减少你很多代码的编写。
关于Unity的介绍这里不再多说。
Part 3:Formview的使用
Formview控件的使用比较适合对于GridView记录的编辑查看等。至少这是我目前感觉到的。GridView的edittemplate,InsertTemplate确实可以让你的更新和新增更加容易,但是如果每个页面显示50条记录时,使用本身的edittemplate客户提样将会很差,所以使用formview可以有更好的用户体验。
你也可以使用Popup Window来实现。比如jQuery的一些popup的plugin,或者是ajaxcontroltoolkit的ModalPopup控件来实现。这里有个例子:
http://blog.csdn.net/dujingjing1230/archive/2009/11/03/4763791.aspx
废话不多说了,进入正题吧,先详细说明下数据访问层的实现:
D1: 创建一个solution,然后添加一个project,我起名为LinqDAL,然后添加一个Linqtosql类:
D2:数据库的链接和表的创建:
我已经在数据库中创建了一个Club的表,包括字段ID,ClubName和URL三个字段。ID为自增字段。把这个表拽入第一步中创建的LINQ to SQL 类的设计模式:
然后是数据访问的代码编写,先看看数据访问层类图:
D3:可以说是分成三部分,第一部分是Linq to SQL自动生成的代码,而clubcontext2是继承它的一个获取数据库连接字符串的类,这样在Web层面clubcontext2可以得到config文件中的连接字符。IclubDataContent接口中定义了所有的可能用到的delete,insert,update和getlist的方法,它的存在就是为了在Business层面使用Microsoft.practices.Unity。
看看它的Methods代码:
void Delete<TSource>(ClubDataContextDataContext.SubsystemEnum subsystem, TSource entity)
where TSource : class;
void DeleteByPK<TSource, TPK>(ClubDataContextDataContext.SubsystemEnum subsystem, TPK pk)
where TSource : class;
void DeleteByPK<TSource, TPK>(TPK[] pkArray, System.Data.Linq.DataContext data)
where TSource : class;
void DeleteByPK<TSource, TPK>(ClubDataContextDataContext.SubsystemEnum subsystem, TPK[] pkArray)
where TSource : class;
void DeleteByPK<TSource, TPK>(TPK pk, System.Data.Linq.DataContext dc)
where TSource : class;
void DeleteList<TSource>(ClubDataContextDataContext.SubsystemEnum subsystem, System.Collections.Generic.List<TSource> list)
where TSource : class;
void Dispose();
//-------------------------------------------------------Return type is a Generic List ---------------------------------------------
System.Collections.Generic.List<TSource> GetList<TSource>(ClubDataContextDataContext.SubsystemEnum subsystem, Func<ClubDataContextDataContext, System.Linq.IQueryable<TSource>> func);
System.Collections.Generic.List<TSource> GetList<TSource, TArg0>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, Func<ClubDataContextDataContext, TArg0, System.Linq.IQueryable<TSource>> func, System.Data.Linq.DataLoadOptions options);
System.Collections.Generic.List<TSource> GetList<TSource, TArg0, TArg1>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, TArg1 arg1, Func<ClubDataContextDataContext, TArg0, TArg1, System.Linq.IQueryable<TSource>> func);
System.Collections.Generic.List<TSource> GetList<TSource, TArg0, TArg1, TArg2>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, TArg1 arg1, TArg2 arg2, Func<ClubDataContextDataContext, TArg0, TArg1, TArg2, System.Linq.IQueryable<TSource>> func);
System.Collections.Generic.List<TSource> GetList<TSource, TArg0>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, Func<ClubDataContextDataContext, TArg0, System.Linq.IQueryable<TSource>> func);
//------------------------------------------------------ Return type is a Custom type ----------------------------------------------
TReturnType GetQueryResult<TSource, TArg0, TArg1, TReturnType>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, TArg1 arg1, Func<ClubDataContextDataContext, TArg0, TArg1, System.Linq.IQueryable<TSource>> func, Func<System.Linq.IQueryable<TSource>, TReturnType> returnExpected);
TReturnType GetQueryResult<TSource, TArg0, TArg1, TArg2, TReturnType>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, TArg1 arg1, TArg2 arg2, Func<ClubDataContextDataContext, TArg0, TArg1, TArg2, System.Linq.IQueryable<TSource>> func, Func<System.Linq.IQueryable<TSource>, TReturnType> returnExpected);
TReturnType GetQueryResult<TSource, TArg0, TArg1, TReturnType>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, TArg1 arg1, Func<ClubDataContextDataContext, TArg0, TArg1, System.Linq.IQueryable<TSource>> func, Func<System.Linq.IQueryable<TSource>, TReturnType> returnExpected, System.Data.Linq.DataLoadOptions options);
TReturnType GetQueryResult<TSource, TArg0, TReturnType>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, Func<ClubDataContextDataContext, TArg0, System.Linq.IQueryable<TSource>> func, Func<System.Linq.IQueryable<TSource>, TReturnType> returnExpected, System.Data.Linq.DataLoadOptions options);
TReturnType GetQueryResult<TSource, TArg0, TReturnType>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, Func<ClubDataContextDataContext, TArg0, System.Linq.IQueryable<TSource>> func, Func<System.Linq.IQueryable<TSource>, TReturnType> returnExpected);
TReturnType GetQueryResult<TSource, TReturnType>(ClubDataContextDataContext.SubsystemEnum subsystem, Func<ClubDataContextDataContext, System.Linq.IQueryable<TSource>> func, Func<System.Linq.IQueryable<TSource>, TReturnType> returnExpected);
//------------------------------------------------------------Return value is a param ------------------------------------------------
TSource GetSingle<TSource, TArg0, TArg1>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, TArg1 arg1, Func<ClubDataContextDataContext, TArg0, TArg1, System.Linq.IQueryable<TSource>> func);
TSource GetSingle<TSource, TArg0>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, Func<ClubDataContextDataContext, TArg0, System.Linq.IQueryable<TSource>> func);
TSource GetSingle<TSource>(ClubDataContextDataContext.SubsystemEnum subsystem, Func<ClubDataContextDataContext, System.Linq.IQueryable<TSource>> func);
TSource GetSingle<TSource, TArg0, TArg1, TArg2>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, TArg1 arg1, TArg2 arg2, Func<ClubDataContextDataContext, TArg0, TArg1, TArg2, System.Linq.IQueryable<TSource>> func);
//---------------------------------------------------------Whether in Context or Not ----------------------------------------------------
T InDataContext<T>(ClubDataContextDataContext.SubsystemEnum subsystem, bool nolock, Func<ClubDataContextDataContext,T> f);
void InDataContext(ClubDataContextDataContext.SubsystemEnum subsystem,bool nolock,ClubDataContextDataContext.DataContextDelegate d);
//-------------------------------------------------------Object-Insert Methods for Club ----------------
TSource Insert<TSource>(ClubDataContextDataContext.SubsystemEnum subsystem,Action<TSource> populate)
where TSource :class,new();
void InsertList<TEntity,TSomething>(ClubDataContextDataContext.SubsystemEnum subsystem,System.Collections.Generic.IEnumerable<TSomething> items,Converter<TSomething,TEntity> converter)
where TEntity:class
where TSomething:class;
//-------------------------------------------------------Object-Update Methods for Club ----------------------------------------------------------
void UpdateList<TEntity>(ClubDataContextDataContext.SubsystemEnum subsystem, System.Collections.Generic.IList<TEntity> list, Action<TEntity> detach,Action<TEntity> postAttachUpdate)
where TEntity:class;
void UpdateObject<TEntity>(ClubDataContextDataContext.SubsystemEnum subsystem, TEntity obj, Action<TEntity> detach, Action<TEntity> postAttachUpdate)
where TEntity : class;
void UpdateObject<TEntity, TArg0>(ClubDataContextDataContext.SubsystemEnum subsystem, TArg0 arg0, Func<ClubDataContextDataContext, TArg0, System.Linq.IQueryable<TEntity>> func, Action<TEntity> postAttachUpdate)
where TEntity : class;
然后是第二部分DatabaseHelper类,说实话它并没有继承IClubDataContent,只是它的方法和IClubDataContent完全相同,它只是为了可以使用不同的数据库而已。在第二部分还有个LinqQueries类,Linq的语法这里就用到了。呵呵。因为我这里只有一个实体Club只需要用到两个方法:
public static readonly Func<ClubDataContextDataContext, int, IQueryable<Club>> CompiledQuery_GetClubById =
CompiledQuery.Compile<ClubDataContextDataContext, int, IQueryable<Club>>((dc, clubId) =>
from club in dc.Clubs
where club.ID == clubId
select club
);
public static readonly Func<ClubDataContextDataContext,IQueryable<Club>> CompiledQuery_GetClubs =
CompiledQuery.Compile<ClubDataContextDataContext, IQueryable<Club>>((dc) =>
from club in dc.Clubs
select club
);
这里就是Linq代替SQL的Select功能体现。
第三部分是ClubRespository,在Business层面对Club的操作方法都这里定义,因为我们使用Unity,所以这里同样需要一个IClubRespository接口和ClubRespository类。
注意这里的IClubDataContext的定义,看到了吧,完全是用接口来定义,不涉及到ClubDataContext的实例化。
Timelog是个帮助类,这里就不说了。
下面我们来完成Business层面的代码:
B1: 创建一个项目命名为Business,注意一定要添加引用:
B2: 它里面的东西不多,就三部分,一个是DashboardFace用来通过sqlhelper类得到Clublist,还有最重要的就是那个Business.Container命名空间中的类,它封装好了使用Unity的所有方法,后面我们对于数据层接口的访问全靠它来实例化了:
代码比较长我就不贴了,主要说明下它的构成:
1. RegisterInstance部分:在Web层面,当加载页面时使用这些方法可以让Interface直接实例化,
2. Resolve部分: 真正的去把Interface转化为Instance。
3. InjectIntoConstructor: 方法所属的类的实例化。
B3: 是Dashboardfacade类:
它主要是Business层面提供Clublist的方法:
public static List<Club> GetClubList()
{
return DatabaseHelper.GetList<Club>(DatabaseHelper.SubsystemEnum.Club, LinqQueries.CompiledQuery_GetClubs);
}
在web层面会把这个list添加到一个datatable中绑定到Gridview。
下面一部分是Business.Facede部分:
BF1: Facade部分的Façade类有两个partial类组成,一个是façade,它包含Façade的构造函数和接口的注册函数:
public static void BootStrap()
{
ServiceLocator.RegisterType<IClubDataContext, ClubDataContext2>();
ServiceLocator.InjectIntoConstructor<ClubDataContext2>(); //dummy injection for empty constructor
ServiceLocator.RegisterType<IClubRepository, ClubRepository>();
}
public Facade(AppContext context) :
this( context, ServiceLocator.Resolve<IClubRepository>())
{
this.Context = context;
}
因为这里只有一个Club实体,所以Resolve()只有一个。。。
BF2: 在façade.club文件中我们定义了Club文件在Business层面的Insert,Delete和Update的方法。贴个Update方法的代码:
Update()方法是个的cl是个delegate,它在数据访问层定义好了,使用的是Collection.Generic中的委托。
上图中的Model是3层结构中的Model部分,这里只包含了个Club,Club的结构和数据库中Club表的结构相同。就不再说Model了。
现在我们已经搞定了DAL和BLL层的代码和两部分的连接。最后我们来看Web层如何实现了,上面有说到我会用到Formview。
W1.创建一个Website,然后把各个生成好的dll引用进来。创建一个User Control:
ClubControl.ascx 。
它包含一个Formview和一个Gridview。页面代码:
具体代码在我提供的代码中看吧,这里不贴了。后台的代码实现我介绍下:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
gvClubList.DataSource = ClubList.BuildClubList();
gvClubList.DataBind();
FormView1.ChangeMode(FormViewMode.ReadOnly);
}
}
这里的BuildClubList()在ClubList类中,它从Business层面得到Club的List并把这些Club存到一个datatable中。
W2:Formview的时间绑定:
protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e)
{
switch (e.CommandName)
{
case "UpdateInfo":
UpdateClub();
FormView1.ChangeMode(FormViewMode.ReadOnly);
gvClubList.DataSource = ClubList.dt;
gvClubList.DataBind();
break;
case "SubmitInfo":
AddClub();
FormView1.ChangeMode(FormViewMode.ReadOnly);
gvClubList.DataSource = ClubList.dt;
gvClubList.DataBind();
break;
case "DeleteInfo":
DeleteClub();
FormView1.ChangeMode(FormViewMode.ReadOnly);
gvClubList.DataSource = ClubList.dt;
gvClubList.DataBind();
break;
case "CancelUpdate":
FormView1.ChangeMode(FormViewMode.ReadOnly);
break;
case "CancelInsert":
FormView1.ChangeMode(FormViewMode.ReadOnly);
break;
default:
break;
}
FormView1.DataBind();
}
例如:
<asp:LinkButton ID="LinkButton20" runat="server" CommandName="SubmitInfo" Text="SubmitInfo">Commit</asp:LinkButton>
当点击它时,ItemCommand为SubmitInfo,也就是说会执行AddClub()方法,
在AddClub方法中,我们使用到了Façade:
当façade被实例化时,它会自动把Façade.Club.cs里面的类实例化,这就是Unity的魅力所在,我完全不需要去定义一个Club类的实例,然后club.InsertClubInstance()。其它几个方法和这个差不多。
对了,忘了一个很重要的事情:在Global.asax文件中去执行Business.Facade.Facade.BootStrap();
最后就是在default.aspx页面中把ClubControl.ascx添加到UpdatePanel中。
编译下,看看结果:
但是当我在Update一条记录时出错了,提示如下:
An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
Google了下,找到了原因,是因为Linq本身对于每条记录都有个时间的记录,参考文章:
http://www.west-wind.com/weblog/posts/134095.aspx
http://geekswithblogs.net/michelotti/archive/2007/12/17/117791.aspx
http://geekswithblogs.net/AzamSharp/archive/2008/05/17/122222.aspx
最后设置timestamp为true,再次去update记录就OK了。
圣诞节到了,这个算是我送给.net用户的礼物吧。如果有不好的地方希望大家多多指教。。如果你觉得有啥建议,请留言吧。