【问题标题】:Entity Framework Code First Mapping Foreign Key Using Fluent API使用 Fluent API 的实体框架代码优先映射外键
【发布时间】:2011-03-30 17:02:50
【问题描述】:

我有一个用户可以有多个地址的情况。因此,我的用户类上有一个 ICollection。但我也希望用户能够选择默认地址。所以我做了以下事情:

public class User 
{
    public int Id { get; set; }
    public int? DefaultAddressId { get; set; }
    [ForeignKey("DefaultAddressId")]
    public virtual Address DefaultAddress { get; set; }
    public virtual ICollection<Address> Addresses { get; set; }
    //properties were removed for purpose of this post
}

我想完全删除 public virtual Address DefaultAddress { get; set; },保留 DefaultAddressId 并使用 Fluent API 映射它,因为当前设置会造成很多麻烦(在这个类和我有类似设置的其他类中)。那么这可以使用fluent api来完成吗?

更新: address 类目前没有对 User 类的任何引用,它是一种单向关系。但是,是的,一个地址只属于一个用户,它不是多对多的关系。这是地址类:

public class Address
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Details { get; set; }
    public virtual Area Area { get; set; }
}

【问题讨论】:

  • Address 类的外观如何?它是否引用了单个User,以便每个地址都唯一地属于一个用户?或者用户可以共享一个地址,在这种情况下我期望一个多对多的关系?
  • @Slauma:我刚刚更新了这个问题。请检查一下。
  • 为什么public virtual Address DefaultAddress { get; set; } 有问题?在我看来,这个属性是表达你想要的关系的最佳方式——我看不出将它映射到数据库的问题。如果您删除该属性(并且在 Address 类中没有 User 属性),DefaultAddressId 基本上就变成了一个根本不参与关系的标量属性。因此,您将丢失数据库中的外键约束检查(每个数字都可以在此属性中,无论是否存在具有此 ID 的地址)。
  • @Slauma:我不确定为什么会有问题。除了我尚未解决的其他问题外,它在升级到 EF 4.1 之前运行良好。 refer to this question
  • 我已经在您的问题中测试了您的模型,它对我来说效果很好。这个模型实际上正是我选择使用地址集合和默认地址的解决方案。我相信,您的并发症在其他地方。

标签: entity-framework ef-code-first entity-framework-4.1 fluent-interface


【解决方案1】:

我会亲自将外键关系从User 移动到Address,并在地址类上添加IsDefaultAddress 属性。

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

    // This property marks the FK relation
    public virtual User User { get; set; }

    public string Name { get; set; }
    public string Details { get; set; }
    public virtual Area Area { get; set; }

    // This property signals whether this is the user's default address
    public bool IsDefaultAddress { get; set; }
}

EF 会知道它需要 AddressUser 之间的 Foreign Key 关系。

这将大大简化您的模型。也就是说,当然,如果一个地址只能属于一个用户(如 cmets 中的 Slauma 所要求的)。

【讨论】:

  • @Sergi Papaseit:答案+1。我不知道为什么我喜欢复杂的事情。我应该从一开始就这样做。我想我对数据库的“规范化”很着迷。我想有时为了简单起见必须做出一些妥协。再次感谢:)
  • @Kassem - 牢记K.I.S.SYAGNI 总是好的 ;) 乐于提供帮助:)
  • 老实说,我会选择问题中的解决方案。我不知道为什么它不应该工作(我刚刚测试过它,它很好。它创建了 2 个关系。)使用您的解决方案,必须确保在业务逻辑中 IsDefaultAddress 的设置不超过一个用户的地址。如果您更改 DefaultAddress,则必须搜索旧地址并重置旧标志。在我看来,问题中的解决方案更好地表达了模型的“自然”关系,并且不需要担心重复的默认标志。
  • @Slauma - 我必须说,你确实有一个好点子。我最初的直觉是按照我的建议去做,但在User 类中拥有DefaultAddress 属性确实可以清楚地表达这种关系。我想这一切都取决于你想要表达/实现什么。不过,我仍然会在 Address 类中添加对 User 的引用。
  • @Slauma:这实际上就是我在问题中采用解决方案的原因。我不想担心可能由 Sergi 的解决方案引起的重复默认地址。但是,我不知道,我想让事情尽可能简单,以便找出导致我的模型出现问题的原因。但是很自然,如果我在 Address 类中添加对 User 的引用,它应该可以正常工作吗?
【解决方案2】:

您在问题中的原始模型应该可以工作。你可以很容易地测试它:

  • 创建新的控制台应用程序 (VS 2010)
  • 将其命名为“EFTestApp”
  • 添加对“EntityFramework.dll”的引用
  • 删除Program.cs的内容并将以下代码复制到文件中

