C# 表示、业务和数据层
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/800946/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Presentation, Business and Data Layer
提问by Jon H
I just started programming in C# and was reading about dividing your application / website into the three different layers was the best practice but I am having a hard time understanding exactly how. Im working on a pet project to lean more about C# but I dont want to start on any bad habits. Can you look at what I have and see if I am doing this right? Offer some hints suggestions as to how to break everything down to the different layers?
我刚开始用 C# 编程,正在阅读有关将您的应用程序/网站划分为三个不同层的最佳实践,但我很难确切地理解如何。我正在从事一个宠物项目,以更多地了解 C#,但我不想养成任何坏习惯。你能看看我有什么,看看我做对了吗?提供一些关于如何将所有内容分解为不同层的提示建议?
Presentation Layer
表示层
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Project: Ruth</title>
<link href="CSS/StyleSheet.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div class="Body">
<div class="Header">
<div class="Nav">
<img src="images/Header_Main.gif" alt="" width="217" height="101" />
<div class="Menu">
<a href="Default.aspx">
<img src="images/Header_Home-Off.gif" alt="" /></a>
<a href="Default.aspx">
<img src="images/Header_About-Off.gif" alt="" /></a>
<a href="Register.aspx">
<img src="images/Header_Register-Off.gif" alt="" /></a>
<a href="Default.aspx">
<img src="images/Header_Credits-Off.gif" alt="" /></a>
</div>
</div>
</div>
<div class="Content">
<div class="CurrentlyListening">
<asp:Label ID="lblCurrentListen" runat="server" Text="(Nothing Now)" CssClass="Txt"></asp:Label>
</div>
<asp:GridView ID="gvLibrary" runat="server" AutoGenerateColumns="False" DataKeyNames="lib_id" DataSourceID="sdsLibrary" EmptyDataText="There are no data records to display." Width="760" GridLines="None">
<RowStyle CssClass="RowStyle" />
<AlternatingRowStyle CssClass="AltRowStyle" />
<HeaderStyle CssClass="HeaderStyle" />
<Columns>
<asp:BoundField DataField="artist_name" HeaderText="Artist" SortExpression="artist_name" HeaderStyle-Width="200" />
<asp:BoundField DataField="album_title" HeaderText="Album" SortExpression="album_title" HeaderStyle-Width="200" />
<asp:BoundField DataField="song_title" HeaderText="Track" SortExpression="song_title" HeaderStyle-Width="200" />
<asp:TemplateField HeaderText="DL">
<ItemTemplate>
<a href="http://####/Proj_Ruth/Data/<%# Eval("file_path") %>" class="lnk">Link</a>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="sdsLibrary" runat="server" ConnectionString="<%$ ConnectionStrings:MusicLibraryConnectionString %>" DeleteCommand="DELETE FROM [Library] WHERE [lib_id] = @lib_id" InsertCommand="INSERT INTO [Library] ([artist_name], [album_title], [song_title], [file_path]) VALUES (@artist_name, @album_title, @song_title, @file_path)" ProviderName="<%$ ConnectionStrings:MusicLibraryConnectionString.ProviderName %>" SelectCommand="SELECT [lib_id], [artist_name], [album_title], [song_title], [file_path] FROM [Library] ORDER BY [artist_name], [album_title]" UpdateCommand="UPDATE [Library] SET [artist_name] = @artist_name, [album_title] = @album_title, [song_title] = @song_title, [file_path] = @file_path WHERE [lib_id] = @lib_id">
<DeleteParameters>
<asp:Parameter Name="lib_id" Type="Int32" />
</DeleteParameters>
<InsertParameters>
<asp:Parameter Name="artist_name" Type="String" />
<asp:Parameter Name="album_title" Type="String" />
<asp:Parameter Name="song_title" Type="String" />
<asp:Parameter Name="file_path" Type="String" />
</InsertParameters>
<UpdateParameters>
<asp:Parameter Name="artist_name" Type="String" />
<asp:Parameter Name="album_title" Type="String" />
<asp:Parameter Name="song_title" Type="String" />
<asp:Parameter Name="file_path" Type="String" />
<asp:Parameter Name="lib_id" Type="Int32" />
</UpdateParameters>
</asp:SqlDataSource>
</div>
</div>
</form>
</body>
</html>
Business Layer
业务层
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
public class User
{
DA da = new DA();
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string AccessCode { get; set; }
public User(string firstName, string lastName, string emailAddress, string password, string accessCode)
{
FirstName = firstName;
LastName = lastName;
EmailAddress = emailAddress;
Password = password;
AccessCode = accessCode;
}
public void CreateUser(User newUser)
{
if (da.IsValidAccessCode(newUser.AccessCode))
{
da.CreateUser(newUser);
}
}
}
Data Access Layer (DAL)
数据访问层 (DAL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using System.Configuration;
public class DA
{
public DA()
{
}
public bool IsValidAccessCode(string accessCode)
{
bool isValid = false;
int count = 0;
using (SqlConnection sqlCnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MusicLibraryConnectionString"].ConnectionString))
{
sqlCnn.Open();
using (SqlCommand sqlCmd = new SqlCommand(String.Format("SELECT COUNT(*) FROM [AccessCodes] WHERE [accessCode_accessCode] = '{0}';", accessCode), sqlCnn))
{
count = (int)sqlCmd.ExecuteScalar();
if (count == 1)
{
isValid = true;
}
}
}
return isValid;
}
public void CreateUser(User newUser)
{
using (SqlConnection sqlCnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MusicLibraryConnectionString"].ConnectionString))
{
sqlCnn.Open();
using (SqlCommand sqlCmd = new SqlCommand(String.Format("INSERT INTO [Users] (user_firstName, user_lastName, user_emailAddress, user_password, user_accessCode) VALUES ('{0}', '{1}', '{2}', '{3}', '{4}');", newUser.FirstName, newUser.LastName, newUser.EmailAddress, newUser.Password, newUser.AccessCode), sqlCnn))
{
sqlCmd.ExecuteNonQuery();
}
}
DeleteAccessCode(newUser.AccessCode);
}
public void DeleteAccessCode(string accessCode)
{
using (SqlConnection sqlCnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MusicLibraryConnectionString"].ConnectionString))
{
sqlCnn.Open();
using (SqlCommand sqlCmd = new SqlCommand(String.Format("DELETE FROM [AccessCodes] WHERE [accessCode_accessCode] = '{0}';", accessCode), sqlCnn))
{
sqlCmd.ExecuteNonQuery();
}
}
}
}
采纳答案by Cerebrus
Jon,
乔恩,
One of the first things to understand is that if you intend to build layer-based applications, then you should not be storing SQL statements directly within ASPX pages (as the SqlDataSource
requires). The SqlDataSource
control was built to demonstrate how easy it is to bind and update an application with database data and is not intended to be used in real world applications, because it kinda defeats the purpose of having a BL layer and Datalayer if you are going to store Select/Update/Delete/Insert statements in the ASPX page.
首先要理解的一件事是,如果您打算构建基于层的应用程序,那么您不应该直接在 ASPX 页面中存储 SQL 语句(根据SqlDataSource
需要)。该SqlDataSource
控件旨在演示使用数据库数据绑定和更新应用程序是多么容易,并不打算在现实世界的应用程序中使用,因为如果您要存储数据,它有点违背了拥有 BL 层和 Datalayer 的目的在 ASPX 页面中选择/更新/删除/插入语句。
The whole purpose of layer-based application design is to encapsulate each layer so that there is no intersection. Each layer interacts with the public interface of the other layers and knows nothing about their internal implementation.
基于层的应用程序设计的全部目的是将每一层封装起来,这样就不会有交集。每一层都与其他层的公共接口交互,并且对它们的内部实现一无所知。
The viable alternative, therefore, is to use the ObjectDataSource
control. This control allows you to bind directly to a DataLayer or to a Biz logic layer which in turn can call the Datalayer. Binding to a Datalayer directly has the drawback that you will be returning data structures which expose the schema of the database tables (for e.g., DataTables or DataViews).
因此,可行的替代方法是使用ObjectDataSource
控件。此控件允许您直接绑定到 DataLayer 或 Biz 逻辑层,后者又可以调用 Datalayer。直接绑定到 Datalayer 的缺点是您将返回暴露数据库表模式的数据结构(例如,DataTables 或 DataViews)。
So, the recommended flow of logic is as follows:
所以,推荐的逻辑流程如下:
The ASPX page uses a DataSource control to bind to a BL class.
This BL class provides appropriate functions such as GetData, UpdateData, DeleteData and InsertData
(with any required overloads) and these functions return strongly typed objects or collections that the ObjectDataSource
can work with and display.
Each public function in the BL class internally calls into the DataLayer to select/update/delete/insert data to/from the database.
ASPX 页使用 DataSource 控件绑定到 BL 类。此 BL 类提供适当的函数,例如GetData, UpdateData, DeleteData and InsertData
(具有任何所需的重载),并且这些函数返回ObjectDataSource
可以使用和显示的强类型对象或集合。BL 类中的每个公共函数在内部调用 DataLayer 以从数据库中选择/更新/删除/插入数据。
An excellent introduction to this layer based design in ASP.NET is provided in the Quickstarts
快速入门中提供了对 ASP.NET 中基于层的设计的出色介绍
P.S: @Andy mentioned generic datalayers that work with all scenarios. See this questionfor an example of what it would look like.
PS:@Andy 提到了适用于所有场景的通用数据层。有关它的外观示例,请参阅此问题。
回答by Andy Moore
If you write your code to be ultimately portable, you will find you will have 3 (or more!) layers in your application.
如果您编写的代码最终是可移植的,您会发现您的应用程序中有 3 个(或更多!)层。
For example - instead of making your Data Access Layer work specifically for this one application, write it so that you will never have to write it again. Make sure all your functions can be passed variables and you don't rely on global variables (or as little as possible). When it comes time for your next project - copy and paste your DAL and suddenly you're up and running again.
例如——不要让你的数据访问层专门为这个应用程序工作,而是编写它,这样你就不必再次编写它。确保您的所有函数都可以传递变量,并且您不依赖全局变量(或尽可能少地依赖)。当需要进行下一个项目时 - 复制并粘贴您的 DAL,您会突然重新启动并运行。
And it doesn't end there - you might want to write a sub-layer for your DAL that interprets between MySQL and MSSQL (just as an example). Or you might have a library of common functions that you perform, like text sanitation or CSS generation or something.
而且它并没有就此结束 - 您可能想要为您的 DAL 编写一个在 MySQL 和 MSSQL 之间进行解释的子层(仅作为示例)。或者你可能有一个你执行的常用函数库,比如文本清理或 CSS 生成或其他东西。
If you write your code so that one day, you sit down to write an app - and it mostly involves cutting and pasting previous code - you've reached programmer nirvana. :)
如果你编写代码,以至于有一天,你坐下来编写一个应用程序——它主要涉及剪切和粘贴以前的代码——你已经达到了程序员的必杀技。:)
回答by Ronald Wildenberg
The whole idea behind layering an application is that each layer does not depend on implementation details of the layer(s) below. For example, in your code you have a T-SQL statement inside your presentation layer. This means you have a direct dependency of your presentation layer on your database (the bottom layer). If you make a change in your database, you must also make a change in your presentation layer. Ideally this is not what you want. The presentation layer should only be concerned about presenting data, not about how to retrieve it. Suppose you move your whole database into CSV files (I know, crazy idea), then your presentation layer should not be aware of this at all.
分层应用程序背后的整个想法是每一层不依赖于下面层的实现细节。例如,在您的代码中,您的表示层中有一个 T-SQL 语句。这意味着您的表示层直接依赖于您的数据库(底层)。如果您对数据库进行了更改,则还必须在表示层中进行更改。理想情况下,这不是您想要的。表示层应该只关心呈现数据,而不是如何检索它。假设您将整个数据库移动到 CSV 文件中(我知道,疯狂的想法),那么您的表示层根本不应该意识到这一点。
So ideally, you have a business layer method that returns just the data you want to show to the user. You should take a look at ObjectDataSource
instead of SqlDataSource
. SqlDataSource
is nice for small prototyping projects, but you should not use it for any more serious projects.
因此,理想情况下,您有一个业务层方法,它只返回您想要向用户显示的数据。你应该看看ObjectDataSource
而不是SqlDataSource
。SqlDataSource
非常适合小型原型项目,但您不应该将它用于任何更严肃的项目。
Between business layer and data layer you should have a similar separation. The data layer is responsible for getting the data you want from some storage location (database, CSV file, web service, ...). Again, ideally, the business layer should not depend on the implementation details of the data layer. If you're talking to SQL Server for example, you should not return a SqlDataReader
instance to your business layer. By doing this you create a dependency of your business layer on an implementation detail of your data layer: the actual database it is retrieving it's data from.
业务层和数据层之间应该有类似的分离。数据层负责从某个存储位置(数据库、CSV 文件、Web 服务等)获取您想要的数据。同样,理想情况下,业务层不应依赖于数据层的实现细节。例如,如果您正在与 SQL Server 交谈,则不应将SqlDataReader
实例返回到您的业务层。通过这样做,您可以创建业务层对数据层实现细节的依赖:它从中检索数据的实际数据库。
In practice you see that the business layer does depend on implementation details of the data layer in some way or another and usually that's not a bad thing. When was the last time you decided to switch databases? But eliminating dependencies and isolating implementation details as much as possible almost always results in an application that's easier to maintain and understand.
在实践中,您会看到业务层确实以某种方式依赖于数据层的实现细节,通常这不是一件坏事。您最后一次决定切换数据库是什么时候?但是尽可能多地消除依赖关系和隔离实现细节几乎总是会导致应用程序更易于维护和理解。
You can find a similar explanation here.
您可以在此处找到类似的解释。
回答by Ian Roke
The greatest explanation of logic layers in ASP.NET applications come from two sources. The first is Microsoft's own ASP.NET website written by Scott Mitchell it provides a good introduction to the separation of logic. The tutorials are quite wordy but I found them very useful. The URL is http://www.asp.net/learn/data-access/.
对 ASP.NET 应用程序中逻辑层的最大解释来自两个来源。第一个是由 Scott Mitchell 编写的 Microsoft 自己的 ASP.NET 网站,它很好地介绍了逻辑分离。这些教程非常冗长,但我发现它们非常有用。URL 是http://www.asp.net/learn/data-access/。
The second resource I found very useful followed on from that and it was written by Imar Spaanjaars and is available here. It is a much more technical article but provides a great way of adding the structure to your application.
之后我发现第二个资源非常有用,它是由 Imar Spaanjaars 编写的,可在此处获得。这是一篇更具技术性的文章,但提供了一种将结构添加到应用程序的好方法。
I hope that helps.
我希望这有帮助。
Ian.
伊恩。
回答by Mike Kingscott
as an aside to the main thrust of his question, I would recommend you looking at ASPNET_REGSQL to configure your SQL database to handle .Net's built-in membership / profile / role abilities. It would remove a lot of shag and hassle for creating / updating users, etc. I've not used profile an awful lot, but it allows you to "tack on" extra attributes to your user, e.g. AccessCode.
作为他问题的主旨的旁白,我建议您查看 ASPNET_REGSQL 来配置您的 SQL 数据库以处理 .Net 的内置成员资格/配置文件/角色能力。它会消除创建/更新用户等的大量麻烦和麻烦。我没有经常使用配置文件,但它允许您为用户“添加”额外的属性,例如 AccessCode。
If you're dealing with an existing DB structure which already does user authentication etc., you could create a custom Membership provider which would leverage the existing db tables and stored procedures.
如果您正在处理已经进行用户身份验证等的现有数据库结构,您可以创建一个自定义成员资格提供程序,该提供程序将利用现有的数据库表和存储过程。