在数据库模型设计中,最基本的实体关系有三种:一对一、一对多、多对多。关于一对多和多对多使用的情况较多,之前也有过一些讨论,现在来说明一下在数据库中一对一的模型设计。

首先,关系数据库中使用外键来表示一对多,使用中间表和两边的外键来表示多对多,而一对一的话有三种表示方式:一种是使用相同的主键值,第二种是使用单边的外键,第三种就是使用双边外键。

1.主键关联

比如我们在做一个ER系统时,设计了一个Employee表保存员工的基本信息(主表),另外有一个EmployeePhoto表(外表),用于保存员工的证件照,员工和照片之间就是一对一的关系。

public class Employee:Entity
{
public virtual string EmployeeNumber { get; set; }
public virtual string Name { get; set; }
public virtual EmployeePhoto EmployeePhoto { get; set; }
}
public class EmployeePhoto:Entity
{
/// <summary>
/// 员工照片,应该是二进制数据,这里只是一个例子,为了方便,所以用String类型
/// </summary>
public virtual string Photo { get; set; } public virtual Employee Employee { get; set; }
}

下面是FluentNHibernate的Mapping配置:

public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Table("EMPLOYEE");
Id(x => x.Id, "EMPLOYEE_ID").GeneratedBy.HiLo("1000000000");
Map(x => x.EmployeeNumber, "EMPLOYEE_NUMBER").Not.Nullable();
Map(x => x.Name, "NAME").Not.Nullable();
HasOne(x => x.EmployeePhoto).Cascade.All();
}
}
public class EmployeePhotoMap : ClassMap<EmployeePhoto>
{
public EmployeePhotoMap()
{
Table("EMPLOYEE_PHOTO");
Id(x => x.Id, "EMPLOYEE_ID").GeneratedBy.Foreign("Employee");
Map(x => x.Photo, "PHOTO").Not.Nullable();
HasOne(x => x.Employee).Cascade.None().Constrained();
}
}

这里需要注意的是EmployeePhoto的主键,不再是普通的生成方式,而是要选择通过Employee做外键生成。

关于NHibernate 的one to one标签上的constrained="true",该标签在外表上设置,千万不要在主表上设置。就是说明这个表的主键与另一个表的主键建立外键约束,也就是说在生成SQL脚本时,会为这个表创建外键,如果不加,是不会创建外键的。另外还有一个作用,就是在查询外表时,如果没有设置该属性,那么就会Join主表,而设置了该属性,就只需要查询外表。

在主键关联的情况下,如果从主表中移除从表的引用,这个时候保存主表,是不会删除从表的,也不会删除这个一对一的关系的。也就是说,我们不能单独保留Employee和Photo表,同时还要去掉两者之间的关系。

2.单向外键关联

比如我们做个中学的管理系统,设计了一个Class表保存班级,另一个Classroom表保存教室,班级和教室是一对一的关系,一个班级有且仅有一个教室,一个教室属于0到1个班级。

public class Class : Entity,IPermanent
{
public virtual bool IsDeleted { get; set; }
public virtual string ClassName { get; set; }
public virtual int StudentCount { get; set; }
public override string ToString()
{
return "Class Id:" + Id + " Name:" + ClassName;
}
public virtual Classroom Classroom { get; set; }
}
public class Classroom:Entity,IPermanent
{
public virtual string Building { get; set; }
public virtual string RoomNumber { get; set; }
public override string ToString()
{
return "Classroom[" + Id + "] " + Building + " " + RoomNumber;
}
public virtual bool IsDeleted { get; set; }
public virtual Class Class { get; set; }
}

Mapping的代码是:

