EF 4.1:使用组合键和共享列映射多对多关系?

发布于 2024-12-28 08:42:27 字数 2831 浏览 2 评论 0原文

我正在使用 EF 4.1 Code First 并尝试对以下内容进行建模。

public class Apple
{
    public int Id {get;set;}
}

public class Banana
{
    public int AppleId {get;set;}
    public int Id {get;set;}
    public virtual ICollection<Coconut> Coconuts {get;set;}
}

public class Coconuts
{
    public int AppleId {get;set;}
    public int Id {get;set;}
    public virtual ICollection<Banana> Bananas {get;set;}
}

数据库看起来像这样。 Fruit

这是一个不遵循 EF 约定的现有架构,因此我使用 Fluent API 来映射实体到数据库。映射看起来像这样。

public class BananaMapping : EntityTypeConfiguration<Banana>
{
    public BananaMapping()
    {
        HasKey(e => new { e.AppleId, e.Id })
            .ToTable("Bananas", "fruit");
        Property(e => e.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        HasMany(e => e.Coconuts)
            .WithMany(s => s.Bananas)
            .Map(l =>
                     {
                         l.ToTable("BananaCoconuts", "fruit");
                         l.MapLeftKey("AppleId", "BananaId");
                         l.MapRightKey("AppleId", "CoconutId");
                     });
    }

(苹果和椰子也被映射,但为简洁起见,此处省略) 如果我这样保留它,它将由于共享列而生成 MetadataException。 “指定的架构无效。错误:(110,6):错误 0019:类型中的每个属性名称必须是唯一的。属性名称“AppleId”已定义。”

要解决此问题,我在 BananaCoconuts 上创建了一个计算列,它仅公开 AppleId 的不同名称副本,并将其命名为 BananaAppleId。我(显然)保留了 FK,并将映射更改为如下所示...

HasMany(e => e.Coconuts)
    .WithMany(s => s.Bananas)
    .Map(l =>
        {
            l.ToTable("BananaCoconuts", "fruit");
            l.MapLeftKey("BananaAppleId", "BananaId");
            l.MapRightKey("AppleId", "CoconutId");
        }
    );

虽然有点臭,而且绝对是黑客技术,但它确实让我克服了 MetadataException,直到我尝试从代码添加新链接。

var banana = dataContext.FindBanana(appleId, bananaId);
var coconut = dataContext.FindCoconut(appleId, coconutId);
banana.Coconuts.Add(coconut);
dataContext.SaveChanges();

保存更改会引发 DbUpdateException

An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.

和内部(实际上是几个异常)......

{"The column \"BananaAppleId\" cannot be modified because it is either a computed column or is the result of a UNION operator."}

现在我没有主意了。 :) 数据库模式准确地模拟了我们需要的内容(计算列除外)。处理这个问题的最佳方法是什么?我并不热衷于将 BananaAppleId 设为“真实”列、更改外键以及重复存储数据,这些数据不应该但可能会不同步。

I'm using EF 4.1 Code First and trying to model the following.

public class Apple
{
    public int Id {get;set;}
}

public class Banana
{
    public int AppleId {get;set;}
    public int Id {get;set;}
    public virtual ICollection<Coconut> Coconuts {get;set;}
}

public class Coconuts
{
    public int AppleId {get;set;}
    public int Id {get;set;}
    public virtual ICollection<Banana> Bananas {get;set;}
}

The database looks like this.
Fruit

This is an existing schema that doesn't follow the EF conventions, so I'm using the Fluent API to map the entities to the database. The mapping looks like this.

public class BananaMapping : EntityTypeConfiguration<Banana>
{
    public BananaMapping()
    {
        HasKey(e => new { e.AppleId, e.Id })
            .ToTable("Bananas", "fruit");
        Property(e => e.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        HasMany(e => e.Coconuts)
            .WithMany(s => s.Bananas)
            .Map(l =>
                     {
                         l.ToTable("BananaCoconuts", "fruit");
                         l.MapLeftKey("AppleId", "BananaId");
                         l.MapRightKey("AppleId", "CoconutId");
                     });
    }

(Apple and Coconut are mapped too, but omitted here for brevity)
If I leave it like that, it will generate a MetadataException because of the shared column.
"Schema specified is not valid. Errors: (110,6) : error 0019: Each property name in a type must be unique. Property name 'AppleId' was already defined."

To get past this, I created a computed column on BananaCoconuts that simply exposes a differently named copy of AppleId and called it BananaAppleId. I left the FKs alone (obviously) and changed the mapping to look like this...

HasMany(e => e.Coconuts)
    .WithMany(s => s.Bananas)
    .Map(l =>
        {
            l.ToTable("BananaCoconuts", "fruit");
            l.MapLeftKey("BananaAppleId", "BananaId");
            l.MapRightKey("AppleId", "CoconutId");
        }
    );

While somewhat smelly and definitely hacktastic, it did get me past the MetadataException, until I tried to add a new link from code.

var banana = dataContext.FindBanana(appleId, bananaId);
var coconut = dataContext.FindCoconut(appleId, coconutId);
banana.Coconuts.Add(coconut);
dataContext.SaveChanges();

Save changes throws a DbUpdateException

An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.

And the inner (couple exceptions down, actually)...

{"The column \"BananaAppleId\" cannot be modified because it is either a computed column or is the result of a UNION operator."}

And now I'm out of ideas. :) The database schema accurately models what we need (with the exception of that computed column). What's the best way to handle this? I'm not crazy about making BananaAppleId a "real" column, changing the foreign keys, and having duplicate stored data that shouldn't but could get out of sync.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

怪异←思 2025-01-04 08:42:27

连接表需要有 4 列作为主键,每一列都是主表的外键。无法将列设置为计算值,因为 EF 将通过发送所有 4 个列值来插入记录。因此,在您的情况下

HasMany(e => e.Coconuts)
    .WithMany(s => s.Bananas)
    .Map(l =>
        {
            l.ToTable("BananaCoconuts", "fruit");
            l.MapLeftKey("BananaAppleId", "BananaId");
            l.MapRightKey("CoconutAppleId", "CoconutId");
        }
    );

,否则您必须将连接表映射为单独的实体。

The join table needs to have 4 columns as primary key and each one is a foreign key to the main table. A column can not be set as computed because EF will insert records by sending all 4 column values. So in your case

HasMany(e => e.Coconuts)
    .WithMany(s => s.Bananas)
    .Map(l =>
        {
            l.ToTable("BananaCoconuts", "fruit");
            l.MapLeftKey("BananaAppleId", "BananaId");
            l.MapRightKey("CoconutAppleId", "CoconutId");
        }
    );

Otherwise you have to map the join table as a separate entity.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文