实体框架核心在OnModelCreating中分配外键约束吗?

时间:2018-07-12 16:59:32

标签: c# .net-core entity-framework-core ef-fluent-api

我试图弄清楚如何使用Entity Framework Core以编程方式生成数据库,并且遇到将外键分配给表中的字段的问题。当我从数据库中检索记录时,我需要地址字段来引用该地址对象并加载它。如果我使用EnsureCreated创建数据库,则它会正确创建表,除了AddressId字段不是Address表的外键。我尝试对此进行研究,并遇到了this article,它使用了一种称为HasForeignKey的方法,但是从Entity()返回的任何信息都不知道HasForeignKey。如果尝试手动输入该错误,则会收到此错误:

  

错误CS1061'EntityTypeBuilder'不包含定义   用于“ HasForeignKey”,并且不接受扩展方法“ HasForeignKey”   可以找到类型为“ EntityTypeBuilder”的第一个参数   (您是否缺少using指令或程序集引用?)

很明显,这种方法是无效的。我尝试遵循this documentation,但我不太了解它在做什么以及如何将其应用于我的情况。在他们的情况下,他们有几个表,博客和帖子,其中博客有很多帖子,而帖子有一个博客。我尽力做到了,但是我并不太了解所有的术语以及它的作用。

如何简单地将AddressId中的值作为外键分配给Addresses中的记录?这是我正在使用的代码。

public class Person
    {
        [Key]
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public byte Age { get; set; }
        public int AddressId { get; set; }
        public Address Address { get; set; }
    }

    public class Address
    {
        [Key]
        public int Id { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
    }

    public class MyContext: DbContext
    {
        public DbSet<Person> People { get; set; }
        public DbSet<Address> Addresses { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite($"Data Source=test.db");
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<Address>().HasKey(v => v.Id);
            builder.Entity<Person>().HasKey(v => v.Id);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new MyContext())
            {
                //var person = db.People.Find(1);

                db.Database.EnsureCreated();

                var person = new Person()
                {
                    FirstName = "Jack",
                    LastName = "Jackson",
                    Age = 50,
                    Address = new Address()
                    {
                        Street = "123 Street St",
                        City = "Jacksonville",
                        State = "Mississippi",
                        ZipCode = "00000-0000"
                    }
                };

                db.People.Add(person);

                db.SaveChanges();
            }
        }
    }

一些额外的信息:

  • 我正在使用.Net Core 2.0
  • 我的数据库是SQLite
  • 我正在为提供者使用Microsoft.EntityFrameworkCore.SQLite和SQLite.Core库。

3 个答案:

答案 0 :(得分:0)

在您的OnModelCreating方法中,尝试更新以下行以使其看起来像这样(我无法测试此代码,因此我的格式可能已关闭):

builder.Entity<Person>().HasOne(v => v.Address).HasForeignKey(v => v.AddressId);

我相信,由于在两个实体中都具有[Key] Data注释,因此您不需要通过Fluent API设置密钥。警告-我也是EF Core的新手,所以我可能是错的。 :)

答案 1 :(得分:0)

我知道已经晚了,但我希望有人可以从这个答案中受益。我遇到了同样的问题,结果我忘了添加所需的nuget包。安装Microsoft.EntityFrameworkCore.Relational,一切都会好起来。

答案 2 :(得分:0)

尽管这个问题已经过时了,但由于它指出的文档帮助我找出了我自己遇到的另一个问题,我会在此处发布解决方案。

有两种方法可以解决这个问题,一种是使用 ForeignKeyAttribute 注释,另一种是像您使用的那样使用 FluentAPI。可以设置Person有一个或多个地址,对应的方法依此变化。

我还建议在 Address 类中创建一个 Person 属性,这样您也可以反过来使用它们。

第一个是在 Model 类本身中设置的:

public class Person
{
    [Key]
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // The byte type is interpreted by EF as a boolean or BIT on SQL Server/Sqlite,
    // so I took the liberty of changing it to integer
    public int Age { get; set; }
    public int AddressId { get; set; }

    [ForeignKey("AddressId")]
    public virtual Address Address { get; set; }
    // Set it to be a Collection of any type for a Person to be able to have multiple Addresses
    // public ICollection<Address> Addresses { get; set; }
}

public class Address
{
    [Key]
    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
    public int PersonId { get; set; }    

    [ForeignKey("PersonId")]
    public virtual Person Person { get; set; }
}

另外,如果您想调整约束名称、删除行为或其他额外设置,您可以删除 ForeignKey 属性并使用 Fluent API 来代替:

public class MyContext: DbContext
{
    public DbSet<Person> People { get; set; }
    public DbSet<Address> Addresses { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite($"Data Source=test.db");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>(person =>
        {
            string tableName = "Persons";
            person.ToTable(tableName);
            person.HasKey(p => p.Id).HasConstraintName($"PK_{tableName}_Id");

            // In case you want to set your Id column to have an Identity/Autoincrement
            person.Property(p => p.Id).UseIdentityColumn();
            person.Property(p => p.FirstName).HasMaxLength(100);
            person.Property(p => p.LastName).HasMaxLength(250);

            // Replace HasOne by HasMany in case you want to make a Person
            // to be able to have multiple Addresses
            person.HasOne(p => p.Address).WithOne(a => a.Person).HasForeignKey("IdAddress").HasConstraintName($"FK_{tableName}_IdAddress").OnDelete(DeleteBehavior.Cascade);
        });

        modelBuilder.Entity<Address>(address => {
            string tableName = "Addresses";
            address.ToTable(tableName);
            address.HasKey(a => a.Id).HasConstraintName($"PK_{tableName}_Id");

            // In case you want to set your Id column to have an Identity/Autoincrement
            address.Property(a => a.Id).UseIdentityColumn();
            address.Property(a => a.Street).HasMaxLength(50);
            address.Property(a => a.City).HasMaxLength(30);
            address.Property(a => a.State).HasMaxLength(30);
            address.Property(a => a.ZipCode).HasMaxLength(10);

            // Replace WithOne by WithMany in case you want to make a Person
            // to be able to have multiple addresses
            address.HasOne(a => a.Person).WithOne(p => p.Address).OnDelete(DeleteBehavior.NoAction);
        });
    }
}