public class ClassMap : ClassMap<Class>
{
public ClassMap()
{
Table("CLASS");
Id(x => x.Id, "CLASS_ID").GeneratedBy.HiLo("1000000000");
Map(x => x.ClassName, "CLASS_NAME").Not.Nullable();
Map(x => x.StudentCount, "STUDENT_COUNT").Not.Nullable();
Map(x => x.IsDeleted, "IS_DELETED");
References(x => x.Classroom, "CLASSROOM_ID").Cascade.All();
ApplyFilter<IsDeletedFilter>("IS_DELETED = :DeleteFlag");
}
}
public class ClassroomMap : ClassMap<Classroom>
{
public ClassroomMap()
{
Table("CLASSROOM");
Id(x => x.Id, "CLASSROOM_ID").GeneratedBy.HiLo("1000000000");
Map(x => x.Building, "BUILDING");
Map(x => x.RoomNumber, "ROOM_NUMBER");
Map(x => x.IsDeleted, "IS_DELETED");
HasOne(x => x.Class).PropertyRef(r => r.Classroom);
ApplyFilter<IsDeletedFilter>("IS_DELETED = :DeleteFlag");
}
}

这里两个表中只需要有一个表持有对方的主键作为外键即可,我们可以在CLASS表中添加CLASSROOM_ID来作为外键,也可以在CLASSROOM表中添加CLASS表作为外键。选择哪一个好呢?如果相互之间都对应的是0到1个对方,那么其实选哪边都无所谓,但是如果我们假定一个Class必须要对应一个Classroom,而一个Classroom可以对应0到1个Class,那么我们就必须在CLASS表中添加CLASSROOM_ID,因为我们必须先创建Classroom,然后再创建Class,然后可以在数据库中将CLASS表中的CLASSROOM_ID设置为不允许为空(当然,设置为允许为空也没有问题,这样可以帮助NHibernate在级联保存时能够正确保存而不报错)。

单向外键关联时,如果数据库允许CLASSROOM_ID为空,那么是可以打断Class和Classroom的关系的,而使得这两个对象独立存在,这一点是和主键关联所不一样的地方。

另外,这个配置还存在一个问题,就是对于一个存在的Classroom A,我接下来建立Class X,Class Y,都可以将这些 Class的班级指向A,同时这也是保存成功的。但是这显然是不对的,我们需要的是一对一,不是一对多。如果查询Classroom A的Class属性,那么就会报错,因为根本不知道应该是X还是Y。所以我们需要在CLASS表的CLASSROOM_ID上建立唯一约束,体现在Mapping上就是:

References(x => x.Classroom, "CLASSROOM_ID").Cascade.All().Unique();
这样我们在保存X和Y的时候,就只能保存成功一个,第二个保存时就会报错。

这其实又带来了另外一个问题,这可能是NHibernate没有考虑到的地方,那就是我们采用的是软删除,也就是说根本不会从数据库删除数据,只是把IS_DELETED置为1。 那么,我们如果先保存了A和X的关系,接下来由于X被取消,所以我删除了X,接下来添加Y与A关联就会失败。所以需要取消唯一约束,就可以保存Y了,但是在取A的Class属性时仍然会出现异常,取不出正确的Class Y,这个暂时无解。

3.双向外键关联

就是说CLASS表中有CLASSROOM_ID,然后在CLASSROOM表中也有CLASS_ID。这是非常不推荐的方式,一来导致数据维护重复,二来导致数据可能存在不一致。所以,这里我就不再累述这种方案的实现了。

示例代码下载:

http://files.cnblogs.com/studyzy/One2OneTest.7z

