本次改动的主要内容是实现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>()); }
具体请参考源码
本人深感能力不足,欢迎大家指正、指教。