程序.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace EFTestApp
{
    public class User
    {
        public int Id { get; set; }
        public int? DefaultAddressId { get; set; }
        [ForeignKey("DefaultAddressId")]
        public virtual Address DefaultAddress { get; set; }
        public virtual ICollection<Address> Addresses { get; set; }
    }

    public class Address
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Context : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Address> Addresses { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new Context())
            {
                try
                {
                    User user = new User() { Addresses = new List<Address>() };

                    Address address1 = new Address() { Name = "Address1" };
                    Address address2 = new Address() { Name = "Address2" };

                    user.Addresses.Add(address1);
                    user.Addresses.Add(address2);

                    context.Users.Add(user);

                    context.SaveChanges();
                    // user has now 2 addresses in the DB and no DefaultAddress

                    user.DefaultAddress = address1;
                    context.SaveChanges();
                    // user has now address1 as DefaultAddress

                    user.DefaultAddress = address2;
                    context.SaveChanges();
                    // user has now address2 as DefaultAddress

                    user.DefaultAddress = null;
                    context.SaveChanges();
                    // user has now no DefaultAddress again
                }
                catch (Exception e)
                {
                    throw;
                }
            }
        }
    }
}

在 SQL Server Express 中,它会创建一个名为“EFTestApp.Context”的新数据库。您可以在上面的每个 SaveChanges 上设置断点,跨步查看数据库中的更改。

如果您查看数据库中的关系,则有两个,并且在数据库中的表Addresses 中是一个外键列User_Id

我认为您也可以删除 public int? DefaultAddressId { get; set; }[ForeignKey("DefaultAddressId")]。它使用可选的 DefaultAddress 创建相同的数据库表和关系。

也许您需要Address -&gt; User 的关系(如果没有用户,地址不能单独存在于数据库中)。然后你可以将它添加到 Context 类中:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
                .HasMany(u => u.Addresses)
                .WithRequired();
}

它使地址表中的User_Id 不可为空,并默认设置级联删除。因此,当用户被删除时,其所有地址也会被删除。

【讨论】:

  • 您也可以在User 类中使用Addresses 上的[Required] 属性将关系标记为必需(EF 还将添加一个 On Delete 级联)。不过,我不确定这对收藏有什么作用。
  • @Sergi Papaseit:是的,我也尝试过将[Required] 属性放在Addresses 集合上,但它不起作用。没有编译器错误或 EF 异常,外键只是保持可为空,所以我们仍然有一个可选的关系。我想我们需要在Address 类上添加一个User 属性,然后将此属性标记为[Required],如果我们希望仅使用DataAnnotations 而没有Fluent API。
  • 我也在用这个。但是想知道在设置默认值之前是否有办法避免中间保存更改?
  • @ravi:您是否测试过它以删除第一个SaveChanges?它应该工作,不是吗?中间SaveChanges 仅用于调试和演示目的,以显示数据库中的更改。
  • 是的。我试过了,它不起作用。似乎主键是一个增量标识符,而不是不知道默认的 id 是什么而不先保存。我认为如果我们使用 guid 作为 pk 会起作用。
【解决方案3】:

DefaultAddressId 不需要任何特定映射,因为它只是User 表中的列,与Address 表没有任何关系(FK)。不会创建任何关系,因为导航属性在任何一侧都不存在。它也应该是一对一的关系,因为 EF 不支持唯一键。

我喜欢@Sergi Papaseit 提供的解决方案

【讨论】:

  • 是的,我想我会接受 Sergi 的回答。但是您是否建议我应该从 Address 类中向 User 添加一个导航属性?
  • 按照@Sergi 的方法,您只需在User 中收集Addresses
【解决方案4】:

如果要删除 DefaultAddress 属性,则不需要映射它。您可以在那里拥有该属性,并且 EF 应该知道如何映射它,前提是 DefaultAddressId 在 User 表中

【讨论】:

  • EF 应该如何知道如何映射它?它仍然可以为空吗?因为如果用户还没有添加地址,这应该为空,并且在将用户添加到数据库时不应该导致错误...
  • 是的,它仍然可以为空。我发现使用 fluent API 映射 1 对 1 关系存在一些问题,因为它似乎没有办法将键映射到特定列。就我而言,即使对象仅包含 1 个子实例,我也在映射中使用了 1 对多。这是使用 HasOptional(u => u.DefaultAddress).WithMany().HasKey(u => u.DefaultAddressId);
猜你喜欢
  • 2014-03-15
  • 2011-08-08
  • 2012-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多