One to One 的数据库模型设计与NHibernate配置的更多相关文章

  1. 数据库模型设计PowerDesigner

    Power Designer 是Sybase公司的CASE工具集,使用它可以方便地对管理信息系统进行分析设计,他几乎包括了数据库模型设计的全过程.利用Power Designer可以制作数据流程图.概 ...

  2. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(15)-用户登录详细错误和权限数据库模型设计

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(15)-用户登录详细错误和权限数据库模型设计     ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)    ...

  3. NHibernate配置

    因为NHibernate被设计为可以在许多不同环境下工作,所以它有很多配置参数.幸运的是,大部分都已经有默认值了. NHibernate.Test.dll包含了一个示例的配置文件app.config, ...

  4. 通俗易懂的Nhibernate教程(2) ---- 配置之Nhibernate配置

    在上一个教程中,我们讲了Nhibernate的基本使用!So,让我们回顾下Nhibernate使用基本的步骤吧 1.NHibernate配置  ----- 这一步我们告诉了Nhibernate:数据库 ...

  5. SQL Server 2016的数据库范围内的配置

    SQL Server 2016真的让人眼前一亮.几天前微软就提供了RCO(候选发布版)版本的下载.我已经围观了一圈RCO版本,其中一个最拽的功能是数据库范围内的配置(Database Scoped C ...

  6. NHibernate系列文章五:NHibernate配置

    摘要 NHibernate有多种配置方法,代码,xml文件,以及Fluent NHibernate.这里只介绍最常用的两种NHibernate配置方法:通过代码和通过配置文件. 1. 通过代码配置 通 ...

  7. SSD Cloud Hosting–Linode-Mysql数据库的安装与配置

    接着上一篇的话题:SSD Cloud Hosting - Linode的配置和部署,搭建Java环境 8.Mysql数据库的安装与配置 安装 检查yum里边有没有mysql: yum list|gre ...

  8. paip.数据库发邮件通知配置

    paip.数据库发邮件通知配置 作者Attilax ,  EMAIL:1466519819@qq.com  来源:attilax的专栏 地址:http://blog.csdn.net/attilax ...

  9. mysql数据库的安装与配置

    mysql数据库的安装与配置及workbench的简单使用 mysql数据库社区版下载:https://dev.mysql.com/downloads/installer/ 我这里选的是社区安装版(适 ...

随机推荐

  1. jquery简单原则器(匹配偶数元素)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. jquery基本选择器(.class选择器)

    //通过class属性值匹配元素<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ht ...

  3. EC笔记,第一部分:1.视C++为一个语言联邦

    C++可看作由以下四种次语言组合而成1.C语言    高效,内置数据类型,数组,区块,语句,指针,预处理器,没有重载.异常.模板2.类    构造,析构,封装,继承,多态,迟绑定3.模板    模板元 ...

  4. Android存储空间不足的解决办法

    安装应用时,有时会出现错误Installation error: INSTALL_FAILED_INSUFFICIENT_STORAGE,这是存储空间不足的错误,这时就只能将应用安装到SD卡,在And ...

  5. java必看书籍

    Java核心技术 卷1 基础知识(第9版)Java核心技术 卷II 高级特性(第9版) 数据库系统概念(原书第6版)Oracle 从入门到精通MySQL数据库应用从入门到精通 Java并发编程实战(第 ...

  6. springmvc(3)拦截器HandlerInterceptor源码的简单解析

    其实拦截器就是我们的AOP编程.拦截器在我们的实际项目中实用性比较大的,比如:日志记录,权限过滤,身份验证,性能监控等等.下面就简单的来研究一下拦截器: public interface Handle ...

  7. [moka同学笔记]三、Yii2.0课程笔记(魏曦老师教程)关联字段增加搜索

    关联字段增加搜索 post表关联adminuser表,通过post.author_id  adminuser.id关联,在YII2.0生成搜索,关联字段搜索时,需要输入关联字段author的id才能搜 ...

  8. No.014:Longest Common Prefix

    问题: Write a function to find the longest common prefix string amongst an array of strings. 官方难度: Eas ...

  9. Spark集群 + Akka + Kafka + Scala 开发(3) : 开发一个Akka + Spark的应用

    前言 在Spark集群 + Akka + Kafka + Scala 开发(1) : 配置开发环境中,我们已经部署好了一个Spark的开发环境. 在Spark集群 + Akka + Kafka + S ...

  10. Java经典实例:纪元秒和本地日期时间互换

    Java版本:1.8开始 import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; /** ...