Skip to main content

配置隐式关联

简单隐式关联

假设有一个订单和订单明细的模型。每个订单明细都与一个订单相关联,这种关联是通过订单ID来表示的,但并没有显式地定义为一个关联类或关联属性。

以下是是隐式关联的模型示例:

public class Order  
{
public int OrderId { get; set; }
public List<OrderDetail> OrderDetails { get; set; }
}

public class OrderDetail
{
public int OrderDetailId { get; set; }
public int OrderId { get; set; } // 隐式关联到订单
public decimal Quantity { get; set; }
public decimal Price { get; set; }
}

配置隐式关联代码:

protected override void CreateModel(ModelBuilder modelBuilder)
{
var orderAndDetailAss = modelBuilder.Association<Order, OrderDetail>();
orderAndDetailAss.ToTable("Order");
}
提示

在大多数情况下,我们不需要手动配置隐式关联,因为Obase框架会自动检测并配置。

多方隐式关联

以“上课”关联为例,如果业务需求不考虑缺勤问题,我们无需在领域模型中定义关联类,该关联即为隐式关联。在Obase中注册多方隐式关联时,具体的配置与二方隐式关联有所不同。首先,需要定义一个承载该关联的类,该类的结构与上述关联类相似,但不需要定义关联属性。然后,应将该关联配置为“显式”。这种配置方式称为隐式关联的显式化配置。

此外,基于多方隐式关联定义的关联引用其配置方式也有所不同。以下代码展示了定义在Class类中的关联引用_havingClassRecords,它引用当前班级的历史上课记录,是一个Tuple< Teacher, Room >集合,每个元素代表一次上课,由任科老师和教室聚合而成。

/// <summary>
///表示班级
/// </summary>
public class Class
{
/// <summary>
///班级id
/// </summary>
private long _classId;

/// <summary>
///班级名称
/// </summary>
private string _name;

/// <summary>
///班级的课程记录
/// </summary>
private List<Tuple<Teacher, Room>> _havingClassRecords;

/// <summary>
///班级id
/// </summary>
public long ClassId
{
get => _classId;
set => _classId = value;
}

/// <summary>
///班级名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}

/// <summary>
///班级的课程
/// </summary>
public virtual List<Tuple<Teacher, Room>> HavingClassRecords
{
get => _havingClassRecords;
set => _havingClassRecords = value;
}

/// <summary>
///添加记录
/// </summary>
/// <param name="havingClass"></param>
public void AddHavingClassRecords(HavingClass havingClass)
{
if (_havingClassRecords == null)
_havingClassRecords = new List<Tuple<Teacher, Room>>();
if(havingClass.Class.ClassId == _classId)
_havingClassRecords.Add(new Tuple<Teacher, Room>(havingClass.Teacher, havingClass.Room));
}

/// <summary>
///获取记录
/// </summary>
/// <returns></returns>
public List<HavingClass> GetHavingClassRecords()
{
return _havingClassRecords?.Select(p => new HavingClass()
{ ClassId = ClassId, Class = this, TeacherId = p.Item1.TeacherId, Teacher = p.Item1, RoomId = p.Item2.RoomId, Room = p.Item2 }).ToList();
}
}
/// <summary>
///走班制上课
/// </summary>
public class HavingClass
{
/// <summary>
///班级
/// </summary>
public virtual Class Class {get;set;}

/// <summary>
///教师
/// </summary>
public virtual Teacher Teacher {get;set;}

/// <summary>
///班级ID
/// </summary>
public long ClassId {get;set;}

/// <summary>
///教师ID
/// </summary>
public long TeacherId {get;set;}

/// <summary>
///教室ID
/// </summary>
public long RoomId {get;set;}

/// <summary>
///教室
/// </summary>
public virtual Room Room {get;set;}
}

这里仅列出修改后的HavingClass和Class类,其他类与显式多方关联相较无修改。 由于_havingClassRecords是隐式引用,它指向关联端而不是关联实例,但我们已将其关联实施了显式化配置,而显式引用指向的应该是关联实例,为了遵循这一原则,我们需要为该关联引用配置特殊的取值器和设值器。

//注册实体型
modelBuilder.Entity<Teacher>();
var classConfiguration = modelBuilder.Entity<Class>();
modelBuilder.Entity<Room>();

//注册关联型
var havingClassAssociation = modelBuilder.Association<HavingClass>();

//注册关联端 设置为端默认附加新对象
havingClassAssociation.AssociationEnd(p => p.Class).HasDefaultAsNew(true);
havingClassAssociation.AssociationEnd(p => p.Teacher).HasDefaultAsNew(true);
havingClassAssociation.AssociationEnd(p => p.Room).HasDefaultAsNew(true);
//多方显示关联需要配置关联引用和左端
var associationReference = classConfiguration.AssociationReference<HavingClass>("HavingClassRecords", true);
associationReference.HasLeftEnd(p => p.Class);

