65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的多层框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (3投票s)

2010年9月23日

CPOL

2分钟阅读

viewsIcon

24012

downloadIcon

408

使用 Microsoft SQL Server 或 XML 文件作为数据存储

引言

这是一个逐步指南,教你如何使用基于 Linq to SQL 和/或 Linq to XML 的自定义 N 层框架。 这个由 abstract 类和接口构成的框架应该可以完成重复性的工作,以便你可以专注于业务逻辑。

在附加的解决方案中,你可以找到一个控制台项目来创建测试数据库。 不要忘记自定义控制台项目和网站项目中的配置文件!

背景

我启动这个项目的原因是我厌倦了重复相同的事情。 我试图创建一个模板解决方案,以便我可以将其用于其他未来的项目。 我的目的不是要自动化一切,而是尽可能地将控制权交给开发人员。

Using the Code

添加新项目的步骤

  1. 项目 EntityModel
  2. 项目 Core
    1. 创建一个业务对象
    2. 创建一个 SearchObject(可选)
    3. 修改 SearchObjectFactory
  3. 项目 DAL
    1. 创建一个服务
    2. 修改 ServiceFactory
  4. 项目 BLL
    1. 创建一个 Manager
    2. 修改 ManagerFactory
  5. Website.Core(可选)
  6. 网站
    1. 创建一个 ListControl
    2. 创建一个 DetailControl
    3. 创建页面

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
2.3 修改 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

6.1 创建一个 ListControl

此控件将根据 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();
    }
}

6.2 创建一个 DetailControl

此控件将根据 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);
    }
}

6.3 创建一个或多个页面

<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 日:首次发布
© . All rights reserved.