C# 中的多层框架






4.25/5 (3投票s)
使用 Microsoft SQL Server 或 XML 文件作为数据存储
引言
这是一个逐步指南,教你如何使用基于 Linq to SQL 和/或 Linq to XML 的自定义 N 层框架。 这个由 abstract
类和接口构成的框架应该可以完成重复性的工作,以便你可以专注于业务逻辑。
在附加的解决方案中,你可以找到一个控制台项目来创建测试数据库。 不要忘记自定义控制台项目和网站项目中的配置文件!
背景
我启动这个项目的原因是我厌倦了重复相同的事情。 我试图创建一个模板解决方案,以便我可以将其用于其他未来的项目。 我的目的不是要自动化一切,而是尽可能地将控制权交给开发人员。
Using the Code
添加新项目的步骤
1. 项目 EntityModel
将新类拖动或添加到你的 DataContext
并实现 ILinqEntity
接口(必需)
partial class Item : ILinqEntity {
}
2. 项目 Core
2.1 创建一个新的业务对象,继承自EntityBase
(必需)并实现接口(可选)public class Item : EntityBase, IEntityWithDescription, IEntityWithOnlineCheck {
public string Title { get; set; }
public string Description { get; set; }
public decimal? Price { get; set; }
public bool Online { get; set; }
public object SectionId { get; set; }
public Section Section { get; set; }
public Item()
: base() {
Online = true;
}
}
可能的接口
IEntityWithTitle
IEntityWithDescription
IEntityWithSortOrder
IEntityWithOnline
2.2 创建一个新的 SearchObject
(可选),继承自 SearchObjectBase
(必需)并实现接口(可选)。
SearchObject 是一种围绕 Dictionary 的包装器,可以扩展到其他类型的 SearchObject。 这将在稍后的 DAL 中启用自动连接。 它还可以从类似 QueryString 的字符串填充其参数,这在 usercontrol 中很有用。
public enum SortItemsBy
{
DateInputDesc = 0,
TitleAsc = 1,
TitleDesc = 2,
PriceAsc = 3,
PriceDesc = 4,
DateInputAsc = 5,
DateUpdateAsc = 6,
DateUpdateDesc = 7,
Nothing = 8
}
public class ItemSearchObject : SearchObjectBase,
ISearchObjectWithOnline, ISearchObjectWithParentId
{
protected override string Prefix {
get { return "i_"; }
}
public object ParentId {
get { return SectionSearchObject.ItemId; }
set { SectionSearchObject.ItemId = value; }
}
public decimal? PriceMin {
get { return GetDecimal("PriceMin"); }
set { SetDecimal("PriceMin", value); }
}
public decimal? PriceMax {
get { return GetDecimal("PriceMax"); }
set { SetDecimal("PriceMax", value); }
}
public bool? Online {
get { return GetBoolean("Online"); }
set { SetBoolean("Online", value); }
}
public SortItemsBy SortBy {
get { return (SortItemsBy)(GetInt("sort") ?? 0); }
set {
if (value != SortItemsBy.DateInputDesc)
SetInt("sort", (int)value);
else
SetInt("sort", null);
}
}
public override bool HasParameters {
get {
return base.HasParameters
|| ParentId != null
|| PriceMin.HasValue
|| PriceMax.HasValue
|| Online.HasValue;
}
}
public override bool HasExtendedParameters {
get {
return HasParameters
|| HasSectionParameters;
}
}
public bool HasSectionParameters {
get { return SectionSearchObject.HasParameters; }
}
public SectionSearchObject SectionSearchObject {
get { return ExtendTo<SectionSearchObject>(); }
}
protected internal override string GetStringFromExtendedParameters() {
return SectionSearchObject.GetStringFromParameters();
}
可能的接口
ISearchObjectWithOnline
ISearchObjectWithParentId
SearchObjectFactory
类(如果您创建了 SearchObject
,则为必需)。添加以下行
else if (typeof(T) == typeof(Item))
return new ItemSearchObject();
3. 项目 DAL
3.1 创建一个服务,继承自 SqlServiceBase<T,Y>
或 XmlServiceBase<T>
(必需)
using Core;
using EntityModel;
using LinqItem = EntityModel.Item;
using CoreItem = Core.Item;
public class SqlItemService : SqlServiceBase<CoreItem, LinqItem>
{
SqlSectionService ss;
public SqlItemService()
: base() {
}
public SqlItemService(string connectionstring)
: base(connectionstring) {
}
internal SqlItemService(EntityDataContext dc)
: base(dc) {
}
public override CoreItem GetItem(object id) {
IQueryable<LinqItem> query = base.Table.Where(i => i.Id.Equals(id));
return base.GetItem(query);
}
protected internal override IQueryable<LinqItem>
FilterQuery(IQueryable<LinqItem> query, ISearchObject so) {
ItemSearchObject iso = so.ExtendTo<ItemSearchObject>();
if (iso.ItemId != null)
query = query.Where(i => iso.ItemId.Equals(i.Id));
if (iso.PriceMin.HasValue)
query = query.Where(i => i.Price >= iso.PriceMin.Value);
if (iso.PriceMax.HasValue)
query = query.Where(i => i.Price <= iso.PriceMax.Value);
if (iso.Online.HasValue)
query = query.Where(i => iso.Online.Value.Equals(i.Online));
if (!String.IsNullOrEmpty(iso.Keywords))
foreach (string keyword in iso.KeywordList) {
string kw = keyword; //otherwise the SQL query will only
// use the last keyword
query = query.Where(i => i.Title.Contains(kw) ||
i.Description.Contains(kw));
}
return query;
}
protected internal override IQueryable<CoreItem>
SortQuery(IQueryable<CoreItem> query, ISearchObject so) {
ItemSearchObject sso = so.CopyTo<ItemSearchObject>();
switch (sso.SortBy) {
case SortItemsBy.TitleAsc:
query = query.OrderBy(q => q.Title);
break;
case SortItemsBy.TitleDesc:
query = query.OrderByDescending(q => q.Title);
break;
case SortItemsBy.PriceAsc:
query = query.OrderBy(q => q.Price);
break;
case SortItemsBy.PriceDesc:
query = query.OrderByDescending(q => q.Price);
break;
case SortItemsBy.DateInputAsc:
query = query.OrderBy(q => q.DateInput);
break;
case SortItemsBy.DateInputDesc:
query = query.OrderByDescending(q => q.DateInput);
break;
case SortItemsBy.DateUpdateAsc:
query = query.OrderBy(q => q.DateUpdate);
break;
case SortItemsBy.DateUpdateDesc:
query = query.OrderByDescending(q => q.DateUpdate);
break;
}
return query;
}
protected internal override IQueryable<LinqItem>
GetParentItemsQuery(IQueryable<LinqItem> query, ISearchObject so) {
if (so.ExtendTo<ItemSearchObject>().HasSectionParameters) {
ss = new SqlSectionService(DataContext);
query = query.Join(
ss.GetQuery(so, FilterInclude.Parent),
i => i.SectionId,
s => s.Id,
(i, s) => i
);
}
return query.Distinct();
}
protected internal override LinqItem ConvertCoreEntity(CoreItem item) {
if (item == null)
return null;
LinqItem lItem = new LinqItem();
int itemId = 0;
if (item.Id != null)
Int32.TryParse(item.Id.ToString(), out itemId);
lItem.Id = itemId;
int parentId = 0;
if (item.SectionId != null)
if (Int32.TryParse(item.SectionId.ToString(), out parentId))
lItem.SectionId = parentId;
lItem.Title = item.Title;
lItem.Description = item.Description;
lItem.Price = item.Price;
lItem.Online = item.Online;
lItem.DateInput = item.DateInput;
lItem.DateUpdate = item.DateUpdate;
return lItem;
}
protected internal override CoreItem ConvertLinqEntity(LinqItem item) {
if (item == null)
return null;
CoreItem cItem = new CoreItem();
cItem.Id = item.Id;
cItem.SectionId = item.SectionId;
cItem.Title = item.Title;
cItem.Description = item.Description;
cItem.Online = item.Online;
cItem.DateInput = item.DateInput;
cItem.DateUpdate = item.DateUpdate;
return cItem;
}
protected internal override IQueryable<CoreItem>
ConvertLinqEntityQuery(IQueryable<LinqItem> query) {
return query.Select(i =>
new CoreItem {
Id = i.Id,
SectionId = i.SectionId,
Title = i.Title,
Description = i.Description,
Price = i.Price,
Online = i.Online,
DateInput = i.DateInput,
DateUpdate = i.DateUpdate
}
);
}
public override void Dispose() {
if (ss != null)
ss.Dispose();
base.Dispose();
}
}
3.2 修改 ServiceFactory
(必需)。
添加以下行
else if (typeof(T) == typeof(Item))
return new SqlItemService() as IService<T>;
4. 项目 BLL
4.1 创建一个 manager,继承自 ManagerBase<T>
(只读)或 ManagerModifiableBase<T>
(必需),并实现接口(可选)
public class ItemManager : ManagerModifiableBase<Item>,
IManagerWithSearchObject<Item>
{
public Item GetItem(ISearchObject so) {
return base.GetItemBySearchObject(so);
}
public IList- GetItems(ISearchObject so) {
return base.GetItemsBySearchObject(so);
}
public int GetCount(ISearchObject so) {
return base.GetCountBySearchObject(so);
}
protected override bool IsValidInput(Item item) {
return base.IsValidInput(item)
&& item.SectionId != null
&& !String.IsNullOrEmpty(item.Title)
&& !String.IsNullOrEmpty(item.Description);
}
}
可能的接口
IManagerWithSearchObject<T>
IManagerCachable<T>
IManagerModifiable<T>
4.2 修改 ManagerFactory<T>
(必需)。
添加以下行
if (typeof(T) == typeof(Item))
return new ItemManager() as IManager<T>;
5. Website.Core
修改 UrlManager
(可选)
public static string CreateDetailUrl(Item item, QueryStringManager q) {
return createUrl("/ItemDetail.aspx", new ItemSearchObject()
{ ItemId = item.Id }, q);
}
public static string CreateListUrl(ItemSearchObject iso, QueryStringManager q) {
return createUrl("/ItemList.aspx", iso, q);
}
6. Website
此控件将根据 QueryString 参数自动过滤结果
<asp:Repeater ID="ItemRepeater" runat="server"
onitemdatabound="ItemRepeater_ItemDataBound">
<ItemTemplate>
<p>
<strong><asp:Literal ID="TitleLiteral" runat="server" /></strong>
(<asp:Literal ID="PriceLiteral" runat="server" />)
</p>
</ItemTemplate>
</asp:Repeater>
public partial class ItemList : ListControl<Item>
{
protected void ItemRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e) {
Item item = e.Item.DataItem as Item;
Literal title = e.Item.FindControl("TitleLiteral") as Literal;
title.Text = "<a href=\"" + UrlManager.CreateDetailUrl(item, Q) + "\">"
+ StringHelper.StripHtml(item.Title) + "</a>";
Literal price = e.Item.FindControl("PriceLiteral") as Literal;
price.Text = StringHelper.ShowPrice(item.Price);
}
protected override void FillControl() {
ItemRepeater.DataSource = base.Items;
ItemRepeater.DataBind();
}
}
此控件将根据 QueryString 参数中包含的 ID 自动获取项目
<h3><asp:Literal ID="TitleLiteral" runat="server" /></h3>
<p><asp:Literal ID="DescriptionLiteral" runat="server" /></p>
<p><b>Price:</b> <asp:Literal ID="PriceLiteral" runat="server" /></p>
public partial class ItemDetail : DetailControl<Item>
{
protected override void FillControl() {
TitleLiteral.Text = base.Item.Title;
DescriptionLiteral.Text = base.Item.Description;
PriceLiteral.Text = StringHelper.ShowPrice(base.Item.Price ?? 0);
}
}
<p><i><asp:Literal ID="CountLiteral" runat="server" Text="No" /> item(s) found</i></p>
<uc1:ItemList ID="ItemListControl" runat="server" />
public partial class ItemListPage : CustomPage
{
protected override void AfterLoad() {
base.AfterLoad();
CountLiteral.Text = ItemListControl.TotalCount.ToString();
}
}
历史
- 2010 年 9 月 23 日:首次发布