ihp - 一对零或一关系
问题描述
介绍
为了测试 IHP,我已将ASP.NET Core的Contoso 大学教程的一部分转换为 IHP。
本教程中的这一步显示了一个数据模型图。根据该页面,我将在这个问题中关注的部分涉及Instructor
并且OfficeAssignment
具有一对零或一的关系。
讲师
C# 中的模型Instructor
为:
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<Course> Courses { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
这导致在 sqlite 中的下表:
CREATE TABLE IF NOT EXISTS "Instructor" (
"ID" INTEGER NOT NULL CONSTRAINT "PK_Instructor" PRIMARY KEY AUTOINCREMENT,
"LastName" TEXT NOT NULL,
"FirstName" TEXT NOT NULL,
"HireDate" TEXT NOT NULL
);
所以在 IHP 中,我使用了以下内容:
CREATE TABLE instructors (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
last_name TEXT NOT NULL,
first_mid_name TEXT NOT NULL,
hire_date DATE NOT NULL
);
办公室分配
C# 中的模型OfficeAssignment
为:
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor Instructor { get; set; }
}
这导致在 sqlite 中的下表:
CREATE TABLE IF NOT EXISTS "OfficeAssignment" (
"InstructorID" INTEGER NOT NULL CONSTRAINT "PK_OfficeAssignment" PRIMARY KEY,
"Location" TEXT NULL,
CONSTRAINT "FK_OfficeAssignment_Instructor_InstructorID" FOREIGN KEY ("InstructorID") REFERENCES "Instructor" ("ID") ON DELETE CASCADE
);
所以在 IHP 中,我使用了以下内容:
CREATE TABLE office_assignments (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
instructor_id UUID NOT NULL,
"location" TEXT NOT NULL
);
CREATE INDEX office_assignments_instructor_id_index ON office_assignments (instructor_id);
ALTER TABLE office_assignments ADD CONSTRAINT office_assignments_ref_instructor_id FOREIGN KEY (instructor_id) REFERENCES instructors (id) ON DELETE NO ACTION;
列差异
请注意,表的 ASP.NET Core 版本OfficeAssignment
仅具有以下列:
InstructorID
Location
而 IHP 表有:
id
instructor_id
location
即它有一个id
专栏。我把它留在那里是因为它是由 IHP 模式编辑器默认添加的。
一对零或一关系
Instructor
给定in的生成代码Types.hs
:
data Instructor' officeAssignments = Instructor {
id :: (Id' "instructors"),
lastName :: Text,
firstMidName :: Text,
hireDate :: Data.Time.Calendar.Day,
officeAssignments :: officeAssignments,
meta :: MetaBag
} deriving (Eq, Show)
国际水文计划似乎在解释这样的事情:
- 一位讲师可以有许多办公室任务
(即字段officeAssignments
是复数。)
但是,根据 ASP.NET Core 教程中的图表,讲师可以有 0 或 1 个办公室作业。(即他们有没有办公室。)
由于 Instructor 上存在以下导航属性,Entity Framework Core 似乎得到了这样一个信号,即每位讲师最多应该有一个办公室任务:
public OfficeAssignment OfficeAssignment { get; set; }
更新:这已得到证实。请参阅下面标题为更新 1的部分。
所需语义 - 创建带办公室的讲师
在 C# 应用程序中,假设我创建一个Instructor
,指定一个办公室:
我们在 sqlite 中看到以下内容:
sqlite> SELECT * FROM Instructor; SELECT * FROM OfficeAssignment;
ID LastName FirstName HireDate
-- ----------- --------- -------------------
1 Fakhouri Fadi 2002-07-06 00:00:00
2 Harui Roger 1998-07-01 00:00:00
3 Kapoor Candace 2001-01-15 00:00:00
4 Zheng Roger 2004-02-12 00:00:00
5 Abercrombie Kim 1995-03-11 00:00:00
10 Curry Haskell 1920-01-01 00:00:00
InstructorID Location
------------ ------------
2 Gowan 27
3 Thompson 304
10 Haskell Room
所需语义 - 创建没有办公室的讲师
现在,在 C# 应用程序中,让我们创建一个讲师,而不是指定一个办公室:
我们在 sqlite 中看到以下内容:
sqlite> SELECT * FROM Instructor; SELECT * FROM OfficeAssignment;
ID LastName FirstName HireDate
-- ----------- --------- -------------------
1 Fakhouri Fadi 2002-07-06 00:00:00
2 Harui Roger 1998-07-01 00:00:00
3 Kapoor Candace 2001-01-15 00:00:00
4 Zheng Roger 2004-02-12 00:00:00
5 Abercrombie Kim 1995-03-11 00:00:00
10 Curry Haskell 1920-01-01 00:00:00
11 Church Alonzo 1940-01-01 00:00:00
InstructorID Location
------------ ------------
2 Gowan 27
3 Thompson 304
10 Haskell Room
11
有趣的是,如果我编辑一位讲师并将办公室留空:
我们在 sqlite 中看到以下内容:
sqlite> SELECT * FROM Instructor; SELECT * FROM OfficeAssignment;
ID LastName FirstName HireDate
-- ----------- --------- -------------------
1 Fakhouri Fadi 2002-07-06 00:00:00
2 Harui Roger 1998-07-01 00:00:00
3 Kapoor Candace 2001-01-15 00:00:00
4 Zheng Roger 2004-02-12 00:00:00
5 Abercrombie Kim 1995-03-11 00:00:00
10 Curry Haskell 1920-01-01 00:00:00
11 Church Alonzo 1940-01-01 00:00:00
InstructorID Location
------------ ------------
2 Gowan 27
3 Thompson 304
即被OfficeAssignment
删除。
这段代码实现了:
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
instructorToUpdate.OfficeAssignment = null;
问题
这是在 IHP 端进行设置以对 C# 应用程序建模的好方法吗?还是我应该在 IHP 方面进行更改以更紧密地模拟这种一对零或一的关系?
我查看了 IHP 手册的关系部分,但没有注意到任何关于这种一对零或一关系的内容。只是想确保在我冒险进入表单方面之前我已经正确设置了模型。
项目存储库
如果有帮助,具有上述内容的项目存储库位于:
https://github.com/dharmatech/ContosoUniversityIhp/tree/2021-09-04-02-queryOr-fix
(它非常混乱,因为它是用于实验的。)
笔记
我意识到这是一个复杂的问题,但我希望它可以作为未来在 IHP 中建立类似关系场景的人们的示例。
更新 1
Entity Framework Core 文档包含以下部分:
它提到:
一对一关系在双方都有一个参考导航属性。它们遵循与一对多关系相同的约定,但在外键属性上引入了唯一索引,以确保只有一个依赖项与每个主体相关。
所以这确实是我们在 C# 模型中看到的Instructor
和OfficeAssignment
。所以我想问题是,IHP 是否明确支持这种关系?如果没有,考虑到当前机制,什么是模拟它的好方法。
可能的模型Instructor
似乎为了Instructor
模拟他们可以有一个或零个办公室的事实,生成的模型应该有一个像这样的字段:
officeAssignment :: Maybe OfficeAssignment
如前所述,目前如下:
data Instructor' officeAssignments = Instructor {
id :: (Id' "instructors"),
lastName :: Text,
firstMidName :: Text,
hireDate :: Data.Time.Calendar.Day,
officeAssignments :: officeAssignments,
meta :: MetaBag
} deriving (Eq, Show)
更新 2
如果我们看一下office_assignments
IHP 方面的表格:
CREATE TABLE office_assignments (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
instructor_id UUID NOT NULL,
"location" TEXT NOT NULL
);
很明显,既然有一个id
列,我们可以office_assignment
为给定的instructor_id
.
但是,如果我们查看 C# 版本:
CREATE TABLE IF NOT EXISTS "OfficeAssignment" (
"InstructorID" INTEGER NOT NULL CONSTRAINT "PK_OfficeAssignment" PRIMARY KEY,
"Location" TEXT NULL,
CONSTRAINT "FK_OfficeAssignment_Instructor_InstructorID" FOREIGN KEY ("InstructorID") REFERENCES "Instructor" ("ID") ON DELETE CASCADE
);
我们注意到:
- 没有
id
列。 - 只有一
InstructorID
列。 InstructorID
是个PRIMARY KEY
- 因此,这似乎强化了这样一个事实,即
OfficeAssignments
任何给定的Instructor
.
因此,也许就像将 IHP 模式更改为一样简单:
CREATE TABLE office_assignments (
instructor_id UUID PRIMARY KEY NOT NULL,
"location" TEXT NOT NULL
);
CREATE INDEX office_assignments_instructor_id_index ON office_assignments (instructor_id);
ALTER TABLE office_assignments ADD CONSTRAINT office_assignments_ref_instructor_id FOREIGN KEY (instructor_id) REFERENCES instructors (id) ON DELETE NO ACTION;
结果
好的,使用我更新的模式编辑器office_assignments
,它现在看起来像这样:
CREATE TABLE office_assignments (
instructor_id UUID PRIMARY KEY NOT NULL,
"location" TEXT NOT NULL
);
这是编译期间的结果:
[ 4 of 23] Compiling Generated.Types ( build/Generated/Types.hs, interpreted )
build/Generated/Types.hs:133:71: error:
• Couldn't match type ‘"instructors"’ with ‘"office_assignments"’
arising from a use of ‘QueryBuilder.filterWhere’
• In the fifth argument of ‘Instructor’, namely
‘(QueryBuilder.filterWhere
(#instructorId, id) (QueryBuilder.query @OfficeAssignment))’
In the expression:
Instructor
id lastName firstMidName hireDate
(QueryBuilder.filterWhere
(#instructorId, id) (QueryBuilder.query @OfficeAssignment))
def {originalDatabaseRecord = Just (Data.Dynamic.toDyn theRecord)}
In an equation for ‘theRecord’:
theRecord
= Instructor
id lastName firstMidName hireDate
(QueryBuilder.filterWhere
(#instructorId, id) (QueryBuilder.query @OfficeAssignment))
def {originalDatabaseRecord = Just (Data.Dynamic.toDyn theRecord)}
|
133 | let theRecord = Instructor id lastName firstMidName hireDate (QueryBuilder.filterWhere (#instructorId, id) (QueryBuilder.query @OfficeAssignment)) def { originalDatabaseRecord = Just (Data.Dynamic.toDyn theRecord) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, three modules loaded.
build/Generated/Types.hs:234:20: error:
• Couldn't match type ‘OfficeAssignment' instructorId0
-> instructorId0’
with ‘Id' "office_assignments"’
arising from a use of ‘QueryBuilder.filterWhere’
• In the second argument of ‘(|>)’, namely
‘QueryBuilder.filterWhere (#instructorId, instructorId)’
In the expression:
builder |> QueryBuilder.filterWhere (#instructorId, instructorId)
In an equation for ‘QueryBuilder.filterWhereId’:
QueryBuilder.filterWhereId instructor_id builder
= builder |> QueryBuilder.filterWhere (#instructorId, instructorId)
|
234 | builder |> QueryBuilder.filterWhere (#instructorId, instructorId)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
解决方案
推荐阅读
- c++ - Visual Studio 2015 是否有数学库一致性选项
- reactjs - 什么/如果“render()”需要做“工作”(不是 100% 纯的?)
- ruby-on-rails - Xero 与 Rails/Devise Omniauthable 的集成
- javascript - 父组件的 CSS 必须使用 Angular 6 在子组件中实现
- javascript - 如何在没有 JavaScript 的情况下分离键盘选项卡旋转组?
- javascript - 如何在数据库基本初始化后让 MongoDB 消化一个 javascript 文件?
- if-statement - 如何将tableau中的ifnull语句转换为powerbi dax?
- python - python请求响应中的“原始”属性是什么意思?
- javascript - 无法理解 React setState 签名“setState(updater[, callback])”
- angular - 更新时未选择 Angular 8 Material 特定的垫子选项