使用 Linq 和 WPF 进行 N 层开发






2.55/5 (8投票s)
使用 Linq 和 WPF 进行 N 层开发
引言
在本文中,我将通过一个事务性数据库应用程序,使用 WPF 作为 UI,Linq 作为 DAL,来展示一小部分功能。我想遵循数据从数据访问层 DAL 到业务逻辑层,再到 WPF UI 的路径。
N 层架构
我喜欢将功能封装到应用程序的各个层中。这样,我可以将应用程序模块彼此隔离,从而最大限度地减少更改的影响。它还有助于以一种易于维护和进一步开发的方式构建应用程序,从而能够在各种抽象级别上进行测试驱动的开发。结果是一个可扩展的架构,可以通过简单地修改适当层中的适当子项目来适应新技术。它也使团队开发更容易,有助于避免冲突更新。我在子项目前面加上了
- DAL = 数据访问层
- DOM = 领域层
- UI = 用户界面
- UTILS = 可在所有层中访问的实用工具
Linq 数据访问层
我避免使用第三方 DAL 的主要原因是,除了购买下一个版本之外,没有自动升级路径,而且开发者也没有熟悉 Microsoft 提供的内置函数,而是学习了可能可用也可能不可用的第三方接口。尽管我旧的 DAL 在 .Net 1.0 到 3.5 中都能工作,而 Linq 只在 3.5 及更高版本中可用,但我认为我将来不会编写任何 .Net 2.0 代码,因此我可以享受这项新技术带来的简化……
正如我在之前的 VB DAL https://codeproject.org.cn/KB/database/NigelDBTable.aspx 中所做的那样,我在一个可以包含在整个解决方案中的程序集中定义了值对象。这些对象与数据库表中的内容是一对一对应的。将它们放在一个单独的程序集中,就可以通过业务层将 DAL 与 UI 分开。这里有两个例子,对应数据库中的表
USR_USERInfo.cs =============== using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace UTILS.TestDB { public class USR_USERInfo { public class TableStructure { public int USER_ID; public String USER_NAME; public String EMAIL; } } } USR_USER_GROUPInfo ================== using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace UTILS.TestDB { public class USR_USER_GROUPInfo { public class TableStructure { public int USER_GROUP_ID; public String USER_GROUP; } public enum USER_GROUPEnum { Super = 1, Normal = 2 } } }
注意:此方法不兼容以下方式定义的 VB 值对象。原因是,将 Linq 数据对象转换为此数据对象的函数能够分配所有字段,但在返回时,某些字段未返回。奇怪的是,有些字段有效,而有些则无效。VB 结构与公共类不同,因此对这些对象的内存管理进行了一些奇怪的处理,导致 ConvertLinqToTableStructure 在将值作为对象返回时遇到问题。
Imports System.Reflection Public Class USR_USERInfo Public Data As TableStructure Public ReadOnlyFields As ReadOnlyFieldsStructure #Region "Developer defines the table in this seaction" REM Definition of fields in the database REM NB We need to use a structure otherwize this does not appear in intellisence when REM programming function parameter data types Public Structure TableStructure Dim USER_ID As Integer Dim DOMAIN_USER_NAME As String Dim DISPLAY_NAME As String Dim MAX_SIM_SERVER_USAGE As Integer Dim EMAIL As String End Structure Public IndexField As String = "USER_ID" ' Primary index Public Structure ReadOnlyFieldsStructure Dim USER_ID As Integer End Structure #End Region End Class
我创建了一个包含所有 Linq 代码的项目。我创建了一些 Linq to SQL 图,并将数据库中的所有表拖放进来。为此,请向 DAL 子项目添加一个新的 Linq to SQL 类,然后从服务器导航器浏览到要导入的表,并将它们拖放到新创建的 Linq to SQL 类上。
我发现将表分组在一起是个好主意,否则屏幕会显得有点拥挤。我还发现不可能在同一项目内的两个图表中拖放同一个表。当数据库发生更改时,需要删除受影响的相应表,刷新服务器资源管理器,然后将表重新拖放到相应的 Linq to SQL 图中。我使用反射来自动化数据从整个解决方案中可访问的值对象到仅在 DAL 中可访问的 Linq 表对象的转换。这在 DBTable 中完成。由于并非所有内容都可以轻松地用 Linq 实现,因此我在其中包含了一些老式的 OLEDB 内容……
DBTable.cs ========== using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.OleDb; using System.Reflection; namespace DAL.TestDB { public class DBTable { private String _DatabaseServerName; private String _DatabaseName; private String ConnectionStr; public String LinqConnectionStr; private String DefaultTableName = "DAL"; public DBTable(string DatabaseServerName, string DatabaseName) { _DatabaseServerName = DatabaseServerName; _DatabaseName = DatabaseName; // String ComputerName = "ThisPC"; (My.Computer.Name String ComputerName = System.Net.Dns.GetHostName(); DefaultTableName = this.GetType().Name; ConnectionStr = "Provider=sqloledb;workstation id='" + ComputerName + "';packet size=4096;integrated security=SSPI;data source='" + DatabaseServerName + "';persist security info=False;initial catalog=" + DatabaseName + ""; LinqConnectionStr = "Data Source=" + DatabaseServerName + ";Initial Catalog=" + DatabaseName + ";Integrated Security=True"; } #region Conversion of objects public bool ConvertInfoToInfo(object Input, object Output, ref String Message) { try { FieldInfo[] InputFields; InputFields = Input.GetType().GetFields(); FieldInfo[] OutputFields; OutputFields = Output.GetType().GetFields(); for (int i = 0; i <= InputFields.Length - 1; i++) { for (int j = 0; j <= OutputFields.Length - 1; j++) { if ((String)InputFields[i].Name == (String)OutputFields[j].Name) { OutputFields[j].SetValue(Output, InputFields[i].GetValue(Input)); } } } return true; } catch (Exception ex) { Message = ex.ToString(); return false; } } public bool ConvertTableStructureToLinq(object Input, object Output, ref String Message) { try { FieldInfo[] InputFields; InputFields = Input.GetType().GetFields(); PropertyInfo[] OutputProperties; OutputProperties = Output.GetType().GetProperties(); for (int i = 0; i <= InputFields.Length - 1; i++) { for (int j = 0; j <= OutputProperties.Length - 1; j++) { if ((String)InputFields[i].Name == (String)OutputProperties[j].Name) { OutputProperties[j].SetValue(Output, InputFields[i].GetValue(Input), null); } } } return true; } catch (Exception ex) { Message = ex.ToString(); return false; } } public bool ConvertLinqToTableStructure(object Input, object Output, ref String Message) { try { PropertyInfo[] InputProperties; InputProperties = Input.GetType().GetProperties(); FieldInfo[] OutputFields; OutputFields = Output.GetType().GetFields(); for (int i = 0; i <= InputProperties.Length - 1; i++) { for (int j = 0; j <= OutputFields.Length - 1; j++) { if ((String)InputProperties[i].Name == (String)OutputFields[j].Name) { OutputFields[j].SetValue(Output, InputProperties[i].GetValue(Input,null)); } } } return true; } catch (Exception ex) { Message = ex.ToString(); return false; } } public bool CompareTableStructureToLinq(object TableStructure, object LinqTable, ref String Message) { try { FieldInfo[] TableStructureFields; TableStructureFields = TableStructure.GetType().GetFields(); PropertyInfo[] LinqTableProperties; LinqTableProperties = LinqTable.GetType().GetProperties(); for (int i = 0; i <= TableStructureFields.Length - 1; i++) { Boolean Found = false; for (int j = 0; j <= LinqTableProperties.Length - 1; j++) { if ((String)TableStructureFields[i].Name == (String)LinqTableProperties[j].Name) { Found = true; } } if (Found == false) return false; } return true; } catch (Exception ex) { Message = ex.ToString(); return false; } } public bool CompareLinqToTableStructure(object LinqTable, object TableStructure, ref String Message) { try { FieldInfo[] TableStructureFields; TableStructureFields = TableStructure.GetType().GetFields(); PropertyInfo[] LinqTableProperties; LinqTableProperties = LinqTable.GetType().GetProperties(); for (int j = 0; j <= LinqTableProperties.Length - 1; j++) { Boolean Found = false; switch (LinqTableProperties[j].PropertyType.FullName ) { case "System.Int32": case "System.Boolean": case "System.Double": case "System.String": for (int i = 0; i <= TableStructureFields.Length - 1; i++) { if ((String)TableStructureFields[i].Name == (String)LinqTableProperties[j].Name) { Found = true; } } if (Found == false) return false; break; } } return true; } catch (Exception ex) { Message = ex.ToString(); return false; } } #endregion #region OLEDB public OleDbConnection objConn = new OleDbConnection(); #region Basic database operations public OleDbConnection OpenConnection() { OleDbConnection objConnection = new OleDbConnection(); if (objConn == null) { objConnection.ConnectionString = ConnectionStr; objConnection.Open(); return objConnection; }; if (objConn.State != ConnectionState.Open) { objConnection.ConnectionString = ConnectionStr; objConnection.Open(); return objConnection; } else { return objConn; } } public DataSet SQLDS(string SQL) { System.Data.DataSet ds = new DataSet(); objConn = OpenConnection(); OleDbDataAdapter objCmd = new OleDbDataAdapter(SQL, objConn); objCmd.Fill(ds, DefaultTableName); objCmd.Dispose(); return ds; } public bool ExecuteSQL(string SQL) { objConn = OpenConnection(); OleDbCommand objCmd = new OleDbCommand(SQL, objConn); objCmd.CommandType = CommandType.Text; objCmd.ExecuteNonQuery(); objCmd.Dispose(); return true; } #endregion #region Higher database operations public DataSet GetDS(String TableName) { string SQL = "SELECT * FROM " + TableName + " "; DataSet ds = new DataSet(); ds = SQLDS(SQL); return ds; } #endregion #region Database string manipulation public string DBSTR(string sStr ) { // Function to convert strings into a format that the // database can take sStr = sStr.Replace("'", "''"); return sStr; } public string SQLBooleanValue(bool bBoolean ) { return " '" + BOOLEAN_To_DBSTR(bBoolean) + "' "; } public string SQLStringValue(string Str ) { return " '" + DBSTR(Str) + "' "; } public string BOOLEAN_To_DBSTR(bool bValue ) { if (bValue) { return "True"; } else { return "False"; }; } #endregion #endregion } }
这是用于将数据库函数暴露给业务层(而非 UI)的 Facade。它继承自 DBTable,并且有变得相当庞大的趋势。
TestDBFacade.cs =============== using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using UTILS.TestDB; using System.Data; namespace DAL.TestDB { public class TestDBFacade: DBTable { public TestDBFacade(String DatabaseServerName, string DatabaseName) : base(DatabaseServerName, DatabaseName) { } #region USR_USER public Boolean USR_USERInsertRecord(USR_USERInfo.TableStructure USR_USERRow, ref String Message) { try { USR_USER USR_USERLinq = new USR_USER(); if (ConvertTableStructureToLinq(USR_USERRow, USR_USERLinq, ref Message) == false) return false; TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; iTestDB_UsrTablesDataContext.USR_USERs.InsertOnSubmit(USR_USERLinq); iTestDB_UsrTablesDataContext.SubmitChanges(); } catch (Exception ex) { Message = ex.Message; return false; } return true; } public int USR_USERInsertAndReadRecord(USR_USERInfo.TableStructure USR_USERRow, ref String Message) { try { USR_USER USR_USERLinq = new USR_USER(); if (ConvertTableStructureToLinq(USR_USERRow, USR_USERLinq, ref Message) == false) return -1; TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; iTestDB_UsrTablesDataContext.USR_USERs.InsertOnSubmit(USR_USERLinq); iTestDB_UsrTablesDataContext.SubmitChanges(); return USR_USERLinq.USER_ID; } catch (Exception ex) { Message = ex.Message; return -1; } } public Boolean USR_USERGetRecord(ref USR_USERInfo.TableStructure USR_USERRow, ref String Message) { try { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; USR_USER USR_USERLinq; int USER_ID = USR_USERRow.USER_ID; USR_USERLinq = (from Something in iTestDB_UsrTablesDataContext.USR_USERs where Something.USER_ID == USER_ID select Something).Single(); if (ConvertLinqToTableStructure(USR_USERLinq, USR_USERRow, ref Message) == false) return false; return true; } catch (Exception ex) { Message = ex.Message; return false; } } public Boolean USR_USERUpdateRecord(USR_USERInfo.TableStructure USR_USERRow, ref String Message) { try { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; USR_USER USR_USERLinq; int USER_ID = USR_USERRow.USER_ID; USR_USERLinq = (from Something in iTestDB_UsrTablesDataContext.USR_USERs where Something.USER_ID == USER_ID select Something).Single(); if (ConvertTableStructureToLinq(USR_USERRow, USR_USERLinq, ref Message) == false) return false; iTestDB_UsrTablesDataContext.Refresh( System.Data.Linq.RefreshMode.KeepCurrentValues, USR_USERLinq); iTestDB_UsrTablesDataContext.SubmitChanges( System.Data.Linq.ConflictMode.ContinueOnConflict); return true; } catch (Exception ex) { Message = ex.Message; return false; } } public Boolean USR_USERDeleteRecord(int USER_ID, ref String Message) { try { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; USR_USER USR_USERLinq; USR_USERLinq = (from Something in iTestDB_UsrTablesDataContext.USR_USERs where Something.USER_ID == USER_ID select Something).Single(); iTestDB_UsrTablesDataContext.USR_USERs.DeleteOnSubmit(( from Something in iTestDB_UsrTablesDataContext.USR_USERs where Something.USER_ID == USER_ID select Something).Single()); iTestDB_UsrTablesDataContext.SubmitChanges(); return true; } catch (Exception ex) { Message = ex.Message; return false; } } public ListUSR_USERList() { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; List USR_USERList; USR_USERList = (from Something in iTestDB_UsrTablesDataContext.USR_USERs where Something.USER_ID > 0 select Something).ToList(); return USR_USERList; } public List USR_USERInfoList() { String Message = ""; List USR_USERInfoLst = new List (); List USR_USERLst; USR_USERLst = USR_USERList(); foreach( USR_USER el in USR_USERLst) { USR_USERInfo.TableStructure newElement = new USR_USERInfo.TableStructure(); if (ConvertLinqToTableStructure(el, newElement, ref Message) == false) return null; USR_USERInfoLst.Add(newElement); } return USR_USERInfoLst; } public DataSet USR_USERGetDS() { String TableName = "USR_USER"; DataSet DS = new DataSet(); DS = this.GetDS(TableName); return DS; } #endregion #region USR_USER_GROUP public Boolean USR_USER_GROUPInsertRecord( USR_USER_GROUPInfo.TableStructure USR_USER_GROUPRow, ref String Message) { try { USR_USER_GROUP USR_USER_GROUPLinq = new USR_USER_GROUP(); if (ConvertTableStructureToLinq(USR_USER_GROUPRow, USR_USER_GROUPLinq, ref Message) == false) return false; TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; iTestDB_UsrTablesDataContext.USR_USER_GROUPs.InsertOnSubmit( USR_USER_GROUPLinq); iTestDB_UsrTablesDataContext.SubmitChanges(); } catch (Exception ex) { Message = ex.Message; return false; } return true; } public int USR_USER_GROUPInsertAndReadRecord( USR_USER_GROUPInfo.TableStructure USR_USER_GROUPRow, ref String Message) { try { USR_USER_GROUP USR_USER_GROUPLinq = new USR_USER_GROUP(); if (ConvertTableStructureToLinq(USR_USER_GROUPRow, USR_USER_GROUPLinq, ref Message) == false) return -1; TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; iTestDB_UsrTablesDataContext.USR_USER_GROUPs.InsertOnSubmit( USR_USER_GROUPLinq); iTestDB_UsrTablesDataContext.SubmitChanges(); return USR_USER_GROUPLinq.USER_GROUP_ID; } catch (Exception ex) { Message = ex.Message; return -1; } } public Boolean USR_USER_GROUPGetRecord( ref USR_USER_GROUPInfo.TableStructure USR_USER_GROUPRow, ref String Message) { try { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; USR_USER_GROUP USR_USER_GROUPLinq; int USER_GROUP_ID = USR_USER_GROUPRow.USER_GROUP_ID; USR_USER_GROUPLinq = (from Something in iTestDB_UsrTablesDataContext.USR_USER_GROUPs where Something.USER_GROUP_ID == USER_GROUP_ID select Something).Single(); if (ConvertLinqToTableStructure(USR_USER_GROUPLinq, USR_USER_GROUPRow, ref Message) == false) return false; return true; } catch (Exception ex) { Message = ex.Message; return false; } } public Boolean USR_USER_GROUPUpdateRecord( USR_USER_GROUPInfo.TableStructure USR_USER_GROUPRow, ref String Message) { try { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; USR_USER_GROUP USR_USER_GROUPLinq; int USER_GROUP_ID = USR_USER_GROUPRow.USER_GROUP_ID; USR_USER_GROUPLinq = (from Something in iTestDB_UsrTablesDataContext.USR_USER_GROUPs where Something.USER_GROUP_ID == USER_GROUP_ID select Something).Single(); if (ConvertTableStructureToLinq(USR_USER_GROUPRow, USR_USER_GROUPLinq, ref Message) == false) return false; iTestDB_UsrTablesDataContext.Refresh( System.Data.Linq.RefreshMode.KeepCurrentValues, USR_USER_GROUPLinq); iTestDB_UsrTablesDataContext.SubmitChanges( System.Data.Linq.ConflictMode.ContinueOnConflict); return true; } catch (Exception ex) { Message = ex.Message; return false; } } public Boolean USR_USER_GROUPDeleteRecord(int USER_GROUP_ID, ref String Message) { try { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; USR_USER_GROUP USR_USER_GROUPLinq; USR_USER_GROUPLinq = (from Something in iTestDB_UsrTablesDataContext.USR_USER_GROUPs where Something.USER_GROUP_ID == USER_GROUP_ID select Something).Single(); iTestDB_UsrTablesDataContext.USR_USER_GROUPs.DeleteOnSubmit(( from Something in iTestDB_UsrTablesDataContext.USR_USER_GROUPs where Something.USER_GROUP_ID == USER_GROUP_ID select Something).Single()); iTestDB_UsrTablesDataContext.SubmitChanges(); return true; } catch (Exception ex) { Message = ex.Message; return false; } } public List USR_USER_GROUPList() { TestDB_UsrTablesDataContext iTestDB_UsrTablesDataContext = new TestDB_UsrTablesDataContext(); iTestDB_UsrTablesDataContext.Connection.ConnectionString = LinqConnectionStr; List USR_USER_GROUPList; USR_USER_GROUPList = (from Something in iTestDB_UsrTablesDataContext.USR_USER_GROUPs where Something.USER_GROUP_ID > 0 select Something).ToList(); return USR_USER_GROUPList; } public List USR_USER_GROUPInfoList() { String Message = ""; List USR_USER_GROUPInfoLst = new List (); List USR_USER_GROUPLst; USR_USER_GROUPLst = USR_USER_GROUPList(); foreach (USR_USER_GROUP el in USR_USER_GROUPLst) { USR_USER_GROUPInfo.TableStructure newElement = new USR_USER_GROUPInfo.TableStructure(); if (ConvertLinqToTableStructure(el, newElement, ref Message) == false) return null; USR_USER_GROUPInfoLst.Add(newElement); } return USR_USER_GROUPInfoLst; } public DataSet USR_USER_GROUPGetDS() { String TableName = "USR_USER_GROUP"; DataSet DS = new DataSet(); DS = this.GetDS(TableName); return DS; } #endregion } }
这里有一些单元测试,演示了这一切如何工作…… 注意:测试 USR_USERInfoTableStructure 检查值对象定义是否与 Linq 表定义同步……我认为这是一种构建 DAL 的很棒的方法,因为成本为 0 且易于扩展……
TestDBFacadeTest.cs =================== using System; using System.Text; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using DAL.TestDB; using UTILS.TestDB; using System.Data; using System.Xml; namespace DAL.TestDBTest { ////// Summary description for TestDBFacadeTest /// [TestClass] public class TestDBFacadeTest { private String _DatabaseServerName = "CHZUPREL227"; private String _DatabaseName = "TestDB"; public TestDBFacadeTest() { // // TODO: Add constructor logic here // } private TestContext testContextInstance; //////Gets or sets the test context which provides ///information about and functionality for the current test run. /// public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } [TestMethod] public void USR_USERCRUD() { String Message = ""; DAL.TestDB.TestDBFacade iTestDBFacade = new DAL.TestDB.TestDBFacade( _DatabaseServerName, _DatabaseName); //Add a row USR_USERInfo.TableStructure USR_USERRow = new USR_USERInfo.TableStructure(); //USR_USERRow.USER_ID = 1;//Index USR_USERRow.USER_NAME = "USER_NAME"; USR_USERRow.EMAIL = "EMAIL"; Assert.IsTrue(iTestDBFacade.USR_USERInsertRecord(USR_USERRow, ref Message)); //Insert and read index int USER_ID = 0; USER_ID = iTestDBFacade.USR_USERInsertAndReadRecord(USR_USERRow, ref Message); Assert.IsTrue(USER_ID > 0); //Read a row USR_USERRow.USER_ID = USER_ID; Assert.IsTrue(iTestDBFacade.USR_USERGetRecord(ref USR_USERRow, ref Message)); Assert.IsTrue(USR_USERRow.USER_NAME == "USER_NAME"); //Edit a row USR_USERRow.USER_NAME = "USER_NAME2"; Assert.IsTrue(iTestDBFacade.USR_USERUpdateRecord(USR_USERRow, ref Message)); USR_USERRow.USER_NAME = ""; Assert.IsTrue(iTestDBFacade.USR_USERGetRecord(ref USR_USERRow, ref Message)); Assert.IsTrue(USR_USERRow.USER_NAME == "USER_NAME2"); //List rows ListUSR_USERList; USR_USERList = iTestDBFacade.USR_USERList(); Assert.IsTrue(USR_USERList.Count > 0); List USR_USERInfoList; USR_USERInfoList = iTestDBFacade.USR_USERInfoList(); Assert.IsTrue(USR_USERInfoList.Count > 0); //Delete a row Assert.IsTrue(iTestDBFacade.USR_USERDeleteRecord(USR_USERRow.USER_ID, ref Message)); DataSet DS = new DataSet(); DS = iTestDBFacade.USR_USERGetDS(); Assert.IsTrue(DS.Tables[0].Rows.Count > 0); } [TestMethod] public void USR_USERInfoTableStructure() { String Message = ""; DAL.TestDB.TestDBFacade iTestDBFacade = new DAL.TestDB.TestDBFacade( _DatabaseServerName, _DatabaseName); //Check that all records in the TableStructure definition are present //in the Linq table definition USR_USERInfo.TableStructure USR_USERRow = new USR_USERInfo.TableStructure(); USR_USER USR_USERLinq = new USR_USER(); Assert.IsTrue(iTestDBFacade.CompareTableStructureToLinq(USR_USERRow, USR_USERLinq, ref Message)); Assert.IsTrue(iTestDBFacade.CompareLinqToTableStructure(USR_USERLinq, USR_USERRow, ref Message)); } [TestMethod] public void USR_USER_GROUPCRUD() { String Message = ""; DAL.TestDB.TestDBFacade iTestDBFacade = new DAL.TestDB.TestDBFacade( _DatabaseServerName, _DatabaseName); //Add a row USR_USER_GROUPInfo.TableStructure USR_USER_GROUPRow = new USR_USER_GROUPInfo.TableStructure(); USR_USER_GROUPRow.USER_GROUP = "USER_GROUP"; Assert.IsTrue(iTestDBFacade.USR_USER_GROUPInsertRecord(USR_USER_GROUPRow, ref Message)); //Insert and read index int USER_GROUP_ID = 0; USER_GROUP_ID = iTestDBFacade.USR_USER_GROUPInsertAndReadRecord( USR_USER_GROUPRow, ref Message); Assert.IsTrue(USER_GROUP_ID > 0); //Read a row USR_USER_GROUPRow.USER_GROUP_ID = USER_GROUP_ID; Assert.IsTrue(iTestDBFacade.USR_USER_GROUPGetRecord(ref USR_USER_GROUPRow, ref Message)); Assert.IsTrue(USR_USER_GROUPRow.USER_GROUP == "USER_GROUP"); //Edit a row USR_USER_GROUPRow.USER_GROUP = "USER_GROUP2"; Assert.IsTrue(iTestDBFacade.USR_USER_GROUPUpdateRecord(USR_USER_GROUPRow, ref Message)); USR_USER_GROUPRow.USER_GROUP = ""; Assert.IsTrue(iTestDBFacade.USR_USER_GROUPGetRecord(ref USR_USER_GROUPRow, ref Message)); Assert.IsTrue(USR_USER_GROUPRow.USER_GROUP == "USER_GROUP2"); //List rows List USR_USER_GROUPList; USR_USER_GROUPList = iTestDBFacade.USR_USER_GROUPList(); Assert.IsTrue(USR_USER_GROUPList.Count > 0); List USR_USER_GROUPInfoList; USR_USER_GROUPInfoList = iTestDBFacade.USR_USER_GROUPInfoList(); Assert.IsTrue(USR_USER_GROUPInfoList.Count > 0); //Delete a row Assert.IsTrue(iTestDBFacade.USR_USER_GROUPDeleteRecord( USR_USER_GROUPRow.USER_GROUP_ID, ref Message)); DataSet DS = new DataSet(); DS = iTestDBFacade.USR_USER_GROUPGetDS(); Assert.IsTrue(DS.Tables[0].Rows.Count > 0); } [TestMethod] public void USR_USER_GROUPInfoTableStructure() { String Message = ""; DAL.TestDB.TestDBFacade iTestDBFacade = new DAL.TestDB.TestDBFacade( _DatabaseServerName, _DatabaseName); //Check that all records in the TableStructure definition are present //in the Linq table definition USR_USER_GROUPInfo.TableStructure USR_USER_GROUPRow = new USR_USER_GROUPInfo.TableStructure(); USR_USER_GROUP USR_USER_GROUPLinq = new USR_USER_GROUP(); Assert.IsTrue(iTestDBFacade.CompareTableStructureToLinq(USR_USER_GROUPRow, USR_USER_GROUPLinq, ref Message)); Assert.IsTrue(iTestDBFacade.CompareLinqToTableStructure(USR_USER_GROUPLinq, USR_USER_GROUPRow, ref Message)); } [TestMethod] public void Reflection() { String Message = ""; TestDBFacade iTestDBFacade = new TestDBFacade(_DatabaseServerName, _DatabaseName); USR_USERInfo.TableStructure Input = new USR_USERInfo.TableStructure(); Input.USER_NAME = "USER_NAME"; Input.EMAIL = "EMAIL"; USR_USERInfo.TableStructure Output = new USR_USERInfo.TableStructure(); Output.USER_NAME = "USER_NAME2"; Output.EMAIL = "EMAIL"; Assert.IsTrue(iTestDBFacade.ConvertInfoToInfo((Object)Input, (Object)Output, ref Message)); Assert.IsTrue(Input.USER_NAME == Output.USER_NAME); } } }
业务逻辑层
我在这里放置应用程序的业务逻辑。在这种情况下,我没有业务逻辑,而是仅仅使用 UTILS.TestDB 中定义的值对象来暴露 DAL。
WPF UI 层
我的 WPF 层基于 http://www.galasoft-lb.ch/mydotnet/articles/article-2007041201.aspx 我强烈建议您阅读这篇文章,它写得非常好,并描述了如何包含设计时和测试数据。在本文中描述的示例中,我扩展了 Laurent Bugnion 的示例,以包含参数化数据提供程序和绑定对象列表。
与其重复 Laurent 的文章,不如看看我为处理对象列表所做的更改。每个 WPF 窗口都有一个数据提供程序,WPF 控件绑定到该数据提供程序。我们经常需要显示基于运行时才能知道的内容(例如外键)的数据。问题是 WPF 窗口的构造函数不允许参数。为了解决这个问题,我使用了一个 Singleton。这是一个使用静态变量的对象,只能实例化一次。这意味着它可以用来将参数(例如外键)传递给数据提供程序。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace UI.MyApplication { public class Singleton { private static Singleton instance; public Singleton() { } public static Singleton Instance() { if (instance == null) instance = new Singleton(); return instance; } private static String _DatabaseServerName = "Defualt"; public String DatabaseServerName { get { return _DatabaseServerName; } set { _DatabaseServerName = value; } } private static String _DatabaseName = "Defualt"; public String DatabaseName { get { return _DatabaseName; } set { _DatabaseName = value; } } } }
我在我的 WPF 页面的构造函数中初始化了这个 Singleton。
public WinMyApp() { Singleton t = Singleton.Instance(); t.DatabaseName = "TestDB"; t.DatabaseServerName = "CHZUPREL227"; InitializeComponent(); }
我在 WinMyAppDataProvider 类的 MakeData 过程中读取这些参数。这些参数在业务层的构造函数中使用。
Singleton t = Singleton.Instance(); String DatabaseName = t.DatabaseName; String DatabaseServerName = t.DatabaseServerName; BusinessLogicFacade iBusinessLogicFacade = new BusinessLogicFacade(DatabaseServerName, DatabaseName);
列表中的对象定义在 Items 类中,该类继承自 DependancyObject。它包含 Laurent 文章中描述的 Dependancy 属性。此外,我存储了每个属性的原始值,以确定项目是否已更改。我第一次尝试这样做是在依赖属性的 setter 中设置一个 Edited 标志,但当属性从 UI 更改时,它不会被执行。
public class Item : DependencyObject { #region USER_NAME private string _USER_NAMEOriginal; public string USER_NAME { get { return (string)GetValue(USER_NAMEProperty); } set { SetValue(USER_NAMEProperty, value); } } public static readonly DependencyProperty USER_NAMEProperty = DependencyProperty.Register("USER_NAME", typeof(string), typeof(Item), new UIPropertyMetadata("")); #endregion … public Boolean EDITED() { if (_USER_NAMEOriginal == USER_NAME && _EMAILOriginal == EMAIL && _USER_IDOriginal == USER_ID && _USER_GROUP_IDOriginal == USER_GROUP_ID && _USER_TO_USER_GROUP_IDOriginal == USER_TO_USER_GROUP_ID) { return false; } else { return true; } } public Item(String uSER_NAME, int uSER_ID, String eMAIL, int uSER_GROUP_ID, int uSER_TO_USER_GROUP_ID) { USER_NAME = uSER_NAME; USER_ID = uSER_ID; EMAIL = eMAIL; USER_GROUP_ID = uSER_GROUP_ID; USER_TO_USER_GROUP_ID = uSER_TO_USER_GROUP_ID; _USER_NAMEOriginal = USER_NAME; _USER_IDOriginal = USER_ID; _EMAILOriginal = EMAIL; _USER_GROUP_IDOriginal = USER_GROUP_ID; _USER_TO_USER_GROUP_IDOriginal = USER_TO_USER_GROUP_ID; } }
在数据提供程序中,我们创建一个可观察的对象列表。
private ObservableCollection- _myCollection = new ObservableCollection
- (); public ObservableCollection
- MyCollection { get { return _myCollection; } }
在 XAML 中,我们定义一个数据模板,其中包括绑定到数据提供程序的 数据绑定。
当用户在列表框中的控件上进行更新时,会相应地在数据提供程序对象中进行更改。一旦用户准备好将数据持久化回数据库,他就会点击保存按钮,该按钮会调用数据提供程序内的 PeristChangesToDB 函数。
String Message = ""; WinMyAppDataProvider dataProvider = TryFindResource("WinMyAppProvider") as WinMyAppDataProvider; if ( dataProvider.PersistChangesToDB(ref Message) == false) { MessageBox.Show("Error: Data not persisted successfully :" +Message); } else { MessageBox.Show("Data persisted successfully"); };
在数据提供程序中,我们实例化我们的业务层,并遍历每个项目,检查是否需要更新。
public Boolean PersistChangesToDB(ref string Message) { Singleton t = Singleton.Instance(); String DatabaseName = t.DatabaseName; String DatabaseServerName = t.DatabaseServerName; BusinessLogicFacade iBusinessLogicFacade = new BusinessLogicFacade( DatabaseServerName, DatabaseName); foreach (Item e in _myCollection) { if (e.EDITED()) { // Update USR_USER USR_USERInfo.TableStructure USR_USERRow = new USR_USERInfo.TableStructure(); USR_USERRow.USER_ID = e.USER_ID; if (iBusinessLogicFacade.USR_USERGetRecord(ref USR_USERRow, ref Message) == true) { USR_USERRow.USER_NAME = e.USER_NAME; if (iBusinessLogicFacade.USR_USERUpdateRecord(USR_USERRow, ref Message) == false) { Message = "Unable to update USER_ID =" + e.USER_ID.ToString() + " " + Message; return false; } } else { Message = "USER_ID =" + e.USER_ID.ToString() + " no longer exists"; return false; } … } } Message = ""; return true; }
结论
我希望这是一个构建可扩展应用程序的有用起点。我对任何改进的想法都很感兴趣。请给我您的评论。