本次改動的主要內容是實現MongoDB.Repository對MongoDBRef的支持。
MongoDB對一對一,一對多,多對多關系的維護,官方推薦文檔嵌入方式,反映到模型的設計如下:
public class Student : Entity { public string Name { get; set; } public int Age { get; set; } } public class Teacher : Entity { public string Name { get; set; } public int Age { get; set; } } public class Grade : RefEntity { public string Name { get; set; } } public class School : Entity { public string Name { get; set; } public List<MongoDBRef> Students { get; set; } public List<MongoDBRef> Teachers { get; set; } public MongoDBRef Master { get; set; } }
該種設計在數據庫中的存儲格式如下圖:
其中的Students, Teachers, Master都以嵌入在School集合中,而在相應的Student,Teacher集合中,並沒有相關的數據。
如果是多學校,我們要查詢系統中所有的學生,或老師,那將是一件非常費力的工作。而我們最終目的應該是在School中存儲鍵值,應該使用MongoDBRef實現,如下:
public class School : Entity { public string Name { get; set; } public List<MongoDBRef> Students { get; set; } public List<MongoDBRef> Teachers { get; set; } public MongoDBRef Master { get; set; } }
而這種設計,MongoDBRef的數據結構則過於簡單,只有Id, CollectionName,DatabaseName三個字段,如果要得到Master,只能去Teacher集合中再次執行查詢操作。
而本次提交,解決的正是這個問題,使代碼可以寫成如school.Pick<Student>(student.Id).Name的形式。
主要接口:IRefEntity,IDBRefContainer。
IRefEntity:為減少數據冗余,並提供一個Update方法。Update方法的邏輯是先保存當前實體,即執行IEntity.Save(),然后對DBRefs中的數據進行保存,接口定義如下:
public interface IRefEntity : IEntity { /// <summary> /// list of MongoDBRef /// </summary> List<MongoDBRef> DBRefs { get; set; } /// <summary> /// save IEntity first, then save list of MongoDBRef /// </summary> void Update(); }
IDBRefContainer接口,為IRefEntity.DBRefs的數據提供一個對應的實體容器,已完成相應的查詢、添加、刪除操作,接口定義如下:
public interface IDBRefContainer { bool Exists(string id); bool Exists<T>() where T : IEntity; bool Exists<T>(Predicate<T> match) where T : IEntity; T Pick<T>(string id) where T : IEntity; T Pick<T>(Expression<Func<T, bool>> where) where T : IEntity; List<T> GetAll<T>() where T : IEntity; List<T> GetMany<T>(Expression<Func<T, bool>> where) where T : IEntity; void Add<T>(T entity) where T : IEntity; void Add<T>(List<T> entities) where T : IEntity; int Remove<T>(Expression<Func<T, bool>> where) where T : IEntity; void Remove<T>(T entity) where T : IEntity; void Remove<T>() where T : IEntity; int Count<T>() where T : IEntity; int Count<T>(Expression<Func<T, bool>> where) where T : IEntity; List<IEntity> GetAll(); }
需要注意的是,盡量使用IEntity.Save()進行保存操作,而減少使用IDBRefEntity.Update()進行更新保存 操作,因為IDBRefEntity.Update()針對的是所有IDBRefContainer中的數據,其執行效率還有待改善和提高。本人也一直在糾結,這個IDBRefEntity.Update()是否需要或者是否應該提供。
下面給出一個具體的測試用例:
[TestCase] public void test() { grade = new Grade(); grade.Name = "No1"; foreach (Student student in students) grade.Add<Student>(student); foreach (Teacher teacher in teachers) grade.Add<Teacher>(teacher); grade.Update(); students[2].Name = "NameChanged"; students[2].Save(); var g = MongoEntity.Get<Grade>(grade.Id); Assert.AreSame(students[2].Name, grade.Pick<Student>(students[2].Id).Name); Assert.AreNotSame(grade.Pick<Student>(students[2].Id).Name, g.Pick<Student>(students[2].Id).Name); Assert.AreEqual(grade.Count<Student>(), g.Count<Student>()); Assert.AreEqual(grade.Count<Teacher>(), g.Count<Teacher>()); }
具體請參考源碼
本人深感能力不足,歡迎大家指正、指教。
