【问题标题】:Order Creation of DbSet in DbContext in EF 6.1EF 6.1 中 DbContext 中 DbSet 的顺序创建
【发布时间】:2016-05-26 06:07:16
【问题描述】:

我需要在 DbContext 中命令创建 DbSet。因为:

顺便说一句,DbSets已经在DbContext中排序了,但是创建迁移时似乎没有效果。

在我的 DbContext 中:

public class CmsDbContext : DbContext
{
    // Basic
    public DbSet<Country> Countries { get; set; }
    public DbSet<District> Districts { get; set; }
    public DbSet<Province> Provinces { get; set; }
    public DbSet<City> Cities { get; set; }
    public DbSet<Area> Areas { get; set; }
    public DbSet<CustomerType> CustomerTypes { get; set; }
    public DbSet<IdType> IdTypes { get; set; }
    public DbSet<IncomeType> IncomeTypes { get; set; }
    public DbSet<OutcomeType> OutcomeTypes { get; set; }
    public DbSet<PaymentType> PaymentTypes { get; set; }
}

如您所见,国家是第一个。 发行后

add-migration initial

DbSet 的顺序不是我想要的:

public partial class initial : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.Area",
            c => new
                {
                    AreaId = c.Int(nullable: false, identity: true),
                    CityId = c.Int(nullable: false),
                    AreaArabicName = c.String(nullable: false, maxLength: 35, unicode: false),
                    AreaEnglishName = c.String(nullable: false, maxLength: 35, unicode: false),
                    AddedDate = c.DateTime(),
                    ModifiedDate = c.DateTime(),
                })
            .PrimaryKey(t => t.AreaId)
            .ForeignKey("dbo.City", t => t.CityId)
            .Index(t => t.CityId);

        CreateTable(
            "dbo.City",
            c => new
                {
                    CityId = c.Int(nullable: false, identity: true),
                    ProvinceId = c.Int(nullable: false),
                    CityArabicName = c.String(nullable: false, maxLength: 35, unicode: false),
                    CityEnglishName = c.String(nullable: false, maxLength: 35, unicode: false),
                    AddedDate = c.DateTime(),
                    ModifiedDate = c.DateTime(),
                })
            .PrimaryKey(t => t.CityId)
            .ForeignKey("dbo.Province", t => t.ProvinceId)
            .Index(t => t.ProvinceId);

等等……

请注意,Country 不是要创建的第一个表。

当我发布时:

update-database

数据库已创建,表也已创建,但种子方法没有。我遇到了异常:

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Area_dbo.City_CityId".

如果我停止向 City 播种,就不会发生这种情况。

我希望问题现在很清楚。

这是城市种子

class CityDefaultData
{
    private readonly string[,] _cities = new string[,]
    {
        {"1", "الرياض", "Riyadh"},
        {"2", "جدة", "Jeddah"},
        {"3", "الدمام", "Dammam"},
        {"4", "بريدة", "Buraidah"}
    };

    private readonly List<City> _newCitiess = new List<City>();

    public List<City> GetDefaultCity()
    {
        for (var i = 0; i <= _cities.GetUpperBound(0); i++)
        {
            _newCitiess.Add(new City()
            {
                ProvinceId = Convert.ToInt32(_cities[i, 0]),
                CityArabicName = _cities[i, 1],
                CityEnglishName = _cities[i, 2]
            });
        }
        return _newCitiess;
    }
}

区域种子

class AreaDefaultData
{
    private readonly string[,] _areas = new string[,]
    {
        {"1", "السلام", "Salam"},
        {"1", "العليا", "Olayya"},
        {"1", "الروابي", "Rawabi"},
        {"1", "القدس", "Quds"},
        {"1", "المنار", "Manar"},
        {"2", "السلام", "Salam"},
        {"2", "العليا", "Olayya"},
        {"2", "الروابي", "Rawabi"},
        {"2", "القدس", "Quds"},
        {"2", "المنار", "Manar"}
    };


    private readonly List<Area> _newAreas = new List<Area>();

