如何创建虚拟列表以实现"many side"上没有外键的一对多引用



我使用的是带有SQLite3的实体框架核心5.0.0(也安装了实体框架6.4.4(。NET核心应用程序,并有这两个表:

CREATE TABLE string 
(
id     INTEGER NOT NULL,
locale TEXT    NOT NULL,
text   TEXT    NOT NULL,
CONSTRAINT PK_string PRIMARY KEY (id, locale)
);
CREATE TABLE elem 
(
id      INTEGER NOT NULL PRIMARY KEY,
name    TEXT    NOT NULL,
titleId INTEGER NOT NULL,
briefId INTEGER NOT NULL
);

这个方案是正确和有效的,但在实体框架的上下文中,这并没有完全实现EF的便利性:因此,在elem表中,我只有一个整数字段,而没有List<string>

一方面,我在这里有一个多对多的关系,但不正确的多对多,因为没有中间表。这也不是一对多,因为stringelem没有任何关系(因为不仅elem引用string(。

即使这里的多对多也不是最方便的,因为我必须创建几个中间表。。。

我希望类中有一个List<string>,而不仅仅是一个整数字段。

有没有什么方法可以在EF中做到这一点而不改变模式(或者只做最小的改变,而不是像改变来完成多对多关系(?

所提出的数据库模型缺少一个重要的部分(从关系的角度来看(-唯一的主体实体,表示string.idelem.titleIdelem.BriefId等的多对一关系的一侧。

如果没有该部分,数据库模型就不能使用/强制执行外键关系;"方便";EF关系映射。

因此,所需的最小修改是引入该实体/表,例如stringTable:

CREATE TABLE stringTable 
(
id      INTEGER NOT NULL PRIMARY KEY
);

对于现有数据库,应使用string表中不同的id值填充该数据库。

现在您可以介绍FK关系:

CREATE TABLE string 
(
id     INTEGER NOT NULL CONSTRAINT FOREIGN KEY REFERENCES stringTable(id) ON DELETE CASCADE,
locale TEXT    NOT NULL,
text   TEXT    NOT NULL,
CONSTRAINT PK_string PRIMARY KEY (id, locale)
);
CREATE TABLE elem 
(
id      INTEGER NOT NULL PRIMARY KEY,
name    TEXT    NOT NULL,
titleId INTEGER NOT NULL CONSTRAINT FOREIGN KEY REFERENCES stringTable(id),
briefId INTEGER NOT NULL CONSTRAINT FOREIGN KEY REFERENCES stringTable(id)
);

相应的EF实体模型是这样的(实体类型和属性名称是任意的(:

[Table("stringTable")]
public class StringTable
{
[Column("id"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
// Navigation properties
public virtual ICollection<StringEntry> Entries { get; set; }
}
[Table("string")]
public class StringEntry
{
[Column("id"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Column("locale"), Required]
public string Locale { get; set; }
[Column("text"), Required]
public string Text { get; set; }
// Navigation properties
public virtual StringTable Table { get; set; }
}
[Table("elem")]
public class Element
{
[Column("id"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Column("name"), Required]
public string Name { get; set; }
[Column("titleId")]
public int TitleId { get; set; }
[Column("briefId")]
public int BriefId { get; set; }
// Navigation properties
public virtual StringTable Title { get; set; }
public virtual StringTable Brief { get; set; }
}

具有复合PK和关系映射:

modelBuilder.Entity<StringEntry>()
.HasKey(e => new { e.Id, e.Locale });
modelBuilder.Entity<StringEntry>()
.HasRequired(e => e.Table)
.WithMany(e => e.Entries)
.HasForeignKey(e => e.Id)
.WillCascadeOnDelete();
modelBuilder.Entity<Element>()
.HasRequired(e => e.Title)
.WithMany()
.HasForeignKey(e => e.TitleId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Element>()
.HasRequired(e => e.Brief)
.WithMany()
.HasForeignKey(e => e.BriefId)
.WillCascadeOnDelete(false);

在所有这些就绪的情况下,您可以访问Element elem的相关字符串,如下所示:

elem.Title.Entries
elem.Brief.Entries

项目/提取相关文本如下:

TitleTexts = elem.Title.Entries.Select(e => e.Text)
BriefTexts = elem.Brief.Entries.Select(e => e.Text)

投影/提取特定string locale:的文本

TitleText = elem.Title.Entries.Where(e => e.Locale == locale).Select(e => e.Text).FirstOrDefault()
BriefText = elem.Brief.Entries.Where(e => e.Locale == locale).Select(e => e.Text).FirstOrDefault()

等等。

更新:对于EF Core,所需的实体模型/数据注释与上述完全相同,只是fluent配置必须使用EF Core等效项(所有这些都将转到DbContext派生类的OnModelCreating方法覆盖(:

modelBuilder.Entity<StringEntry>()
.HasKey(e => new { e.Id, e.Locale });
modelBuilder.Entity<StringEntry>()
.HasOne(e => e.Table)
.WithMany(e => e.Entries)
.HasForeignKey(e => e.Id)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Element>()
.HasOne(e => e.Title)
.WithMany()
.HasForeignKey(e => e.TitleId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Element>()
.HasOne(e => e.Brief)
.WithMany()
.HasForeignKey(e => e.BriefId)
.OnDelete(DeleteBehavior.Restrict);

除非;字符串";表有一个指向元素的ElementId,元素将不包含"的列表;字符串";实体。

从你的模式来看,我想尝试一下,你的title Id和可能的briefId是对";字符串";本地化标题和简短文本的表格?(使用"字符串"作为表/实体名称会导致很多混乱(

如果是这样的话,你会得到这样的东西:

[Table("elem")]
public class Element
{
// ...
public int TitleId {get; set;}
[ForeignKey("TitleId")]
public virtual StringEntity Title { get; set; } 
public int BriefId {get; set;}
[ForeignKey("BriefId")]
public virtual StringEntity Brief { get; set; } 
}
[Table("string")]
public class StringEntity
{ // ...
}

然后获取标题的本地化资源:element.Title.Text

更新:好的,字符串资源表使用一个复合键作为ID和区域设置。由于联接表没有区域设置ID,因此EF无法直接映射这两个元素。您可以通过Joins和投影到视图模型中来查询这些表中的信息。。。

[Table("string")]
public class StringEntry
{
[Column("id"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Column("locale"), Required]
public string Locale { get; set; }
[Column("text"), Required]
public string Text { get; set; }
// Navigation properties
public virtual StringTable Table { get; set; }
}
[Table("elem")]
public class Element
{
[Column("id"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Column("name"), Required]
public string Name { get; set; }
[Column("titleId")]
public int TitleId { get; set; }
[Column("briefId")]
public int BriefId { get; set; }
}

给定一个视图模型,如:

[Serializable]
public class ElementViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Brief { get; set; }
}

若要查询本地化数据。。。

int localeId = GetCurrentLocaleId(); // Some method/code to get the current locale.
var elements = context.Elements
.Join(context.Strings, 
x => new { Id = x.TitleId, Locale = localeId }, 
x => new { x.Id, x.LocaleId }, 
(e, s) => new { Element = e, Title = s })
.Join(context.Strings, 
x => new { Id = x.Element.BriefId, LocaleId = localeId }, 
x => new { x.Id, x.LocaleId }, 
(e, s) => new { Element = e.Element, Title = e.Title, Brief = s })
.Select(x => new ElementViewModel
{ 
Id = x.Element.Id, 
Name = x.Element.Name,
Title = x.Title.Text,
Brief = x.Brief.Text 
}).ToList();

不用说,如果像这样使用大量本地化字符串,查询将变得相当复杂和繁琐。

最新更新