使用 MyGeneration 框架和 Visual Studio C# .NET 进行面向对象应用程序开发的完整指南






4.89/5 (16投票s)
一篇关于使用 MyGeneration 代码生成框架进行应用程序开发的完整指南。本文将开发一个会议室预订 Web 应用程序。
- 下载 Published_Web_Application.zip - 3.4 MB
- 下载源代码 - 9.3 MB
- 下载 Data_Model_SQLs_and_Backup_File.zip - 144.2 KB
- 下载 My_Generation_Templates.zip - 15.9 KB
简介
在本文中,我将解释在 MyGeneration 代码生成平台的基础上开发 Microsoft.NET Web 应用程序(此处为 ASP.NET C#)所涉及的步骤。我将通过一个小型 MS SQL 2008 Express 数据库开发一个示例应用程序。虽然本文使用 Web 应用程序和 Microsoft SQL Server,但该解决方案也适用于 MySQL 和其他 UI 类型,如 Windows、控制台应用程序。最重要的是,我对这种方法充满信心,因为我已将其用于商业应用程序中的大型项目,并且运行良好。
第一部分 - 理解应用程序需求
理解需求
这是软件开发中最重要的方面之一。它与医生在开始任何治疗之前诊断疾病同样重要。一旦您正确地获得了需求,开发就会变得更加清晰。我不会在这里花一整天的时间,因为这是一个庞大的领域,您可以在网上或图书馆找到大量的文章、书籍等。提到这一点的主要原因是只是为了强调需求收集的重要性。
本文的示例应用程序 - 会议室预订系统
除了教程,我们还将开发一个基于 Web 的应用程序,用于管理公司内部的会议室预订。为了保持简单和简洁,我们将出于时间和可用性的考虑,不追求过于花哨的功能。
我们应用程序的需求
- 可通过内部 intranet 网络访问的 Web 应用程序。
- 列出房间、房间类型和房间预订
- 允许添加/编辑/删除房间、房间类型和预订
- 存储每次预订与会者的姓名和电子邮件地址
- 向与会者发送提醒和取消通知
- 由于我们假设该应用程序仅在内部可用,因此任何人都可以访问或修改预订详细信息。
第二部分 - MyGeneration 框架
MyGeneration 框架是一个开源的、基于模板的代码生成软件。一旦您根据需求配置了模板,其余的应用程序开发就会变得非常容易,您可以节省宝贵的时间并消除人为错误。有关此框架的更多详细信息,请关注以下链接。在您能够轻松理解本文的其余部分之前,理解框架非常重要。
MyGeneration 下载链接
MyGeneration 框架快速参考指南
通用教程和文章
- http://www.mygenerationsoftware.com/portal/dOOdads/CUsage/tabid/53/Default.asp
- http://www.mygenerationsoftware.com/portal/Documentation/dOOdads/tabid/66/Default.aspx
- http://geekswithblogs.net/mnf/articles/106543.aspx
- http://www.mygenerationsoftware.com/TemplateLibrary/Article/?guid=257aa5be-5995-4fac-b70e-ff8391bca25
- http://dotnet.sys-con.com/node/48158
- http://www.mygenerationsoftware.com/TemplateLibrary/Article/?guid=d68f1213-0915-4f2b-98a3-58afde28770e
有用的 MySQL & .NET 文章
我希望您在阅读了上述链接和文章后对 MyGeneration 有了一个很好的了解。因此,在第三部分,我们将着手进行编程的核心部分,但在那之前,让我们将模板配置为满足我们的需求,以便我们可以生成所需的代码。
MyGeneration 模板配置
我已经打包了预配置的模板,它们可以与 MS SQL 2008 和 C#.NET 4.0 一起使用。应该也可以与更高版本的 .NET 框架一起使用,但我没有在任何其他 .NET 框架版本上进行测试。模板可在上面的链接中下载。您应该将这些解压缩的文件放在 *C:/Programe Files/Mygeneration13/* 目录下,这样在您使用 MyGeneration 客户端时就可以更轻松地浏览。每次尝试打开模板时,MyGeneration 客户端都会查看此目录。这些模板文件以 .vbgen 扩展名保存。
供您参考,我很久以前从 MyGeneration 下载了这些模板,并对其进行了修改,使其能够与 C#.NET 4.0 和 MS SQL 2008 一起使用。我添加了一些额外的代码生成方法,例如用于 LINQ 的泛型列表创建、列名列表等。随意根据您的需求修改它们,如果您发现任何更好的方法,请与大家分享。
在 MyGeneration 客户端中设置连接字符串
MyGeneration 客户端可以与多个连接一起使用,您可以保存任意多个连接。只需选择您需要使用的连接,然后单击右上角的保存按钮。这将是当前上下文中用于使用所需模板创建所有代码的数据库。
第三部分 - 应用程序开发
决定应用程序架构
在这一部分,我们将开发前面讨论的实际应用程序。当涉及到应用程序开发时,应用程序架构是一个关键步骤。架构应该对其他程序员也易于理解。在这种情况下,我们有应用程序的问题域和需求。实际上,我们在这方面开发了一个非常简单的应用程序,但我们将演示多层方法。当我说多层应用程序时,这意味着我们将分离数据、业务逻辑和表示层。在我们的例子中,我们将应用程序分成以下几层。
- 数据层或数据库
- 数据访问层
- 业务逻辑层
- 实用工具层 - (注意:此层未使用,但包含在源项目代码中)
- GUI 层(Windows 或 Web)
- 基础框架 - 您不必真正创建此项,因为它已包含在文章中。您可以将其作为项目引用或直接 DLL 引用添加到解决方案中的项目中。
数据层
如本文前面所述,我们将使用 MS SQL Server 2008 Express 版本作为我们的数据库引擎。(您也可以使用 MySQL,但那样的话,您将需要使用 MySQL 模板来生成代码,如本文后面所述)。为简单起见,我们将使数据模型非常简单。 正如您从下面的模型中看到的,我们将只有四个表来管理我们的预订。
- RoomType:此表将存储公司内的所有可能房间类型。
- Room:包含房间数据的表。
- Booking:此表将包含预订日期和时间等数据。
- RoomAttendee:存储每次预订的与会者详细信息。
创建存储过程
我们将使用 *MyGen_Template_SQL_StoredProcs.vbgen* 模板来创建存储过程。打开 MyGeneration 软件客户端,然后选择用于创建存储过程的已配置模板。
选择我们数据库“RoomData”下的所有表,如上图所示。一旦生成了 SQL 脚本,您就可以直接在 MS SQL IDE 中使用 Management Studio 运行它,或者如果您更喜欢在应用程序平台内进行数据库任务,则可以使用 Visual Studio。在大多数情况下,我倾向于直接将 SQL 脚本从 MyGeneration 输出复制到 MS SQL Management Studio 并按 F5 键。
注意:您永远不应手动修改或重命名这些存储过程,因为当您再次使用此模板时,它们将被重新创建,并且存储过程在数据访问层中按原样使用。如果您需要在数据库中自定义 SQL 脚本,您应该将它们与这些自动生成的脚本明确分开。
完成的数据模型应与下图类似...
一旦创建了存储过程,我们就完成了此应用程序的数据模型。下一步是开发应用程序本身。
Visual Studio 解决方案
为了将概念模型转化为实际形状,让我们开始在 Visual Studio 解决方案中创建以下项目。如本文架构部分前面所述,我们将把解决方案分成 5 个不同的层,每个层通常是一个项目,如下图所示。
MyGeneration.doodads_2005 - (C#.NET 类库) 随源代码提供的基础库。您通常不需要为此项目进行修改,除非您需要进一步扩展它。
RoomBooking.DAL - (C#.NET 类库) - 数据访问层 - 包含由代码生成自动创建的 抽象 类的项目。请注意,您**不得**直接修改这些类,因为代码生成将始终覆盖它们。您必须通过继承在业务逻辑层中使用这些类。
RoomBooking.BLL - (C#.NET 类库) - 业务逻辑层 - 包含继承自数据访问层的类的项目。这是您的主要工作区域项目。请注意,这些类**不会**被代码生成覆盖,因此您可以大胆地在这些类中编写自定义方法。
RoomBooking.Util - (C#.NET 库) - 包含实用程序或帮助类(如发送电子邮件的代码、读取 XML 文件等)的项目。将其作为独立项目使用的原因是,如果需要,它可以被解决方案中的任何其他项目使用。
RoomBookingUI - (ASP.NET C# Web 项目) - 这是我们的用户界面。我们将使用 Microsoft Visual Studio 提供的 ASP.NET Web 应用程序。业务逻辑应该发生在上面讨论的层中。界面应仅用于 Web 元素,并利用其他层完成的智能工作。请将您的全部注意力集中在使其成为一个美观且响应迅速的 Web 应用程序上。
数据访问层
如上文所述,此项目将包含抽象类。要创建这些类,请打开 MyGeneration 应用程序。通过浏览到模板目录打开模板,然后选择 *MyGen_Template_CSharp_SQL_dOOdads_AbstractClass*。单击运行(播放按钮)或按 F5。一旦您的连接字符串设置为正确的数据库,您将看到如下对话框。
输出文件路径:这必须是您的 DAL 项目,如前面所述。如果您愿意,也可以将这些类放在子文件夹下。
命名空间:类的命名空间。(通常是类库名称)。您可以更改它,但建议将其保留为默认项目名称,因为这是 Visual Studio 的默认行为。
选择您希望创建类的数据库和表。我建议保留“在文件名下划线前添加下划线”的勾选状态,因为这将清楚地将抽象类(数据访问层)与具体类(业务逻辑层)区分开。单击“确定”,您应该会在项目目录中看到创建的类。刷新解决方案或使用“添加现有项”到项目,然后手动将这些类添加到 DAL 项目。
您不应手动修改此项目中的任何类,因为当您再次使用上述模板时,这些类将被覆盖。
如果您想一窥代码,下面是为 RoomType 表创建的类。 正如您所见,该类已经准备就绪,开箱即用地提供了所有必需的方法和属性。
/*
'===============================================================================
' Generated From - MyGen_Template_CSharp_SQL_dOOdads_AbstractClass.vbgen
'
' Author: Gurdeep Singh
' Email: gurdeeptoor@yahoo.ie
'
' ** IMPORTANT **
' How to Generate your stored procedures:
'
' SQL = SQL_StoredProcs.vbgen
' ACCESS = Access_StoredProcs.vbgen
' ORACLE = Oracle_StoredProcs.vbgen
' FIREBIRD = FirebirdStoredProcs.vbgen
' POSTGRESQL = PostgreSQL_StoredProcs.vbgen
'
' The supporting base class SqlClientEntity is in the Architecture directory in "dOOdads".
'
' This object is 'abstract' which means you need to inherit from it to be able
' to instantiate it. This is very easilly done. You can override properties and
' methods in your derived class, this allows you to regenerate this class at any
' time and not worry about overwriting custom code.
'
' NEVER EDIT THIS FILE.
'
' public class YourObject : _YourObject
' {
'
' }
'
'===============================================================================
*/
// Generated by MyGeneration Version # (1.3.1.1)
using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Collections.Specialized;
using MyGeneration.dOOdads;
using System.Collections.Generic;
namespace RoomBooking.DAL
{
public abstract class _RoomType : SqlClientEntity
{
public _RoomType()
{
this.QuerySource = "RoomType";
this.MappingName = "RoomType";
}
public List<string> ColumnsNamesList()
{
List<string> ColList = new List<string>();
ColList.Add("RoomTypeID");
ColList.Add("RoomTypeName");
ColList.Add("RoomTypeDesc");
ColList.Add("Active");
return ColList;
}
//=================================================================
// public Overrides void AddNew()
//=================================================================
//
//=================================================================
public override void AddNew()
{
base.AddNew();
}
public override void FlushData()
{
this._whereClause = null;
this._aggregateClause = null;
base.FlushData();
}
//=================================================================
// public Function LoadAll() As Boolean
//=================================================================
// Loads all of the records in the database, and sets the currentRow to the first row
//=================================================================
public bool LoadAll()
{
ListDictionary parameters = null;
return base.LoadFromSql("[" + this.SchemaStoredProcedure +
"proc_RoomTypeLoadAll]", parameters);
}
//=================================================================
// public Overridable Function LoadByPrimaryKey() As Boolean
//=================================================================
// Loads a single row of via the primary key
//=================================================================
public virtual bool LoadByPrimaryKey(int RoomTypeID)
{
ListDictionary parameters = new ListDictionary();
parameters.Add(Parameters.RoomTypeID, RoomTypeID);
return base.LoadFromSql("[" + this.SchemaStoredProcedure +
"proc_RoomTypeLoadByPrimaryKey]", parameters);
}
#region Parameters
protected class Parameters
{
public static SqlParameter RoomTypeID
{
get
{
return new SqlParameter("@RoomTypeID", SqlDbType.Int, 0);
}
}
public static SqlParameter RoomTypeName
{
get
{
return new SqlParameter("@RoomTypeName", SqlDbType.NVarChar, 50);
}
}
public static SqlParameter RoomTypeDesc
{
get
{
return new SqlParameter("@RoomTypeDesc", SqlDbType.NVarChar, 100);
}
}
public static SqlParameter Active
{
get
{
return new SqlParameter("@Active", SqlDbType.Bit, 0);
}
}
}
#endregion
#region ColumnNames
public class ColumnNames
{
public const string RoomTypeID = "RoomTypeID";
public const string RoomTypeName = "RoomTypeName";
public const string RoomTypeDesc = "RoomTypeDesc";
public const string Active = "Active";
static public string ToPropertyName(string columnName)
{
if (ht == null)
{
ht = new Hashtable();
ht[RoomTypeID] = _RoomType.PropertyNames.RoomTypeID;
ht[RoomTypeName] = _RoomType.PropertyNames.RoomTypeName;
ht[RoomTypeDesc] = _RoomType.PropertyNames.RoomTypeDesc;
ht[Active] = _RoomType.PropertyNames.Active;
}
return (string)ht[columnName];
}
static private Hashtable ht = null;
}
#endregion
#region PropertyNames
public class PropertyNames
{
public const string RoomTypeID = "RoomTypeID";
public const string RoomTypeName = "RoomTypeName";
public const string RoomTypeDesc = "RoomTypeDesc";
public const string Active = "Active";
static public string ToColumnName(string propertyName)
{
if (ht == null)
{
ht = new Hashtable();
ht[RoomTypeID] = _RoomType.ColumnNames.RoomTypeID;
ht[RoomTypeName] = _RoomType.ColumnNames.RoomTypeName;
ht[RoomTypeDesc] = _RoomType.ColumnNames.RoomTypeDesc;
ht[Active] = _RoomType.ColumnNames.Active;
}
return (string)ht[propertyName];
}
static private Hashtable ht = null;
}
#endregion
#region StringPropertyNames
public class StringPropertyNames
{
public const string RoomTypeID = "s_RoomTypeID";
public const string RoomTypeName = "s_RoomTypeName";
public const string RoomTypeDesc = "s_RoomTypeDesc";
public const string Active = "s_Active";
}
#endregion
#region Properties
public virtual int RoomTypeID
{
get
{
return base.Getint(ColumnNames.RoomTypeID);
}
set
{
base.Setint(ColumnNames.RoomTypeID, value);
}
}
public virtual string RoomTypeName
{
get
{
return base.Getstring(ColumnNames.RoomTypeName);
}
set
{
base.Setstring(ColumnNames.RoomTypeName, value);
}
}
public virtual string RoomTypeDesc
{
get
{
return base.Getstring(ColumnNames.RoomTypeDesc);
}
set
{
base.Setstring(ColumnNames.RoomTypeDesc, value);
}
}
public virtual bool Active
{
get
{
return base.Getbool(ColumnNames.Active);
}
set
{
base.Setbool(ColumnNames.Active, value);
}
}
#endregion
#region String Properties
public virtual string s_RoomTypeID
{
get
{
return this.IsColumnNull(ColumnNames.RoomTypeID) ?
string.Empty : base.GetintAsString(ColumnNames.RoomTypeID);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.RoomTypeID);
else
this.RoomTypeID = base.SetintAsString(ColumnNames.RoomTypeID, value);
}
}
public virtual string s_RoomTypeName
{
get
{
return this.IsColumnNull(ColumnNames.RoomTypeName) ?
string.Empty : base.GetstringAsString(ColumnNames.RoomTypeName);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.RoomTypeName);
else
this.RoomTypeName = base.SetstringAsString(ColumnNames.RoomTypeName, value);
}
}
public virtual string s_RoomTypeDesc
{
get
{
return this.IsColumnNull(ColumnNames.RoomTypeDesc) ?
string.Empty : base.GetstringAsString(ColumnNames.RoomTypeDesc);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.RoomTypeDesc);
else
this.RoomTypeDesc = base.SetstringAsString(ColumnNames.RoomTypeDesc, value);
}
}
public virtual string s_Active
{
get
{
return this.IsColumnNull(ColumnNames.Active) ?
string.Empty : base.GetboolAsString(ColumnNames.Active);
}
set
{
if (string.Empty == value)
this.SetColumnNull(ColumnNames.Active);
else
this.Active = base.SetboolAsString(ColumnNames.Active, value);
}
}
#endregion
#region Where Clause
public class WhereClause
{
public WhereClause(BusinessEntity entity)
{
this._entity = entity;
}
public TearOffWhereParameter TearOff
{
get
{
if (_tearOff == null)
{
_tearOff = new TearOffWhereParameter(this);
}
return _tearOff;
}
}
#region WhereParameter TearOffs
public class TearOffWhereParameter
{
public TearOffWhereParameter(WhereClause clause)
{
this._clause = clause;
}
public WhereParameter RoomTypeID
{
get
{
WhereParameter where =
new WhereParameter(ColumnNames.RoomTypeID, Parameters.RoomTypeID);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
public WhereParameter RoomTypeName
{
get
{
WhereParameter where =
new WhereParameter(ColumnNames.RoomTypeName, Parameters.RoomTypeName);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
public WhereParameter RoomTypeDesc
{
get
{
WhereParameter where =
new WhereParameter(ColumnNames.RoomTypeDesc, Parameters.RoomTypeDesc);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
public WhereParameter Active
{
get
{
WhereParameter where = new WhereParameter(ColumnNames.Active, Parameters.Active);
this._clause._entity.Query.AddWhereParameter(where);
return where;
}
}
private WhereClause _clause;
}
#endregion
public WhereParameter RoomTypeID
{
get
{
if (_RoomTypeID_W == null)
{
_RoomTypeID_W = TearOff.RoomTypeID;
}
return _RoomTypeID_W;
}
}
public WhereParameter RoomTypeName
{
get
{
if (_RoomTypeName_W == null)
{
_RoomTypeName_W = TearOff.RoomTypeName;
}
return _RoomTypeName_W;
}
}
public WhereParameter RoomTypeDesc
{
get
{
if (_RoomTypeDesc_W == null)
{
_RoomTypeDesc_W = TearOff.RoomTypeDesc;
}
return _RoomTypeDesc_W;
}
}
public WhereParameter Active
{
get
{
if (_Active_W == null)
{
_Active_W = TearOff.Active;
}
return _Active_W;
}
}
private WhereParameter _RoomTypeID_W = null;
private WhereParameter _RoomTypeName_W = null;
private WhereParameter _RoomTypeDesc_W = null;
private WhereParameter _Active_W = null;
public void WhereClauseReset()
{
_RoomTypeID_W = null;
_RoomTypeName_W = null;
_RoomTypeDesc_W = null;
_Active_W = null;
this._entity.Query.FlushWhereParameters();
}
private BusinessEntity _entity;
private TearOffWhereParameter _tearOff;
}
public WhereClause Where
{
get
{
if (_whereClause == null)
{
_whereClause = new WhereClause(this);
}
return _whereClause;
}
}
private WhereClause _whereClause = null;
#endregion
#region Aggregate Clause
public class AggregateClause
{
public AggregateClause(BusinessEntity entity)
{
this._entity = entity;
}
public TearOffAggregateParameter TearOff
{
get
{
if (_tearOff == null)
{
_tearOff = new TearOffAggregateParameter(this);
}
return _tearOff;
}
}
#region AggregateParameter TearOffs
public class TearOffAggregateParameter
{
public TearOffAggregateParameter(AggregateClause clause)
{
this._clause = clause;
}
public AggregateParameter RoomTypeID
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.RoomTypeID, Parameters.RoomTypeID);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
public AggregateParameter RoomTypeName
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.RoomTypeName, Parameters.RoomTypeName);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
public AggregateParameter RoomTypeDesc
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.RoomTypeDesc, Parameters.RoomTypeDesc);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
public AggregateParameter Active
{
get
{
AggregateParameter aggregate =
new AggregateParameter(ColumnNames.Active, Parameters.Active);
this._clause._entity.Query.AddAggregateParameter(aggregate);
return aggregate;
}
}
private AggregateClause _clause;
}
#endregion
public AggregateParameter RoomTypeID
{
get
{
if (_RoomTypeID_W == null)
{
_RoomTypeID_W = TearOff.RoomTypeID;
}
return _RoomTypeID_W;
}
}
public AggregateParameter RoomTypeName
{
get
{
if (_RoomTypeName_W == null)
{
_RoomTypeName_W = TearOff.RoomTypeName;
}
return _RoomTypeName_W;
}
}
public AggregateParameter RoomTypeDesc
{
get
{
if (_RoomTypeDesc_W == null)
{
_RoomTypeDesc_W = TearOff.RoomTypeDesc;
}
return _RoomTypeDesc_W;
}
}
public AggregateParameter Active
{
get
{
if (_Active_W == null)
{
_Active_W = TearOff.Active;
}
return _Active_W;
}
}
private AggregateParameter _RoomTypeID_W = null;
private AggregateParameter _RoomTypeName_W = null;
private AggregateParameter _RoomTypeDesc_W = null;
private AggregateParameter _Active_W = null;
public void AggregateClauseReset()
{
_RoomTypeID_W = null;
_RoomTypeName_W = null;
_RoomTypeDesc_W = null;
_Active_W = null;
this._entity.Query.FlushAggregateParameters();
}
private BusinessEntity _entity;
private TearOffAggregateParameter _tearOff;
}
public AggregateClause Aggregate
{
get
{
if (_aggregateClause == null)
{
_aggregateClause = new AggregateClause(this);
}
return _aggregateClause;
}
}
private AggregateClause _aggregateClause = null;
#endregion
protected override IDbCommand GetInsertCommand()
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_RoomTypeInsert]";
CreateParameters(cmd);
SqlParameter p;
p = cmd.Parameters[Parameters.RoomTypeID.ParameterName];
p.Direction = ParameterDirection.Output;
return cmd;
}
protected override IDbCommand GetUpdateCommand()
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_RoomTypeUpdate]";
CreateParameters(cmd);
return cmd;
}
protected override IDbCommand GetDeleteCommand()
{
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[" + this.SchemaStoredProcedure + "proc_RoomTypeDelete]";
SqlParameter p;
p = cmd.Parameters.Add(Parameters.RoomTypeID);
p.SourceColumn = ColumnNames.RoomTypeID;
p.SourceVersion = DataRowVersion.Current;
return cmd;
}
private IDbCommand CreateParameters(SqlCommand cmd)
{
SqlParameter p;
p = cmd.Parameters.Add(Parameters.RoomTypeID);
p.SourceColumn = ColumnNames.RoomTypeID;
p.SourceVersion = DataRowVersion.Current;
p = cmd.Parameters.Add(Parameters.RoomTypeName);
p.SourceColumn = ColumnNames.RoomTypeName;
p.SourceVersion = DataRowVersion.Current;
p = cmd.Parameters.Add(Parameters.RoomTypeDesc);
p.SourceColumn = ColumnNames.RoomTypeDesc;
p.SourceVersion = DataRowVersion.Current;
p = cmd.Parameters.Add(Parameters.Active);
p.SourceColumn = ColumnNames.Active;
p.SourceVersion = DataRowVersion.Current;
return cmd;
}
}
}
下面是数据访问层的类图
业务逻辑层
现在您已经完成了数据层和数据访问层。下一步是业务逻辑层,您将在其中实际开始手动编写代码。但为了让您开始使用这些类,有一个模板可以为您创建一些开箱即用的方法。要创建这些类,请从模板目录中选择 *MyGen_Template_CSharp_SQL_dOOdads_ConcreteClass*,然后运行此模板。您将看到如下对话框。
输出文件路径:使用 BAL 项目目录来放置您的具体类。(注意:如果您决定将数据访问和业务逻辑层合并到单个项目中,这可能与 DAL 项目相同。)
业务命名空间:业务逻辑层中的类使用的命名空间。(通常是项目名称)。
数据访问命名空间:数据访问层类使用的数据访问命名空间。
选择表并单击“确定”。将这些创建的类导入您的业务逻辑层(RoomBooking.BLL)项目。如果任何类已存在,此模板**不会覆盖**旧类。您可以按需扩展这些类。下面是 Class Room 的示例,即数据库中的 Room 表。注意其从对应数据访问类的 _Room 继承。另请注意,我在此类中手动编写的自定义方法,用于获取此类 RoomType 对象。
//Generator Framework: MyGeneration Version: # (1.3.1.1)
//Author: Gurdeep Singh
//Email: gurdeeptoor@yahoo.ie
using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using MyGeneration.dOOdads;
using RoomBooking.DAL;
namespace RoomBooking.BLL
{
public class Room : _Room
{
//Search Type Enum
public enum SearchType
{
Equal,
StartsWith,
EndsWith,
Wild,
}
//Sort Direction Type Enum
public enum SortDirection
{
Asc,
Desc,
}
/// <summary>
/// Construct the class with passing Dynamic connection string.
/// </summary>
/// <param name="ConnectionString">Connection string
/// from web.config or app.config file</param>
public Room(string ConnectionString)
{
this.ConnectionString = ConnectionString;
}
//Load all with applying sort
public bool LoadAll(bool ApplySort)
{
bool _Loadall = true;
_Loadall = this.LoadAll();
if (_Loadall && ApplySort)
{
this.Sort = Room.ColumnNames.RoomName;
}
return _Loadall;
}
/// <summary>
/// Load by a Single Column Value
/// </summary>
/// <param name="ColumnName">Name of Column</param>
/// <param name="ColumnValue">Value to be Searched</param>
/// <param name="SearchType">Search Type Enum</param>
/// <returns></returns>
public bool LoadByColumnValue(string ColumnName, string ColumnValue, SearchType SearchTypeOption)
{
bool _Loadall = true;
_Loadall = this.LoadAll();
if (_Loadall)
{
switch (SearchTypeOption)
{
case SearchType.Equal:
this.Filter = ColumnName + "='" + ColumnValue + "'";
return _Loadall;
case SearchType.StartsWith:
this.Filter = ColumnName + " Like '%" + ColumnValue + "'";
return _Loadall;
case SearchType.EndsWith:
this.Filter = ColumnName + " Like '" + ColumnValue + "%'";
return _Loadall;
case SearchType.Wild:
this.Filter = ColumnName + " Like '%" + ColumnValue + "%'";
return _Loadall;
}
}
return _Loadall;
}
/// <summary>
/// ConvertToList : You must load the object before converting to list.
/// </summary>
/// <param name="InputObject">Input Object pre populated</param>
/// <returns>generic list of Room collection</returns>
public List<Room> ConvertToList(Room InputObject)
{
List<Room> List = new List<Room>();
List<DataRow> dlist = new List<DataRow>();
dlist = InputObject.DefaultView.ToTable().AsEnumerable().ToList<DataRow>();
foreach (DataRow row in dlist)
{
Room InsObject = new Room(this.ConnectionString);
InsObject.AddNew();
//This method below is in BusinessEntity class in MyGeneration framework
DataRowToObject(InsObject, row);
List.Add(InsObject);
}
return List;
}
/// <summary>
/// ConvertToList : You must load the object before converting to list.
/// </summary>
/// <param name="InputObject">Input Object pre populated</param>
/// <param name="sort">Column to Sort</param>
/// <param name="sortcolumn">Sort direction</param>
/// <param name="page">Page number to display</param>
/// <param name="numRows">Total number of records to be displayed on page</param>
/// <returns>Sorted generic collection of Continent limited to one page data</returns>
public List<Room> ConvertToList(Room InputObject,
string sortcolumn, SortDirection sortDir, int page, int numRows)
{
List<Room> List = new List<Room>();
//Sor this object first
if (sortcolumn != null)
InputObject.Sort = string.Format("{0} {1}", sortcolumn, sortDir.ToString());
List = ConvertToList(InputObject);
return List.AsQueryable().Skip((page - 1) * numRows).Take(numRows).ToList<Room>();
}
/// <summary>
/// Get RoomType for this Room
/// </summary>
/// <returns></returns>
public RoomType GetRoomType()
{
RoomType RoomType = new RoomType(this.ConnectionString);
RoomType.LoadByPrimaryKey(this.RoomTypeID);
return RoomType;
}
}
}
下面是业务逻辑层的类图
实用工具层
此项目是为了方便起见,并且为了保持简单,在本例中我们没有使用它。您可以将任何可重用的方法包含在此项目中,例如,我们本可以将发送电子邮件的代码放在这里。
UI 层 - ASP.NET Web 应用程序
此时,您更像是一名 Web 设计师,而不是应用程序开发人员。在一个更大的团队中,这部分应该与上述层并行完成。在正常情况下,设计概念会提前与最终用户达成一致。无论如何,假设只有您一个人在工作,除了需求之外,还没有为这部分做任何事情。
注意:您可以使用任何方法论或方法来设计 Web 应用程序,例如 MVC 等。本文将坚持使用 Microsoft ASP.NET Web 应用程序结合 C#.NET 的常规方法。
在 Web 应用程序开发方面,我是用户控件的忠实拥护者。由于这是一个非常简单的应用程序,我们只需要三个主要部分,即 Room、RoomType 和 Bookings(这里指的是对象)。因此,您可以假设 Room 和 Room 类型是常规的列表和编辑操作。因此,让我们开始列出和编辑 RoomTypes。
列出/编辑 RoomTypes: 要列出 RoomTypes,我们将使用 Web 窗体上的 DataGridView。加载时,此页面将显示系统中可用的所有房间类型。要通过代码实现这一点,我们将使用业务逻辑层的 List 对象,即 RoomTypes 的列表,并将其绑定到 GridView。请看下面的代码片段...
private void LoadRoomTypeList()
{
RoomType RoomType = new RoomType(AppGlobals.ConnectionStrings.cstrRoomData);
RoomType.LoadAll(true);
gvRoomType.DataSource = RoomType.DefaultView;
gvRoomType.DataBind();
}
现在,通过查看上面的代码,您可能会提出的明显问题是,“AppGlobals.ConnectionStrings.ctrRoomdata”是什么?它只是传递给业务对象的连接字符串。 业务逻辑层中创建的每个业务对象都将其作为初始化参数。 AppGlobals 是一个静态类,用于存储此类静态值以在应用程序中使用。您可以想象我们必须多少次使用这些连接字符串,并且将它们硬编码在各处可能不是一个好主意。
下面是在 Web 窗体上显示的房间类型列表。
要进行编辑,我们将使用用户控件,并通过 Ajax Popup extender 以弹出窗口的形式在页面上可用。用户控件本质上是一个表格形式。请注意,此用户控件会引发两个自定义事件:SaveClicked 和 CancelClicked。这样做是为了处理 Ajax Popup Extender,因为 Popup Extender 只能(至少很容易地)从创建它的页面进行控制。
从用户控件处理弹出扩展程序:每次用户单击“保存”或“取消”时,我们都需要隐藏弹出窗口并显示主页面。我们将在用户控件上创建两个自定义事件,并在主页面上处理它们。
public EventHandler SaveClicked;
public EventHandler CancelClicked;
protected void btnSave_Click(object sender, EventArgs e)
{
//validate the input...
Validate();
RoomType RoomType = new RoomType(AppGlobals.ConnectionStrings.cstrRoomData);
if (Page.IsValid)
{
if (lblRoomTypeID.Text == "0")
{
RoomType.AddNew();
RoomType.Active = true;
RoomType.RoomTypeName = txtRoomTypeName.Text;
RoomType.RoomTypeDesc = txtRoomTypeDesc.Text;
RoomType.RoomTypeID = 0;
RoomType.Save();
}
else
{
//load the copy from DB
RoomType.LoadByPrimaryKey(Convert.ToInt32(lblRoomTypeID.Text));
//Check if description was changed
if (RoomType.RoomTypeDesc != txtRoomTypeDesc.Text ||
RoomType.RoomTypeName != txtRoomTypeName.Text)
{
RoomType.RoomTypeName = txtRoomTypeName.Text;
RoomType.RoomTypeDesc = txtRoomTypeDesc.Text;
RoomType.Save();
}
}
//raise the event
SaveClicked(sender, e);
}
else
{
}
}
protected void btnCancel_Click(object sender, EventArgs e)
{
CancelClicked(sender, e);
}
在 Web 窗体上,我们将如下注册这些事件。
//Register user control event handlers
ucRoomTypeEdit1.SaveClicked += new EventHandler(ucRoomTypeEdit1_SaveClicked);
ucRoomTypeEdit1.CancelClicked += new EventHandler(ucRoomTypeEdit1_CancelClicked);
然后像这样处理它们来显示/隐藏 Web 窗体上的编辑弹出窗口。请注意,此方法用于整个应用程序。
protected void ucRoomTypeEdit1_SaveClicked(object sender, EventArgs e)
{
pnlRoomTypeEdit.Visible = false;
lblInfo.Visible = false;
MPE.Hide();
LoadRoomTypeList();
}
protected void ucRoomTypeEdit1_CancelClicked(object sender, EventArgs e)
{
pnlRoomTypeEdit.Visible = false;
lblInfo.Visible = false;
MPE.Hide();
}
列出/编辑房间:我们将节省时间,因为不再需要再次解释。这与 RoomType 完全相同,只是对象不同。
列出/编辑预订:我们应用程序 Web 层中最重要的部分。我们需要提供一种简单有效的方式来处理预订。如果您像上面的对象那样列出预订,那么显示预订并不是真正用户友好的方式。开发一些可以处理所有这些出色功能的自定义控件有点挑战。那么为什么不使用 Google 来寻找一些可以满足这些需求的东西呢?花了不长但很长几个小时后,我发现了以下内容,它非常适合我们的需求。
http://www.daypilot.org/calendar.html
本文档中,我使用了 Daypilot 日历控件的完整功能试用版。它提供了开箱即用的功能,可以处理屏幕编辑、删除和创建日历条目,并为最终用户提供精美的外观。您还可以根据自己的方式对控件进行样式设置。
数据绑定与 GridView 相同。我们将按房间分割预订显示,以便用户可以选择房间和日期范围来查找或创建相应的预订。下面是将预订数据绑定到日历控件的代码片段。
int RoomID = ddlRoom.SelectedValue.Length > 0 ? Convert.ToInt32(ddlRoom.SelectedValue) : 0;
int Days = Convert.ToInt32(rdWeekView.SelectedValue) * 5;
Booking Booking = new Booking(AppGlobals.ConnectionStrings.cstrRoomData);
DayPilotCalendar1.Days = Days;
Booking.LoadByColumnValue(Booking.ColumnNames.RoomID, RoomID.ToString(), RoomBooking.BLL.Booking.SearchType.Equal);
DayPilotCalendar1.DataSource = Booking.DefaultView;
DayPilotCalendar1.DataBind();
DayPilotCalendar1.UpdateWithMessage(LoadingMessage);
上面的代码是完成的预订表单。日历控件的服务器端事件用于处理预订的创建、删除和修改。下面是一些来自此表单的代码片段。
每当用户选择现有预订时,我们就需要弹出预订编辑表单。
protected void DayPilotCalendar1_EventSelect(object sender, DayPilotEventArgs e)
{
int SelectedBookingID = Convert.ToInt32(DayPilotCalendar1.SelectedEvents[0].Value);
//pop-up
MPE.Show();
pnlBookingEdit.Visible = true;
ucBookingEdit1.LoadData(SelectedBookingID);
}
除了通过“新建”按钮创建预订外,用户还可以通过拖动鼠标来创建预订,我们需要处理此事件以创建新预订。下面是处理 TimeRangeSelected 事件并将新预订表单显示在弹出窗口中的代码。请注意,我们阻止了过去的日期预订。
protected void DayPilotCalendar1_TimeRangeSelected(object sender, TimeRangeSelectedEventArgs e)
{
if (e.Start.Date > DateTime.Today.AddDays(-1))
{
//pop-up
MPE.Show();
pnlBookingEdit.Visible = true;
ucBookingEdit1.CreateNew(Convert.ToInt32(ddlRoom.SelectedValue), e.Start, e.End, false);
}
else
{
LoadBookings("New booking for Past date not possible");
}
}
处理与会者以及通知会议取消和重新安排:与会者与每次预订相关联。正如您所见,这些在预订编辑表单中处理。每次预订创建、移动或删除时,都会通过发送电子邮件通知与会者。请下载源代码以获取详细信息。下面是发送电子邮件的代码,该代码位于 Web 应用程序的 AppGlobals 静态类中。
public static void SendMeetingEmail(Room Room, Booking RoomBooking,
List<RoomAttendee> RoomAttendeeList, string MeetingAction)
{
int ToEmailCount;
int DocCount;
string _EmailRecepients = string.Empty;
foreach (RoomAttendee roomAttendee in RoomAttendeeList)
{
if (_EmailRecepients.Length > 0)
_EmailRecepients = string.Format("{0};{1}", _EmailRecepients, roomAttendee.Email);
else
_EmailRecepients = roomAttendee.Email;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("Hi, <br/><br/> Please Note - " +
"Meeting detailed below has been <b>{0}</b> by {1}",
MeetingAction, RoomBooking.BookedBy));
sb.AppendLine("<br/><br/>");
sb.AppendLine(string.Format("Description: {0}",
RoomBooking.BookingNotes.Remove(RoomBooking.BookingNotes.LastIndexOf("-"))));
//Note we are taking away the user initials
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("Location: {0}", Room.RoomName));
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("Start Time: {0}", RoomBooking.BookDateFrom));
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("End Time: {0}", RoomBooking.BookDateTo));
sb.AppendLine("<br/>");
sb.AppendLine("<br/>");
sb.AppendLine(string.Format("People in this meeting : {0}", RoomAttendeeList.Count));
sb.AppendLine("<table border='1'>");
foreach (RoomAttendee roomAttendee in RoomAttendeeList)
{
sb.AppendLine(string.Format("<tr><td>{0} {1} {2}" +
"</td></tr>", roomAttendee.Title,
roomAttendee.FirstName, roomAttendee.LastName));
}
sb.AppendLine("</table>");
sb.AppendLine("<br/>");
sb.AppendLine("<br/>");
sb.AppendLine("<i>System generated email, Please DO NOT Reply !!</i>");
sb.AppendLine("<br/><br/>");
sb.AppendLine("Best Regards,");
sb.AppendLine("<br/>");
sb.AppendLine("Admin");
sb.AppendLine("<br/>");
sb.AppendLine("Meeting Room Booking System");
if (_EmailRecepients.Trim().Length > 0)
{
SendEmail(_EmailRecepients,
string.Format("Meeting Room Booking System - Meeting {0}", MeetingAction),
sb.ToString(),
string.Empty,
out ToEmailCount,
out DocCount);
}
}
第四部分 - My2ndGeneration:未来的代码生成
MyGeneration 最近已转向 Web。我还没有详细研究它。看起来是代码生成的未来。更多详情请参见此链接。
一旦我熟悉了这个新平台,我将在这里发布另一篇文章,请保持联系...!!
结论
总而言之,这是开发面向对象应用程序的众多方法之一。就像任何新平台都需要时间来学习一样,MyGeneration 平台也不例外。它需要您花费一些时间来理解它。但可以肯定的是,它是实用的,并且我已经在许多项目中使用了它。它为我们节省了大量的开发时间,而模板的力量使其更加有趣。 如果您认为它对世界有所帮助,请随时提供任何反馈并为本文**投票**。
编码愉快...!!