老兄,我一次打那么多字也不容易啊。我是左窗口VS2010,右边开着CSDN……而且这个是面向没有基础的人的,也得让别人有时间消化一下。

解决方案 »

  1.   

    首先感谢大家的支持以及版主的加精。废话少说,我们继续。昨天简单讲了一下如何用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多字符了,下一贴继续(马上回来)。
      

  2.   

    好了,现在我们有了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一下,或者留言问我)今天先讲到这里,下回继续。再次感谢大家支持。
      

  3.   

    想当老师不是那么好当的,没有丰富的开发经验,当老师让人笑的。
    好为人师的太多了,被人指出问题又不肯接受,盲目自负。CSDN 还是适合问些纯技术的问题。比如某个控件怎么用啊,某个语法怎么用啊。
    这倒对我有很大的帮助。
      

  4.   

    “这里恳请各位读者不要纠结于一些细节问题,比如Student的Age怎么没有做验证之类的问题”验证恰恰是最重要的地方,这是体现逻辑的地方,楼主直接把最重要的给无视了,让我们情何以堪。
    那些什么增加修改删除,谁都写得出来,没什么可讲的。