Entity framework 实体框架代码首先使用Fluent API映射外键

Entity framework 实体框架代码首先使用Fluent API映射外键,entity-framework,ef-code-first,entity-framework-4.1,fluent-interface,Entity Framework,Ef Code First,Entity Framework 4.1,Fluent Interface,我的情况是,一个用户可以有几个地址。因此,我的用户类上有一个ICollection。但我也希望用户能够选择一个默认地址。因此,我做了以下工作: public class User { public int Id { get; set; } public int? DefaultAddressId { get; set; } [ForeignKey("DefaultAddressId")] public virtual Address DefaultAddress

我的情况是,一个用户可以有几个地址。因此,我的用户类上有一个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
}

如果要删除DefaultAddress属性,则不需要映射它。您可以将属性放在那里,如果DefaultAddressId在用户表中,EF应该知道如何映射它。我会亲自将外键关系从
用户
移动到
地址
,并在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将知道它需要一个
外键
地址
用户
之间的关系


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

DefaultAddressId
不需要任何特定的映射,因为它只是
user
表中的一列,与
address
表没有任何关系(FK)。将不会创建任何关系,因为两侧都不存在导航属性。此外,它应该是一对一的关系,这将不起作用,因为EF不支持唯一键


我喜欢@Sergi Papaseit提供的解决方案,您在问题中的原始模型应该可以工作。您可以非常轻松地测试它:

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

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

EF如何知道如何映射它?它仍然可以为空吗?因为如果用户尚未添加地址,则该地址应为null,并且在将用户添加到数据库时不应导致错误……是的,它仍然可以为null。我发现使用fluentapi映射1对1关系存在一些问题,因为它似乎没有办法将键映射到特定列。在我的例子中,我在映射中使用了1对多,即使对象只包含子对象的一个实例。这是使用HasOptional(u=>u.DefaultAddress)完成的;
地址
类是什么样子的?它是否引用了单个
用户
,因此每个地址都只属于一个用户?或者用户可以共享一个地址,在这种情况下,我会期望多对多关系?@Slauma:我刚刚更新了这个问题。请检查一下。为什么您在使用
公共虚拟地址DefaultAddress{get;set;}
时遇到问题?在我看来,这个属性是表达你想要拥有的关系的最好方式——我认为把它映射到DB没有问题。如果删除属性(并且Address类中没有
User
属性),
DefaultAddressId
基本上成为一个标量属性,它根本不参与任何关系。因此,您将丢失数据库中的外键约束检查(每个数字都可以在此属性中,无论是否存在具有此ID的地址)。@Slauma:我不确定为什么会出现此问题。它在升级到EF 4.1之前运行良好,此外还有其他我尚未解决的问题。我已经测试了您的模型,正如上面在您的问题中所述,它对我来说很好。这个模型实际上正是我选择使用地址集合和DefaultAddress的解决方案。我相信,你的并发症在别的地方。@Sergi Papaseit:+1回答。我不知道为什么我喜欢把事情复杂化。我从一开始就应该这么做。我想我是痴迷于数据库的“规范化”。我想,为了简单起见,有时必须有一些妥协。再次感谢:)@Kassem-记住这一点总是好的;)很乐意帮忙:)老实说,我同意这个问题的答案。我不知道为什么它不应该工作(我刚刚测试了它,它很好。它创建了两个关系。)对于您的解决方案,您必须在业务逻辑中确保
IsDefaultAddress
没有在一个用户的多个地址上设置。如果您更改了DefaultAddress,则必须搜索旧地址并重置旧标志。在我看来,问题中的解决方案更好地表达了模型的“自然”关系,人们不必担心重复的默认标志。@Slauma-我必须说,你的观点是正确的。我最初的直觉是按照我的建议去做,但在
User
类中使用
DefaultAddress
属性确实可以清楚地表达这种关系。我想这完全取决于你想表达/实现什么。尽管如此,我还是会在
Address
类中添加对
User
的引用。@Slauma:这就是我为什么选择这个问题的解决方案的原因。我不想担心Sergi的解决方案可能会导致重复的默认地址。但是,我不想,我想让事情尽可能简单,以便跟踪是什么导致了我的模型中的问题。但是很自然,如果我在Address类中添加一个对用户的引用,它应该可以正常工作吗?是的,我想我会同意Sergi的答案。但是您是否建议我将导航属性从Address类添加回User?按照@Sergi的方法,您只需要
用户
中的
地址
集合。您还可以使用
用户
类中
地址
上的
[Required]
属性根据需要标记关系(EF还将添加一个On-Delete级联)。但我不确定这在集合上如何工作。@Sergi Papaseit:是的,我也尝试过这样做来放置
[必需]<
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;
                }
            }
        }
    }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
                .HasMany(u => u.Addresses)
                .WithRequired();
}