    public List<Area> GetDefaultArea()
    {
        for (var i = 0; i <= _areas.GetUpperBound(0); i++)
        {
            _newAreas.Add(new Area()
            {
                CityId = Convert.ToInt32(_areas[i, 0]),
                AreaArabicName = _areas[i, 1],
                AreaEnglishName = _areas[i, 2]
            });
        }
        return _newAreas;
    }
}

这是我的 DbInitialize

class DbInitializer
{
    public void DataSeed(CmsDbContext context)
    {
        // Country
        List<Country> coutry =
            new CountryDefaultData().GetDefaultCountry();
        coutry.ForEach(e => context.Countries.Add(e));
        context.Countries.AddOrUpdate();

        // District
        List<District> district =
            new DistrictDefaultData().GetDefaultDistrict();
        district.ForEach(e => context.Districts.Add(e));
        context.Districts.AddOrUpdate();

        // Province
        List<Province> province =
            new ProvinceDefaultData().GetDefaultProvince();
        province.ForEach(e => context.Provinces.Add(e));
        context.Provinces.AddOrUpdate();

        // City
        List<City> city =
            new CityDefaultData().GetDefaultCity();
        city.ForEach(e => context.Cities.Add(e));
        context.Cities.AddOrUpdate();

        // Area
        List<Area> area =
            new AreaDefaultData().GetDefaultArea();
        area.ForEach(e => context.Areas.Add(e));
        context.Areas.AddOrUpdate();

        // Id Type
        List<IdType> idType =
            new IdTypeDefultData().GetDefaultIdTypes();
        idType.ForEach(e => context.IdTypes.Add(e));
        context.IdTypes.AddOrUpdate();

        // Customer Type
        List<CustomerType> customerType =
            new CustomerTypeDefaultData().GetDefaultCustomerType();
        customerType.ForEach(e => context.CustomerTypes.Add(e));
        context.CustomerTypes.AddOrUpdate();

        // Income Type
        List<IncomeType> incomeType =
            new IncomeTypeDefaultData().GetDefaultIncomeType();
        incomeType.ForEach(e => context.IncomeTypes.Add(e));
        context.IncomeTypes.AddOrUpdate();

        // Outcome Type
        List<OutcomeType> outcomeType =
            new OutcomeTypeDefaultData().GetDefaultOutcomeType();
        outcomeType.ForEach(e => context.OutcomeTypes.Add(e));
        context.OutcomeTypes.AddOrUpdate();

        // Payment Type
        List<PaymentType> paymentType =
            new PaymentTypeDefaultData().GetDefaultPaymentType();
        paymentType.ForEach(e => context.PaymentTypes.Add(e));
        context.PaymentTypes.AddOrUpdate();
    }
}

最后:

internal sealed class Configuration : DbMigrationsConfiguration<CMS.Model.Domain.CmsDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
    }

    protected override void Seed(CMS.Model.Domain.CmsDbContext context)
    {
        DbInitializer all = new DbInitializer();
        all.DataSeed(context);
    }
}

【问题讨论】:

  • 我不知道你在说什么。请显示重现问题的代码。
  • @Gert Arnold,感谢您的关心。我更新了问题。
  • Seed 方法会导致问题,这就是您应该展示的内容。其余的无关紧要。如果数据库创建得很好(就是这样),为什么还要担心CreateTable 语句的顺序呢?
  • 您似乎认为创建数据库表后,立即对其进行种子化。但是 EF 怎么会知道怎么做呢?首先创建数据库,即整个 数据库。 然后执行种子方法。问题是您的种子数据没有正确的相互引用,特别是:Areas 没有 City 引用。
  • 我和@GertArnold 在一起。附带说明一下,您的 AddOrUpdate 调用无济于事。

标签: c# entity-framework dbcontext


【解决方案1】:

好吧,终于在消除了关于创建表、播种数据和添加数据的顺序的一些误解之后,我们已经到了可以提供一些有希望有帮助的见解的地步。

添加或更新

此方法专门用于播种数据,因此您走在了正确的轨道上。但是您没有有效地使用它,因为您没有输入任何实体。这是一个如何使用它的示例:

