例如,如果我正在创建3层应用程序(数据/业务/ UI),并且数据层正在抓取单个或多个记录.在发送到业务层之前,我是否将所有内容从数据层转换为通用列表/集合?发送数据表是否可以?将信息发送回数据层怎么样?
如果我使用对象/列表,那么这些是Data或Business层的成员吗?我可以使用相同的对象传递到图层吗?
这是一些伪代码:
对象用户使用电子邮件/密码
在UI层,用户输入电子邮件/密码.UI层进行验证,然后我假设创建一个新的对象用户传递给业务层,业务层进行进一步验证并将相同的对象传递给数据层以插入记录.它是否正确?
我是.NET的新手(来自8年以上的ASP VBScript背景)并试图以"正确"的方式加快速度.
Mar*_*ham 24
我正在更新这个答案,因为Developr留下的评论似乎表明他想要更多细节.
对您的问题的简短回答是,您需要使用类实例(对象)来调解UI与业务逻辑层之间的接口.BLL和DAL将如下所述进行通信.你不应该传递SqlDataTables或SqlDataReaders.
关于原因的简单原因:对象是类型安全的,提供Intellisense支持,允许您在业务层进行添加或更改,这些添加或更改不一定在数据库中找到,并且您可以自由地将应用程序与数据库取消链接这样即使数据库发生变化(当然也在限制范围内),您仍可以保持一致的BLL界面.这只是一个很好的编程实践.
总体情况是,对于UI中的任何页面,您将拥有一个或多个要显示和交互的"模型".对象是捕获模型当前状态的方法.在流程方面:UI将从业务逻辑层(BLL)请求模型(可以是单个对象或对象列表).然后BLL创建并返回此模型 - 通常使用数据访问层(DAL)中的工具.如果在UI中对模型进行了更改,则UI会将修改后的对象发送回BLL,并附带有关如何处理它们的说明(例如插入,更新,删除).
.NET非常适合这种分离关注,因为Generic容器类 - 特别是List <>类 - 非常适合这种工作.它们不仅允许您传递数据,而且可以通过ObjectDataSource类轻松地与网格,列表等复杂的UI控件集成.您可以使用ObjectDataSource实现开发UI所需的全部操作:使用参数,CRUD操作,排序等"填充"操作.
因为这非常重要,所以让我快速转移一下来演示如何定义ObjectDataSource:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetArticles"
OnObjectCreating="OnObjectCreating"
TypeName="MotivationBusinessModel.ContentPagesLogic">
<SelectParameters>
<asp:SessionParameter DefaultValue="News" Name="category"
SessionField="CurPageCategory" Type="String" />
</SelectParameters>
</asp:ObjectDataSource>
Run Code Online (Sandbox Code Playgroud)
这里,MotivationBusinessModel是BLL的命名空间,而ContentPagesLogic是实现内容页面逻辑的类.用于提取数据的方法是" GetArticles ",它采用名为CurPageCategory的参数.在这种特殊情况下,ObjectDataSource返回一个对象列表,然后由网格使用.请注意,我需要将会话状态信息传递给BLL类,因此,在后面的代码中,我有一个方法" OnObjectCreating ",它允许我创建对象并传入参数:
public void OnObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
e.ObjectInstance = new ContentPagesLogic(sessionObj);
}
Run Code Online (Sandbox Code Playgroud)
所以,这就是它的工作原理.但这引出了一个非常大的问题 - 模型/业务对象来自哪里?像Linq to SQL和Subsonic这样的ORM提供了代码生成器,可以让您为每个数据库表创建一个类.也就是说,这些工具表示应该在DAL中定义模型类,并将映射直接映射到数据库表.Linq to Entities允许您以与数据库布局完全不同的方式定义对象,但相应地更复杂(这就是为什么Linq to SQL和Linq to Entities之间存在区别).从本质上讲,它是一个BLL解决方案.Joel和我已经在这个帖子的各个地方说过,实际上,业务层通常是应该定义模型的地方(尽管我实际上混合使用BLL和DAL对象).
一旦决定这样做,如何实现从模型到数据库的映射?好吧,你在BLL中编写类来提取数据(使用你的DAL)并填充对象或对象列表.它是业务逻辑,因为映射通常伴随着额外的逻辑来充实模型(例如,定义派生字段的值).
Joel创建静态Factory类来实现模型到数据库的映射.这是一个很好的方法,因为它使用一个众所周知的模式,并将映射放在要返回的对象的构造中.您总是知道去哪里查看映射,整体方法简单明了.
我采取了不同的方法.在我的BLL中,我定义了Logic类和Model类.这些通常在匹配对中定义,其中两个类在同一文件中定义,并且其名称因其后缀而不同(例如,ClassModel和ClassLogic).Logic类知道如何使用Model类 - 执行Fill,Save("Upsert"),Delete等操作,并为Model Instance生成反馈.
特别是,为了执行Fill,我利用在我的主DAL类中找到的方法(如下所示),它允许我使用任何类和任何SQL查询,并找到使用查询返回的数据创建/填充类的实例的方法(作为单个实例或列表).也就是说,Logic类只是获取Model类定义,定义SQL Query并将它们发送到DAL.结果是单个对象或对象列表,然后我可以将其传递给UI.请注意,查询可能会返回一个表中的字段或连接在一起的多个表.在映射级别,我真的不在乎 - 我只想填充一些对象.
这是第一个功能.它将采用任意类并将其自动映射到从查询中提取的所有匹配字段.通过查找名称与类中的属性匹配的字段来执行匹配.如果有额外的类字段(例如,您将使用业务逻辑填充的字段)或额外的查询字段,则会忽略它们.
public List<T> ReturnList<T>() where T : new()
{
try
{
List<T> fdList = new List<T>();
myCommand.CommandText = QueryString;
SqlDataReader nwReader = myCommand.ExecuteReader();
Type objectType = typeof (T);
PropertyInfo[] typeFields = objectType.GetProperties();
if (nwReader != null)
{
while (nwReader.Read())
{
T obj = new T();
for (int i = 0; i < nwReader.FieldCount; i++)
{
foreach (PropertyInfo info in typeFields)
{
// Because the class may have fields that are *not* being filled, I don't use nwReader[info.Name] in this function.
if (info.Name == nwReader.GetName(i))
{
if (!nwReader[i].Equals(DBNull.Value))
info.SetValue(obj, nwReader[i], null);
break;
}
}
}
fdList.Add(obj);
}
nwReader.Close();
}
return fdList;
}
catch
{
conn.Close();
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
这在我的DAL上下文中使用,但是你在DAL类中唯一需要的是QueryString的持有者,一个带有打开Connection和任何参数的SqlCommand对象.关键是确保ExecuteReader在调用它时能够正常工作.我的BLL对此功能的典型用法如下所示:
return qry.Command("Select AttendDate, Count(*) as ClassAttendCount From ClassAttend")
.Where("ClassID", classID)
.ReturnList<AttendListDateModel>();
Run Code Online (Sandbox Code Playgroud)
您还可以实现对匿名类的支持,如下所示:
public List<T> ReturnList<T>(T sample)
{
try
{
List<T> fdList = new List<T>();
myCommand.CommandText = QueryString;
SqlDataReader nwReader = myCommand.ExecuteReader();
var properties = TypeDescriptor.GetProperties(sample);
if (nwReader != null)
{
while (nwReader.Read())
{
int objIdx = 0;
object[] objArray = new object[properties.Count];
for (int i = 0; i < nwReader.FieldCount; i++)
{
foreach (PropertyDescriptor info in properties) // FieldInfo info in typeFields)
{
if (info.Name == nwReader.GetName(i))
{
objArray[objIdx++] = nwReader[info.Name];
break;
}
}
}
fdList.Add((T)Activator.CreateInstance(sample.GetType(), objArray));
}
nwReader.Close();
}
return fdList;
}
catch
{
conn.Close();
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
对此的调用如下:
var qList = qry.Command("Select QueryDesc, UID, StaffID From Query")
.Where("SiteID", sessionObj.siteID)
.ReturnList(new { QueryDesc = "", UID = 0, StaffID=0 });
Run Code Online (Sandbox Code Playgroud)
现在,qList是动态定义的动态创建的类实例的通用列表.
假设您的BLL中有一个函数,它将下拉列表作为参数,并使用数据填充列表.以下是如何使用上面检索的结果填充下拉列表:
foreach (var queryObj in qList)
{
pullDownList.Add(new ListItem(queryObj.QueryDesc, queryObj.UID.ToString()));
}
Run Code Online (Sandbox Code Playgroud)
简而言之,我们可以动态定义匿名业务模型类,然后通过将一些(动态)SQL传递给DAL来填充它们.因此,BLL非常容易更新以响应UI中不断变化的需求.
最后一点:如果您担心定义和传递对象浪费内存,则不应该:如果您使用SqlDataReader来提取数据并将其放入构成列表的对象中,那么您只需要一个内存副本(列表),因为阅读器以只读,仅向前的方式迭代.当然,如果您在数据访问层使用DataAdapter和Table类(等),那么您将产生不必要的开销(这就是您不应该这样做的原因).
kem*_*002 13
一般来说,我认为发送对象而不是数据表会更好.对于对象,每个层都知道它接收的内容(哪些对象具有哪些属性等).您可以使用对象获得编译时安全性,您不会意外拼错属性名称等,并强制两层之间的固有合同.
Joshua还提出了一个很好的观点,通过使用自定义对象,您还可以将其他层与数据层分离.您始终可以从其他数据源填充自定义对象,而其他层将更加明智.使用SQL数据表,这可能不会那么容易.
乔尔也提出了一个很好的观点.让您的数据层了解您的业务对象并不是一个好主意,原因与使您的业务和UI层了解数据层的细节相同.
由于世界上有编程团队,所以有几乎"正确"的方法可以做到这一点.也就是说,我喜欢做的是为每个业务对象构建一个工厂,看起来像这样:
public static class SomeBusinessObjectFactory
{
public static SomeBusinessObject FromDataRow(IDataRecord row)
{
return new SomeBusinessObject() { Property1 = row["Property1"], Property2 = row["Property2"] ... };
}
}
Run Code Online (Sandbox Code Playgroud)
我还有一个通用的翻译方法,我用它来调用这些工厂:
public static IEnumerable<T> TranslateQuery(IEnumerable<IDatarecord> source, Func<IDatarecord, T> Factory)
{
foreach (IDatarecord item in source)
yield return Factory(item);
}
Run Code Online (Sandbox Code Playgroud)
根据您的团队喜欢的内容,项目的大小等,这些工厂对象和翻译器可以与业务层或数据层一起使用,甚至可以使用额外的"翻译"程序集/层.
然后我的数据层将具有如下所示的代码:
private SqlConnection GetConnection()
{
var conn = new SqlConnection( /* connection string loaded from config file */ );
conn.Open();
return conn;
}
private static IEnumerable<IDataRecord> ExecuteEnumerable(this SqlCommand command)
{
using (var rdr = command.ExecuteReader())
{
while (rdr.Read())
{
yield return rdr;
}
}
}
public IEnumerable<IDataRecord> SomeQuery(int SomeParameter)
{
string sql = " .... ";
using (var cn = GetConnection())
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("@Someparameter", SqlDbType.Int).Value = SomeParameter;
return cmd.ExecuteEnumerable();
}
}
Run Code Online (Sandbox Code Playgroud)
然后我可以像这样把它们放在一起:
SomeGridControl.DataSource = TranslateQuery(SomeQuery(5), SomeBusinessObjectFactory.FromDataRow);
Run Code Online (Sandbox Code Playgroud)