领域模型中Relationship的编码实现
问题引入
在尝试利用DDD(领域模型驱动开发)的思想来开发新的系统。由于领域模型思考的出发点是按照现实的业务实体来建立,得到一个更接近客观尽量稳定的模型。常常遇到对象之间关系,最常见的就是1对n的关系,比如班级和学生的关系:
在实际设计编码中,对于有关系的2个类如何处理他们的关系呢?
1)双向导航?初始化存在麻烦。
2)单边导航?另外方向的查找会比较麻烦。
很容易得到他们的代码
public class CClase
{
public string Name{get;set;}
public ILazyList<CStudent> students{get;set}
}
public class CStudent
{
public CClase MyClass{get;set;}
public string Name{get;set;}
}
ILazyList<CStudent>将会延迟加载。其实在这个业务模型中,类中的字段应该都是必须的字段,在初始化时应该都能够确定。
在定义构造函数时,有下列几种方式:
理想模式:
CClase(string name,IQueryable<CStudent> stus);
CStudent(int id,string name,CClase c);
在Service Layer或者Repository层有类似下面的代码:
public IQueryable<CClass> GetAllClasses()
{
return from a in _db.Classes select new CClass(a.Name,from b in _db.Students where b.MyClass.Name==a.Name select new CStudent(b.Name,???));
}
???的代码将无法完成。陷入先有鸡还是先有蛋的困境。
折中模式:
一个折中的方案是:
public class CStudent
{
privade string _className;
public CClass MyClass{get{return _Service.GetClassByName(_className);}};
public string Name{get;set;}
public CStudent(string name,string className)
{
Name=name;_className=className;
}
}
在服务层的代码将如下:
public IQueryable<CClass> GetAllClasses()
{
return from a in _db.Classes select new CClass(a.Name,from b in _db.Students where b.MyClass.Name==a.Name select new CStudent(b.Name,a.Name));
}
这样带来的一个问题是领域模型这个层需要了解_Service这个层次,形成依赖。不够干净(不够贫血)
还有一个方案是:
在服务层的代码将如下:
public IQueryable<CClass> GetAllClasses()
{
IQueryable<CClass> classes=from a in _db.Classes select new CClass(a.Name,from b in _db.Students where b.MyClass.Name==a.Name select new CStudent(b.Name,a.Name));
//循环给CStudent中的MyClass赋值,
Return classes;
}
由于循环了classes,这样需要真正执行SQL语句,这样将失去了延迟加载的好处了。
最终的方案:
//-------CStudent.cs---------------------
public class CStudent
{
privade string _className;
public string Name{get;set;}
public CStudent(string name,string className)
{
Name=name;_className=className;
}
}
//---------ClassService.cs-----------------
public class ClassService
{
MyDBContext _db;
public ClassService(MyDBContext dbcotext)
{
_db= dbcotext;
}
public IQueryable<CClass> GetAllClasses()
{
return from a in _db.Classes
let s= GetStudentsByClassName(a.Name)
select new CClass(a.Name,s);
}
public IQueryable<CStudent> GetStudentsByClassName(string name)
{
return from b in _db.Students
where b.MyClass.Name==name
select new CStudent(b.Name,name);
}
public CClass GetClassByName(string name)
{
return from a in GetAllClasses().
where(x=>x.Name==name).
SingleOrDefault();
}
}
如果需要取得某个学生的班级请在使用层调用_Service.GetClassByName(_className)来取得。
也就是说只有单边的直接导航,另一侧采用间接导航。