var cities = new City[]
{
    new City { ProvinceId = 1, CityArabicName = "الرياض", CityEnglishName = "Riyadh"},
    new City  { ProvinceId = 2, CityArabicName = "جدة",  CityEnglishName = "Jeddah"},
    new City  { ProvinceId = 3, CityArabicName = "الدمام", CityEnglishName = "Dammam"},
    new City  { ProvinceId = 4, CityArabicName = "بريدة", CityEnglishName = "Buraidah"}
};
context.Cities.AddOrUpdate(c => c.CityArabicName, cities);

lambda 表达式 c =&gt; c.CityArabicName 告诉 EF 通过 CityArabicName 查找城市。如果具有该名称的城市已经存在,则将该城市标记为已修改,否则将其标记为新。请注意,AddOrUpdate 不保存任何数据,这是通过最后一次 SaveChanges 调用完成的。

如果您不提供此 lambda 表达式,EF 会假定主键 CityId 是用于查找现有城市的键。好吧,cities 中没有指定它们(即0)。因此找不到现有城市,Seed 方法将始终复制您的城市。因此,当自动生成主键时,您总是需要一些 自然键,EF 将通过该键识别现有实体。

但即使您在种子数据中输入主键值,EF 也会忽略它们。 使用自动生成的主键,您无法为主键值设定种子。

这与您的问题有什么关系?

好吧,如果主键值不可预测,您也不能为硬编码的外键值播种。这正是您使用Areas 所做的。现在,诚然,当城市 播种时,你得到 FK 违规是很奇怪的,而不是在它们没有播种时。毕竟,很有可能会生成CityIds 1 和 2(尽管 Sql Server 可以决定以更高的值开始生成)。所以我无法真正解释你的发现,但我确实知道无论如何你都必须修复你的种子代码。

修复它

在种子脚本中有两种方法可以将对象绑定在一起:

  1. 将整个脚本封装在TransactionScope 中,多次调用SaveChanges,并捕获生成的主键值,以便在后续步骤中将它们分配给实体属性。我不太喜欢这个选项,但有时你必须使用它。更好的选择是 -

  2. 创建对象图。也就是说,创建具有嵌套实体的实体并通过一次SaveChanges 调用来保存它们。在您的情况下,这意味着 City 应该有一个导航属性...

    public virtual ICollection<Area> Areas { get; set; }
    

    ...并且您应该填充种子实体中的区域:

    var cities = new City[]
    {
        new City { ProvinceId = 1, CityArabicName = "الرياض", CityEnglishName = "Riyadh",
                   Areas = areas1 },
        new City { ProvinceId = 2, CityArabicName = "جدة",  CityEnglishName = "Jeddah",
                   Areas = areas2 },
        new City { ProvinceId = 3, CityArabicName = "الدمام", CityEnglishName = "Dammam"},
        new City { ProvinceId = 4, CityArabicName = "بريدة", CityEnglishName = "Buraidah"}
    };
    

    ... 其中areas1areas2 显然是属于前两个城市的两个区域集合。现在您只需要AddOrUpdate 城市,因为如果必须添加一个城市,那么它的区域也必须添加。

后续步骤

这并不能说明全部。播种相关数据可能很困难。如果您必须在应用程序的更高版本中将Area 添加到City 怎么办?如果您应该更新Area 或删除一个怎么办?这通常需要更详细的步骤,不再使用AddOrUpdate

【讨论】:

  • 我调用了 SaveChanges(),而不是 AddOrUpdate()。因此,context.Cities.AddOrUpdate() 将是 context.SaveChanges()。有用。感谢您的帮助。
【解决方案2】:

用于播种 创建Country的对象,在其中添加District,在District中添加Province,在Province中添加City>Area>TypeIDTypeID中添加该层次结构的客户数据。 现在分别向CityArea 添加另一个AreaTypeID

这样Country 的对象就有多个区,每个区有多个省,每个省有多个市......同样如此。

现在将此对象Country 添加到函数中:

context.Countries.Add(

并在此将多个国家添加到上下文中。

【讨论】:

  • 我会试试的。它看起来被嵌套太多了。谢谢
猜你喜欢
  • 2017-01-24
  • 2019-06-10
  • 2020-02-10
  • 1970-01-01
  • 2012-11-11
  • 1970-01-01
  • 2021-06-19
  • 1970-01-01
  • 2016-03-07
相关资源
最近更新 更多