//此处需要为这个关联引用设置设值器 因为这个关联的引用的关联型是HavingClass 但他的引用类型是Tuple<Teacher, Room> 需要进行转换
//设值器是相对于属性来说的 即调用属性的Set方法为属性设置值
//第一个参数是个委托 指示如何从HavingClass转为List<Tuple<Teacher, Room>> 这里使用了一个Class类定义的方法也可以使用Lambda表达式
//第二个是模式 对于多重的关联对象 为追加模式 否则为赋值模式
//这里使用了一个类内方法作为他的自定义取值器
associationReference.HasValueSetter<HavingClass>((cla, havingClass) =>
cla.AddHavingClassRecords(havingClass), eValueSettingMode.Appending)
.HasValueGetter(typeof(Class).GetMethod("GetHavingClassRecords"));

配置完成后,多方隐式关联的新增、删除,关联端加载及投影都与二方隐式关联无异,概从略。

自定义关联型的隐式关联

有些时候,我们需要对具有相同关联端的关系配置不同的关联型,比如教师和班级间平时是“教学”关系,而在考试期间则是“监考”关系。一般而言,班级的监考教师不会是平时的任教教师,这时我们就需要为班级定义两个不同的关系“教学”和“监考”,那么我们定义的类如下:

/// <summary>
///表示教师
/// </summary>
public class Teacher
{
/// <summary>
///教师姓名
/// </summary>
private string _name;

/// <summary>
/// 教师ID
/// </summary>
private long _teacherId;

/// <summary>
/// 教师ID
/// </summary>
public long TeacherId
{
get => _teacherId;
set => _teacherId = value;
}

/// <summary>
///学校名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}

/// <summary>
///转换为字符串表示形式
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Teacher:{TeacherId-{_teacherId},Name-\"{_name}\"}";
}
}
/// <summary>
///表示班级
/// </summary>
public class Class
{
/// <summary>
///班级id
/// </summary>
private long _classId;

/// <summary>
///班级的监考教师
/// </summary>
private List<Teacher> _invigilating;


/// <summary>
///班级名称
/// </summary>
private string _name;

/// <summary>
///班级的任课教师
/// </summary>
private List<Teacher> _teachings;

/// <summary>
///班级id
/// </summary>
public long ClassId
{
get => _classId;
set => _classId = value;
}

/// <summary>
///班级名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}

/// <summary>
///班级的任课教师
/// </summary>
public virtual List<Teacher> Teachings
{
get => _teachings;
set => _teachings = value;
}

/// <summary>
///班级的监考教师
/// </summary>
public virtual List<Teacher> Invigilating
{
get => _invigilating;
set => _invigilating = value;
}

/// <summary>
///转换为字符串表示形式
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Class:{ClassId-{_classId},Name-\"{_name}\"}";
}
}

教师类与之前描述的没有变化。在班级类中,我们定义了两个不同的关联引用。为了清晰地表示这两个关系,我们为它们建立了两张独立的关联表。如果使用伴随关联的方式,我们需要确保监考教师和任教教师在关联表中使用不同的字段作为映射字段,以避免数据混淆。这里为了演示方便,我们使用了独立的关联表。

对于此类自定义关联型的隐式关联,其操作方式与一般的隐式关联类似。在查询时,我们可以使用Include方法强制包含关联数据,也可以选择延迟加载。这种方式提供了更大的灵活性,可以根据实际需求选择合适的加载策略,提高查询效率和数据一致性。

//注册实体型
modelBuilder.Entity<Teacher>();
var classConfiguration = modelBuilder.Entity<Class>();

//注册关联型 并且设置为隐式关联
modelBuilder.Association<Teaching>().HasVisible(false);
modelBuilder.Association<Invigilating>().HasVisible(false);

//为各自的关联引用设置 注意这里要设置左右端
classConfiguration.AssociationReference<Teaching>("Teachings", true)
.HasLeftEnd(p => p.Class).HasRightEnd(p => p.Teacher);
classConfiguration.AssociationReference<Invigilating>("Invigilating", true)
.HasLeftEnd(p => p.Class).HasRightEnd(p => p.Teacher);

以上就是配置代码,注意要将关联型配置为隐式以及为关联引用配置左端和右端。

var context = new CustomImplicitContext();

var cla = new Class
{
Name = @"某某班1"
};

//新两个增老师
var teacher1 = new Teacher {Name = "老师1"};
var teacher2 = new Teacher {Name = "老师2"};

//添加任教和监考教师
cla.Teachings = new List<Teacher> {teacher1};
cla.Invigilating = new List<Teacher> {teacher2};

context.Classes.Attach(cla);
context.Teachers.Attach(teacher1);
context.Teachers.Attach(teacher2);

context.SaveChanges();
var context = new CustomImplicitContext();

var cla = context.Classes.FirstOrDefault();

if (cla?.Teachings != null)
foreach (var teacher in cla?.Teachings)
Console.WriteLine(teacher);

if (cla?.Invigilating != null)
foreach (var teacher in cla?.Invigilating)
Console.WriteLine(teacher);

这里仅列出如何添加和查询,其余方法读者们可以自己编写代码。