首先感谢大家的支持以及版主的加精。废话少说,我们继续。昨天简单讲了一下如何用MVVM的方式读取数据,但那个只是最最基本的一点概念而已,如果你是MVVM新人,那么我们要学的还有很多。不过今天我们先把MVVM暂且一放,因为我们有一个问题尚未解决——即数据层。我们昨天是自己创建了一个静态类StudentDesignDataService,利用它返回一些随机生成的StudentViewModel,为的是使我们快速看到结果。不过我们不能一直没有数据层,否则就无法保存任何Student的数据了。为此,我们今天就抽出一点时间来写点简单的数据层代码,来为后来的MVVM讲解做铺垫。首先我说一下大体思路:其实很简单,由于这是讲解,所以我希望一切从简。原本我想使用JSON作为数据源,后来觉得有些人还得下载C#的JSON类库,干脆还是用XML吧。思路大体是这样:我们利用ADO.NET提供的DataSet和DataTable这两个类来操作数据,最后将其转换为XML数据保存到本地硬盘里。还记得昨天的Student类吗?我们需要将其改写一下,代码如下:using System; using System.Data;namespace MvvmTutorial.Model { public class Student { #region Fields public const string StudentIDPropertyName = "StudentID"; public const string NamePropertyName = "Name"; public const string AgePropertyName = "Age"; public const string GenderPropertyName = "Gender"; public const string DescriptionPropertyName = "Description"; #endregion // Fields #region Constructors /// <summary> /// Creates an instance of Student from DataRow /// </summary> public static Student FromDataRow(DataRow dataRow) { return new Student() { StudentID = dataRow.Field<Guid>(Student.StudentIDPropertyName), Name = dataRow.Field<string>(Student.NamePropertyName), Age = dataRow.Field<int>(Student.AgePropertyName), Gender = dataRow.Field<Gender>(Student.GenderPropertyName), Description = dataRow.Field<string>(Student.DescriptionPropertyName) }; } /// <summary> /// Creates a brand new instance of Student /// </summary> public static Student CreateNewStudent(string name, int age, Gender gender, string description) { return new Student() { IsNew = true, StudentID = Guid.NewGuid(), Name = name, Age = age, Gender = Gender.Male, Description = description }; } private Student() { } #endregion // Constructors #region Properties public Guid StudentID { get; private set; } public string Name { get; set; } public int Age { get; set; } public Gender Gender { get; set; } public string Description { get; set; } public bool IsNew { get; private set; } #endregion // Properties } public enum Gender { Male, Female } }注意到没?原先Student的默认构造函数已经被改成private的了——原因在于我不想让外界调用它,并且我更希望外界调用那个两个static构造函数,即FromDataRow和CreateNewStudent。其中,FromDataRow我们待会儿便会用到,而CreateNewStudent无非就是强迫你填入一些参数然后生成一个新的Student而已。下面是重点了。首先,为了简单起见,我不打算建立新的Project了,所以我就直接把数据层的代码放到MvvmTutorial.Model下了。在MvvmTutorial.Model的Project下添加一个新类,名字叫做DataRepository,代码如下:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.IO;namespace MvvmTutorial.Model { public class DataRepository { #region Fields private DataSet _data; /// <summary> /// The file path that we use to store XML file which holds all the app data we need /// </summary> public static readonly string PhysicalFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "mvvm.rep"); /// <summary> /// Name of [Students] table /// </summary> private const string StudentTableName = "Students"; #endregion // Fields #region Constructors public DataRepository() { this.CreateDataSource(); if (!File.Exists(PhysicalFilePath)) { this.AddDefaultStudents(); this.Save(); } else { this.Load(); } } #endregion // Constructors #region Properties /// <summary> /// Gets Students table /// </summary> private DataTable Students { get { return _data.Tables[StudentTableName]; } } #endregion // Properties #region Public Methods /// <summary> /// Gets all the rows from Students table, transforms them into Student and returns a list of Student intances. /// </summary> public IEnumerable<Student> GetStudents() { foreach (DataRow dataRow in Students.Rows) { yield return Student.FromDataRow(dataRow); } } public void SaveStudent(Student student) { // If current student is a new record, it means this student is a new tuple, so we // need to add it into table if (student.IsNew) { AddStudent(student); } else { // Otherwise, the student may have existed, we need to find it and update its values to current values // Here, we search the student. var target = Students.AsEnumerable().FirstOrDefault(p => p.Field<Guid>(Student.StudentIDPropertyName) == student.StudentID); if (target != null) // We indeed found our wanted student record, so update it { target.SetField<string>(Student.NamePropertyName, student.Name); target.SetField<int>(Student.AgePropertyName, student.Age); target.SetField<Gender>(Student.GenderPropertyName, student.Gender); target.SetField<string>(Student.DescriptionPropertyName, student.Description); } } _data.AcceptChanges(); this.Save(); } #endregion // Public Methods #region Private Methods /// <summary> /// Creates the structure of all tables /// </summary> private void CreateDataSource() { _data = new DataSet("StudentData"); DataTable students = new DataTable(StudentTableName); students.Columns.Add(Student.StudentIDPropertyName, typeof(Guid)); students.Columns.Add(Student.NamePropertyName, typeof(string)); students.Columns.Add(Student.AgePropertyName, typeof(int)); students.Columns.Add(Student.GenderPropertyName, typeof(Gender)); students.Columns.Add(Student.DescriptionPropertyName, typeof(string)); _data.Tables.Add(students); } /// <summary> /// Adds some default student rows to Students table if there aren't any yet. /// </summary> private void AddDefaultStudents() { int total = new Random(Guid.NewGuid().GetHashCode()).Next(10, 30); for (int i = 0; i < total; ++i) { string randomName = "Name " + i; int randomAge = new Random(Guid.NewGuid().GetHashCode()).Next(17, 30); Gender randomGender = i % 2 == 0 ? Gender.Male : Gender.Female; string randomDescription = "Description " + i; Student randomStudent = Student.CreateNewStudent(randomName, randomAge, randomGender, randomDescription); this.AddStudent(randomStudent); } } /// <summary> /// Accepts all changes to current repository and persist changes to XML file /// </summary> private void Save() { _data.AcceptChanges(); _data.WriteXml(PhysicalFilePath); } /// <summary> /// Reads data from XML file /// </summary> private void Load() { _data.ReadXml(PhysicalFilePath); } /// <summary> /// Adds one student as a new row to Students table /// </summary> /// <param name="student"></param> private void AddStudent(Student student) { Students.Rows.Add(new object[] { student.StudentID, student.Name, student.Age, student.Gender, student.Description }); } #endregion // Private Methods } }这段代码稍微有点长,不过并不难理解。大体功能就是:从C:\ProgramData\中找到本程序的XML数据文件,如果未找到就新建一个此文件,并填入一些随机数据。至于其他函数,我都有注释,应该比较好理解,不懂的留言提问。OK,我现在只能输入1200多字符了,下一贴继续(马上回来)。
好了,现在我们有了DataRepository这个类,那么它真的实现我们所要求的功能么?也就是说,它是否存在Bug我们尚不得知,所以,接下来的工作自然是——测试!我们要写点Unit test来在最短时间内测试我们的代码是否存在问题。在VS下面建立测试Project是非常方便的。比如,我是这么做的:打开DataRepository.cs,找到GetStudents方法,然后点右键,选择Create unit tests...,如果你用的是中文版,那差不多应该是选择“建立单元测试”。之后,跟着向导走,它会让你为Project取一个名字,我们就叫它MvvmTutorial.Test。搞定了吗?当你有了MvvmTutorial.Test之后,你会得到一个DataRepositoryTest.cs文件,此文件是VS为你自动生成的,因为你当时是在DataRepository中建立的unit test project。你需要在MvvmTutorial.Test工程下添加一个文件夹,名叫Model,并把DataRepositoryTest.cs拖到这个文件夹下,然后你要把DataRepositoryTest的namespace改成MvvmTutorial.Test.Model,这是因为以后也许我们会有其他层的测试文件,所以我们用文件夹来进行分类,并用namespace来进行逻辑分类。DataRepositoryTest里会有一些自动生成的代码,你可以不编辑它们,而你所要做的就是添加一些新的测试方法。注意,每个测试方法的头顶都有一个[TestMethod()],但凡有这个属性的方法都是要被测试(也就是会被run的方法)。DataRepositoryTest的代码如下:using MvvmTutorial.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq;namespace MvvmTutorial.Test.Model { /// <summary> ///This is a test class for DataRepositoryTest and is intended ///to contain all DataRepositoryTest Unit Tests ///</summary> [TestClass()] public class DataRepositoryTest { private TestContext testContextInstance; /// <summary> ///Gets or sets the test context which provides ///information about and functionality for the current test run. ///</summary> public TestContext TestContext { get { return testContextInstance; } set { testContextInstance = value; } } #region Additional test attributes // //You can use the following additional attributes as you write your tests: // //Use ClassInitialize to run code before running the first test in the class //[ClassInitialize()] //public static void MyClassInitialize(TestContext testContext) //{ //} // //Use ClassCleanup to run code after all tests in a class have run //[ClassCleanup()] //public static void MyClassCleanup() //{ //} // //Use TestInitialize to run code before running each test //[TestInitialize()] //public void MyTestInitialize() //{ //} // //Use TestCleanup to run code after each test has run //[TestCleanup()] //public void MyTestCleanup() //{ //} // #endregion /// <summary> ///A test for GetStudents ///</summary> [TestMethod()] public void GetStudentsTest() { DataRepository target = new DataRepository(); Debug.WriteLine(DataRepository.PhysicalFilePath); foreach (Student student in target.GetStudents()) { Debug.WriteLine(student.Name); } } /// <summary> ///A test for persisting a new student by using SaveStudent method ///</summary> [TestMethod()] public void AddNewStudentTest() { DataRepository target = new DataRepository(); var expect = Student.CreateNewStudent("Chris", 20, Gender.Male, "Lazy..."); target.SaveStudent(expect); target = new DataRepository(); var actual = target.GetStudents().FirstOrDefault(p => p.StudentID == expect.StudentID); if (actual != null) { Assert.AreEqual(actual.StudentID, expect.StudentID); Assert.AreEqual(actual.Name, actual.Name); Assert.AreEqual(actual.Age, expect.Age); Assert.AreEqual(actual.Gender, expect.Gender); Assert.AreEqual(actual.Description, actual.Description); } else { Assert.Fail("Didn't find the persisted student"); } } /// <summary> /// A test for updating an existing student by using SaveStudent method /// </summary> [TestMethod()] public void UpdateStudentTest() { DataRepository target = new DataRepository(); var expect = target.GetStudents().First(); expect.Name = "John"; expect.Gender = Gender.Male; expect.Age = 18; expect.Description = "Hello world!"; target.SaveStudent(expect); target = new DataRepository(); var actual = target.GetStudents().FirstOrDefault(p => p.StudentID == expect.StudentID); if (actual != null) { Assert.AreEqual(actual.StudentID, expect.StudentID); Assert.AreEqual(actual.Name, actual.Name); Assert.AreEqual(actual.Age, expect.Age); Assert.AreEqual(actual.Gender, expect.Gender); Assert.AreEqual(actual.Description, actual.Description); } else { Assert.Fail("Didn't find the updated student"); } } } } 那一大段被注释掉的代码是自动生成的,我们可能以后会用到,所以先留着它备用。好了,现在你需要做的是run一遍所有Test,然后观察程序输出以及它们是否全部通过。(我测试过,应该是全部通过,如果有问题,你得自己debug一下,或者留言问我)今天先讲到这里,下回继续。再次感谢大家支持。
using System.Data;namespace MvvmTutorial.Model
{
public class Student
{
#region Fields
public const string StudentIDPropertyName = "StudentID";
public const string NamePropertyName = "Name";
public const string AgePropertyName = "Age";
public const string GenderPropertyName = "Gender";
public const string DescriptionPropertyName = "Description";
#endregion // Fields #region Constructors
/// <summary>
/// Creates an instance of Student from DataRow
/// </summary>
public static Student FromDataRow(DataRow dataRow)
{
return new Student()
{
StudentID = dataRow.Field<Guid>(Student.StudentIDPropertyName),
Name = dataRow.Field<string>(Student.NamePropertyName),
Age = dataRow.Field<int>(Student.AgePropertyName),
Gender = dataRow.Field<Gender>(Student.GenderPropertyName),
Description = dataRow.Field<string>(Student.DescriptionPropertyName)
};
} /// <summary>
/// Creates a brand new instance of Student
/// </summary>
public static Student CreateNewStudent(string name, int age, Gender gender, string description)
{
return new Student()
{
IsNew = true,
StudentID = Guid.NewGuid(),
Name = name,
Age = age,
Gender = Gender.Male,
Description = description
};
} private Student() { }
#endregion // Constructors #region Properties
public Guid StudentID
{
get;
private set;
} public string Name
{
get;
set;
} public int Age
{
get;
set;
} public Gender Gender
{
get;
set;
} public string Description
{
get;
set;
} public bool IsNew
{
get;
private set;
}
#endregion // Properties
} public enum Gender
{
Male,
Female
}
}注意到没?原先Student的默认构造函数已经被改成private的了——原因在于我不想让外界调用它,并且我更希望外界调用那个两个static构造函数,即FromDataRow和CreateNewStudent。其中,FromDataRow我们待会儿便会用到,而CreateNewStudent无非就是强迫你填入一些参数然后生成一个新的Student而已。下面是重点了。首先,为了简单起见,我不打算建立新的Project了,所以我就直接把数据层的代码放到MvvmTutorial.Model下了。在MvvmTutorial.Model的Project下添加一个新类,名字叫做DataRepository,代码如下:using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.IO;namespace MvvmTutorial.Model
{
public class DataRepository
{
#region Fields
private DataSet _data;
/// <summary>
/// The file path that we use to store XML file which holds all the app data we need
/// </summary>
public static readonly string PhysicalFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "mvvm.rep");
/// <summary>
/// Name of [Students] table
/// </summary>
private const string StudentTableName = "Students";
#endregion // Fields #region Constructors
public DataRepository()
{
this.CreateDataSource(); if (!File.Exists(PhysicalFilePath))
{
this.AddDefaultStudents();
this.Save();
}
else
{
this.Load();
}
}
#endregion // Constructors #region Properties
/// <summary>
/// Gets Students table
/// </summary>
private DataTable Students
{
get
{
return _data.Tables[StudentTableName];
}
}
#endregion // Properties #region Public Methods
/// <summary>
/// Gets all the rows from Students table, transforms them into Student and returns a list of Student intances.
/// </summary>
public IEnumerable<Student> GetStudents()
{
foreach (DataRow dataRow in Students.Rows)
{
yield return Student.FromDataRow(dataRow);
}
} public void SaveStudent(Student student)
{
// If current student is a new record, it means this student is a new tuple, so we
// need to add it into table
if (student.IsNew)
{
AddStudent(student);
}
else
{
// Otherwise, the student may have existed, we need to find it and update its values to current values
// Here, we search the student.
var target = Students.AsEnumerable().FirstOrDefault(p => p.Field<Guid>(Student.StudentIDPropertyName) == student.StudentID);
if (target != null) // We indeed found our wanted student record, so update it
{
target.SetField<string>(Student.NamePropertyName, student.Name);
target.SetField<int>(Student.AgePropertyName, student.Age);
target.SetField<Gender>(Student.GenderPropertyName, student.Gender);
target.SetField<string>(Student.DescriptionPropertyName, student.Description);
}
}
_data.AcceptChanges();
this.Save();
}
#endregion // Public Methods #region Private Methods
/// <summary>
/// Creates the structure of all tables
/// </summary>
private void CreateDataSource()
{
_data = new DataSet("StudentData"); DataTable students = new DataTable(StudentTableName);
students.Columns.Add(Student.StudentIDPropertyName, typeof(Guid));
students.Columns.Add(Student.NamePropertyName, typeof(string));
students.Columns.Add(Student.AgePropertyName, typeof(int));
students.Columns.Add(Student.GenderPropertyName, typeof(Gender));
students.Columns.Add(Student.DescriptionPropertyName, typeof(string)); _data.Tables.Add(students);
} /// <summary>
/// Adds some default student rows to Students table if there aren't any yet.
/// </summary>
private void AddDefaultStudents()
{
int total = new Random(Guid.NewGuid().GetHashCode()).Next(10, 30);
for (int i = 0; i < total; ++i)
{
string randomName = "Name " + i;
int randomAge = new Random(Guid.NewGuid().GetHashCode()).Next(17, 30);
Gender randomGender = i % 2 == 0 ? Gender.Male : Gender.Female;
string randomDescription = "Description " + i;
Student randomStudent = Student.CreateNewStudent(randomName, randomAge, randomGender, randomDescription);
this.AddStudent(randomStudent);
}
} /// <summary>
/// Accepts all changes to current repository and persist changes to XML file
/// </summary>
private void Save()
{
_data.AcceptChanges();
_data.WriteXml(PhysicalFilePath);
} /// <summary>
/// Reads data from XML file
/// </summary>
private void Load()
{
_data.ReadXml(PhysicalFilePath);
} /// <summary>
/// Adds one student as a new row to Students table
/// </summary>
/// <param name="student"></param>
private void AddStudent(Student student)
{
Students.Rows.Add(new object[]
{
student.StudentID,
student.Name,
student.Age,
student.Gender,
student.Description
});
}
#endregion // Private Methods
}
}这段代码稍微有点长,不过并不难理解。大体功能就是:从C:\ProgramData\中找到本程序的XML数据文件,如果未找到就新建一个此文件,并填入一些随机数据。至于其他函数,我都有注释,应该比较好理解,不懂的留言提问。OK,我现在只能输入1200多字符了,下一贴继续(马上回来)。
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;namespace MvvmTutorial.Test.Model
{
/// <summary>
///This is a test class for DataRepositoryTest and is intended
///to contain all DataRepositoryTest Unit Tests
///</summary>
[TestClass()]
public class DataRepositoryTest
{
private TestContext testContextInstance; /// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
} #region Additional test attributes
//
//You can use the following additional attributes as you write your tests:
//
//Use ClassInitialize to run code before running the first test in the class
//[ClassInitialize()]
//public static void MyClassInitialize(TestContext testContext)
//{
//}
//
//Use ClassCleanup to run code after all tests in a class have run
//[ClassCleanup()]
//public static void MyClassCleanup()
//{
//}
//
//Use TestInitialize to run code before running each test
//[TestInitialize()]
//public void MyTestInitialize()
//{
//}
//
//Use TestCleanup to run code after each test has run
//[TestCleanup()]
//public void MyTestCleanup()
//{
//}
//
#endregion
/// <summary>
///A test for GetStudents
///</summary>
[TestMethod()]
public void GetStudentsTest()
{
DataRepository target = new DataRepository();
Debug.WriteLine(DataRepository.PhysicalFilePath); foreach (Student student in target.GetStudents())
{
Debug.WriteLine(student.Name);
}
} /// <summary>
///A test for persisting a new student by using SaveStudent method
///</summary>
[TestMethod()]
public void AddNewStudentTest()
{
DataRepository target = new DataRepository();
var expect = Student.CreateNewStudent("Chris", 20, Gender.Male, "Lazy...");
target.SaveStudent(expect); target = new DataRepository();
var actual = target.GetStudents().FirstOrDefault(p => p.StudentID == expect.StudentID);
if (actual != null)
{
Assert.AreEqual(actual.StudentID, expect.StudentID);
Assert.AreEqual(actual.Name, actual.Name);
Assert.AreEqual(actual.Age, expect.Age);
Assert.AreEqual(actual.Gender, expect.Gender);
Assert.AreEqual(actual.Description, actual.Description);
}
else
{
Assert.Fail("Didn't find the persisted student");
}
} /// <summary>
/// A test for updating an existing student by using SaveStudent method
/// </summary>
[TestMethod()]
public void UpdateStudentTest()
{
DataRepository target = new DataRepository();
var expect = target.GetStudents().First();
expect.Name = "John";
expect.Gender = Gender.Male;
expect.Age = 18;
expect.Description = "Hello world!";
target.SaveStudent(expect); target = new DataRepository();
var actual = target.GetStudents().FirstOrDefault(p => p.StudentID == expect.StudentID);
if (actual != null)
{
Assert.AreEqual(actual.StudentID, expect.StudentID);
Assert.AreEqual(actual.Name, actual.Name);
Assert.AreEqual(actual.Age, expect.Age);
Assert.AreEqual(actual.Gender, expect.Gender);
Assert.AreEqual(actual.Description, actual.Description);
}
else
{
Assert.Fail("Didn't find the updated student");
}
}
}
}
那一大段被注释掉的代码是自动生成的,我们可能以后会用到,所以先留着它备用。好了,现在你需要做的是run一遍所有Test,然后观察程序输出以及它们是否全部通过。(我测试过,应该是全部通过,如果有问题,你得自己debug一下,或者留言问我)今天先讲到这里,下回继续。再次感谢大家支持。
好为人师的太多了,被人指出问题又不肯接受,盲目自负。CSDN 还是适合问些纯技术的问题。比如某个控件怎么用啊,某个语法怎么用啊。
这倒对我有很大的帮助。
那些什么增加修改删除,谁都写得出来,没什么可讲的。