如果我使用对象/列表,这些是数据层还是业务层的成员?我能够使用同一对象来在层之间传递吗?
以下是一些伪代码:
对象用户具有电子邮件/密码
在UI层中,用户输入电子邮件/密码。 UI层进行验证,然后我假设创建一个新的用户对象传递给业务层进行进一步验证,并将相同的对象传递给数据层以插入记录。 这样正确吗?
我是.NET的新手(来自8年ASP VBScript背景),正在努力了解“正确”处理事物的方法。
我更新了这个答案,因为Developr的评论似乎表明他希望有更多的细节。
简短回答你的问题是是的,你需要使用类实例(对象)来调解UI和业务逻辑层之间的接口。BLL和DAL将如下所述进行通信。您不应该在它们之间传递SqlDataTables或SqlDataReaders。
简单的原因是:对象是类型安全的,提供Intellisense支持,允许您在业务层中进行添加或修改,而这些在数据库中并不一定存在,并且让您有一些自由来断开应用程序与数据库之间的联系,以便您可以在数据库变化时保持一致的BLL接口(当然,在某种程度上)。这只是好的编程实践。
大局是,对于您UI中的任何页面,您都将拥有一个或多个要显示和交互的“模型”。对象是捕获模型当前状态的方法。在过程方面:UI将从业务逻辑层(BLL)请求模型(可能是单个对象或对象列表)。然后,BLL使用数据访问层(DAL)的工具创建并返回此模型。如果在UI中对模型进行更改,则UI将向BLL发送修订后的对象,并附带指示如何处理它们的说明(例如,插入、更新、删除)。
.NET非常适合这种关注点分离,因为通用容器类 - 特别是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>
public void OnObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
e.ObjectInstance = new ContentPagesLogic(sessionObj);
}
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;
}
}
这是在我的数据访问层(DAL)中使用的,但你只需要在DAL类中拥有一个QueryString的变量、一个带有打开连接和任意参数的SqlCommand对象即可。关键是确保当调用时ExecuteReader函数能够正常工作。因此,我的业务逻辑层(BLL)通常会这样使用该函数:
return qry.Command("Select AttendDate, Count(*) as ClassAttendCount From ClassAttend")
.Where("ClassID", classID)
.ReturnList<AttendListDateModel>();
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;
}
}
var qList = qry.Command("Select QueryDesc, UID, StaffID From Query")
.Where("SiteID", sessionObj.siteID)
.ReturnList(new { QueryDesc = "", UID = 0, StaffID=0 });
foreach (var queryObj in qList)
{
pullDownList.Add(new ListItem(queryObj.QueryDesc, queryObj.UID.ToString()));
}
总的来说,我认为发送对象比数据表更好。 通过对象,每个层都知道它正在接收什么(哪些具有什么属性的对象等)。 对象提供编译时安全性,您不会意外拼写属性名称等,并且它强制两个层之间存在内在契约。
Joshua也提出了一个很好的观点,通过使用自定义对象,还可以将其他层与数据层解耦。 您始终可以从另一个数据源填充自定义对象,而其他层则不会注意到。 对于SQL数据表,这可能不太容易。
Joel也提出了一个很好的观点。 让数据层了解业务对象与让业务层和UI层了解数据层的具体情况一样不明智。
在世界上的编程团队中,实现这个功能几乎有无数种“正确”的方法。尽管如此,我喜欢为我的每个业务对象构建一个工厂,它看起来像这样:
public static class SomeBusinessObjectFactory
{
public static SomeBusinessObject FromDataRow(IDataRecord row)
{
return new SomeBusinessObject() { Property1 = row["Property1"], Property2 = row["Property2"] ... };
}
}
我还有一种通用的翻译方法,我使用它来调用这些工厂:
public static IEnumerable<T> TranslateQuery(IEnumerable<IDatarecord> source, Func<IDatarecord, T> Factory)
{
foreach (IDatarecord item in source)
yield return Factory(item);
}
根据您的团队喜好、项目大小等因素,这些工厂对象和翻译器可以与业务层或数据层共存,甚至可以是额外的“翻译”程序集/层。
然后我的数据层将具有以下代码:
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();
}
}
然后我可以像这样把它全部组合起来:
SomeGridControl.DataSource = TranslateQuery(SomeQuery(5), SomeBusinessObjectFactory.FromDataRow);
这里有很多非常好的答案,我只想补充一点,在您花费大量时间创建翻译层和工厂之前,了解应用程序的目的和未来是很重要的。
无论是在配置映射文件、工厂还是直接在数据/业务/UI层中,某个对象/文件/类等都必须了解每个层之间发生的事情。如果交换层是现实的,则创建翻译层很有用。其他时候,让某些层(通常是在业务层)了解所有接口(或至少足够来处理数据和UI之间的关系)是有意义的。
再次强调,这并不是说所有这些东西都是坏的,只是可能存在YAGNI的情况。有些DI和ORM框架使这种东西变得非常简单,所以不做这些操作就太傻了。如果您正在使用其中一个框架,则可能最好充分利用它。