简体   繁体   English

实体框架6代码优先:如何使用“循环”关系和存储生成的列对数据进行种子化?

[英]Entity Framework 6 Code first: How to seed data with a 'circular' relationship and store-generated columns?

I am new to EF Code First, and I am trying to seed my database with some data using code-first migrations. 我是EF Code First的新手,我正在尝试使用代码优先迁移为我的数据库添加一些数据。 I have managed to solve several errors so far, but now I am stuck and have not been able to find the answer. 到目前为止,我已设法解决了几个错误,但现在我陷入困境,无法找到答案。 I have two issues when updating the database from my code. 从我的代码更新数据库时,我有两个问题。

I have several objects which have various many-to-many and one-to-one relationships, and some eventually create a circle. 我有几个具有各种多对多和一对一关系的对象,有些最终会创建一个圆圈。 I am not sure if this is the cause of second problem or not, when I try to seed the database. 当我尝试为数据库播种时,我不确定这是否是第二个问题的原因。

  1. The first error I have is: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'LicenseId'. A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'LicenseId'.的第一个错误是: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'LicenseId'. A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'LicenseId'.

Is there a way I can use a db generated id as a foreign key? 有没有办法可以使用db生成的id作为外键? Is is just the order in which I am creating/inserting objects? 这只是我创建/插入对象的顺序吗? (see seeding code below) (见下面的播种代码)

If I don't use [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] in the License I get an error about not being able to implicitly insert an id that is not generated by the database. 如果我在许可证中不使用[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] ,我会收到一条错误,指出无法隐式插入数据库未生成的ID。

public class License : Entity
{
    [Key, ForeignKey("Customer")]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int LicenseId { get; set; }

    [Required(ErrorMessage = "Date Created name is required")]
    public DateTime DateCreated { get; set; }

    public virtual ICollection<ProductLicense> ProductLicenses { get; set; } // one License has many ProductLicenses
    public virtual Customer Customer { get; set; } // one Customer has one License
}

public class Customer : Entity
{
    [Key]
    public int CustomerId { get; set;}

    [Required(ErrorMessage = "Username is required")]
    [Column(TypeName = "nvarchar")]
    [MaxLength(500)]
    public string Name { get; set; }

    public virtual ICollection<User> Users { get; set; } // one Customer has many users
    public virtual License License { get; set; } // one Customer has one License
    //public virtual ICollection<License> License { get; set; } // one Customer has many Licenses
}
  1. If I change the License-Customer to be a many-to-many relationship (which I do not want) I get the following error: Cannot insert duplicate key row in object 'dbo.Users' with unique index 'IX_Username'. 如果我将License-Customer更改为多对多关系(我不想要), Cannot insert duplicate key row in object 'dbo.Users' with unique index 'IX_Username'.出现以下错误: Cannot insert duplicate key row in object 'dbo.Users' with unique index 'IX_Username'.

These are all my relationships: 这些都是我的关系:

Customer -many-> User -many-> Role -many-> Permission -one- ProductLicense <-many- License -one- Customer

Here is the code I have been using in protected override void Seed(DAL.Models.Context context) , creating the the objects in code and then using AddOrUpdate: 这是我在protected override void Seed(DAL.Models.Context context)中使用的代码,在代码中创建对象,然后使用AddOrUpdate:

// Default Customers - create
var customers = new[]{
    new Customer { Name = "cust_a" },
    new Customer { Name = "cust_b" },
    new Customer { Name = "cust_c" }
};

// Default Licenses - create
var licenses = new[]{
    new License { DateCreated = DateTime.Now.AddDays(-3), Customer = customers[0] },
    new License { DateCreated = DateTime.Now.AddDays(-2), Customer = customers[1] },
    new License { DateCreated = DateTime.Now.AddDays(-1), Customer = customers[2] }
};

// Default ProductLicenses - create, and add default licenses
var productLicenses = new[]{
    new ProductLicense { LicenceType = LicenseType.Annual, StartDate = DateTime.Now, License = licenses[0] },
    new ProductLicense { LicenceType = LicenseType.Monthly, StartDate = DateTime.Now, License = licenses[1] },
    new ProductLicense { LicenceType = LicenseType.PAYG, StartDate = DateTime.Now, License = licenses[2] }
    };

// Default Permissions - create, and add default product licenses
var permissions = new[]{
    new Permission { Name = "Super_a", ProductLicense = productLicenses[0] },
    new Permission { Name = "Access_b", ProductLicense = productLicenses[1] },
    new Permission { Name = "Access_c", ProductLicense = productLicenses[2] }
};

// Default Roles - create, and add default permissions
var roles = new[]{
    new Role { Name = "Super_a", Permissions = permissions.Where(x => x.Name.Contains("_a")).ToList() },
    new Role { Name = "User_b", Permissions = permissions.Where(x => x.Name.Contains("_b")).ToList() },
    new Role { Name = "User_c", Permissions = permissions.Where(x => x.Name.Contains("_c")).ToList() }
};

// Default Users - create, and add default roles
var users = new[]{
    new User { Username = "user@_a.com", Password = GenerateDefaultPasswordHash(), Salt = _defaultSalt, Roles = roles.Where(x => x.Name.Contains("_a")).ToList() },
    new User { Username = "user@_b.co.uk", Password = GenerateDefaultPasswordHash(), Salt = _defaultSalt, Roles = roles.Where(x => x.Name.Contains("_b")).ToList() },
    new User { Username = "user@_c.com", Password = GenerateDefaultPasswordHash(), Salt = _defaultSalt, Roles = roles.Where(x => x.Name.Contains("_c")).ToList() }
        };

// Default Customers - insert, with default users

foreach (var c in customers)
{
    c.Users = users.Where(x => x.Username.Contains(c.Name.ToLower())).ToList();
    context.Customers.AddOrUpdate(c);
}

I also tried changing the first part following //Default Customers - create to 我还尝试更改//Default Customers - create的第一部分

// Default Customers - create and insert
context.Customers.AddOrUpdate(
    u => u.Name,
    new Customer { Name = "C6" },
    new Customer { Name = "RAC" },
    new Customer { Name = "HSBC" }
);

context.SaveChanges();

var customers = context.Customers.ToList();

I would greatly appreciate help with this, so that I can seed my database with appropriate data. 我非常感谢您的帮助,以便我可以使用适当的数据为我的数据库提供支持。

Many thanks for your help, and sorry for the long post. 非常感谢你的帮助,对于长篇文章感到抱歉。

--- UPDATE --- ---更新---

After following Chris Pratt's excellent answer below I now get the following error: Conflicting changes detected. This may happen when trying to insert multiple entities with the same key. 按照下面的Chris Pratt的优秀答案后,我现在收到以下错误: Conflicting changes detected. This may happen when trying to insert multiple entities with the same key. Conflicting changes detected. This may happen when trying to insert multiple entities with the same key.

Here is my new code for the seeding and all the models involved: https://gist.github.com/jacquibo/c19deb492ec3fff0b5a7 这是我的播种新代码和所涉及的所有模型: https//gist.github.com/jacquibo/c19deb492ec3fff0b5a7

Can anyone help with this? 有人能帮忙吗?

Seeding can be a bit complicated, but you just need to keep a few things in mind: 播种可能有点复杂,但您只需要记住以下几点:

  1. Anything added with AddOrUpdate will be, well, added or updated. AddOrUpdate添加的任何内容AddOrUpdate将被添加或更新。 But anything else will be added , not updated. 但是其他任何东西都会被添加 ,而不是更新。 In your code, the only thing you're using AddOrUpdate with is Customer . 在您的代码中,您唯一使用AddOrUpdate的是Customer

  2. EF will add related items when AddOrUpdate item is being added , but it ignores those relations when that item is being updated. EF将添加相关项目时AddOrUpdate添加的项目,但是却忽略了这些关系时,该项目正在更新。

Based on that, if you're adding object hierarchies like this, then you have to take a little extra care. 基于此,如果你要添加这样的对象层次结构,那么你必须要特别小心。 First, you need to call SaveChanges between levels of the hierarchy, and second, you need to work with ids , not relationships. 首先,您需要在层次结构的级别之间调用SaveChanges ,其次,您需要使用ID ,而不是关系。 For example: 例如:

var customers = new[]{
    new Customer { Name = "cust_a" },
    new Customer { Name = "cust_b" },
    new Customer { Name = "cust_c" }
};
context.Customers.AddOrUpdate(r => r.Name, customers[0], customers[1], customers[2]);
context.SaveChanges()

Now you have all your customers taken care of, and they each will have an value for their id, one way or another. 现在,您已经为所有客户提供了照顾,并且他们每个人都会以这种或那种方式获得他们身份的价值。 Then, start digging into your hierarchy: 然后,开始挖掘您的层次结构:

// Default Licenses - create
var licenses = new[]{
    new License { DateCreated = DateTime.Now.AddDays(-3), CustomerId = customers[0].CustomerId },
    new License { DateCreated = DateTime.Now.AddDays(-2), CustomerId = customers[1].CustomerId },
    new License { DateCreated = DateTime.Now.AddDays(-1), CustomerId = customers[2].CustomerId }
};
context.Licenses.AddOrUpdatE(r => r.DateCreated, licenses[0], licenses[1], licenses[2]);

You don't need to call SaveChanges if you're dealing with objects at the same level in the hierarchy until you've defined them all. 如果您在层次结构中处理同一级别的对象,则不需要调用SaveChanges ,直到您将它们全部定义为止。 However, if you have an entity that references something like License , then you would want to call SaveChanges again before adding those. 但是,如果您有一个引用类似License的实体,那么您需要在添加之前再次调用SaveChanges

Also, notice that I'm switch to setting the id instead of the relationship. 另外,请注意我切换到设置id而不是关系。 Doing it this way will allow you to update the relationship later by changing the id. 这样做可以让您稍后通过更改ID来更新关系。 For example: 例如:

// Changed customer id from customer[1] to customer[0]
new License { DateCreated = DateTime.Now.AddDays(-2), CustomerId = customers[0].CustomerId },

Whereas the following would not work: 而下面是行不通的:

// Attempted to change customer[1] to customer[0], but EF ignores this in the update.
new License { DateCreated = DateTime.Now.AddDays(-2), Customer = customers[0] },

Of course, you don't actually have a CustomerId property on License , but you should. 当然,您在License上实际上没有CustomerId属性,但您应该这样做。 While EF will automatically generate a column to hold the relationship without it, you can never actually get at the foreign key value without an explicit property, and for more reasons than this alone, being able to work with the actual foreign key is extremely beneficial. 虽然EF会在没有它的情况下自动生成一个列来保存关系,但是如果没有显式属性,你实际上永远无法获得外键值,并且由于更多的原因,能够使用实际的外键是非常有益的。 Just follow this convention for all your reference properties: 只需遵循此约定即可获得所有参考属性:

[ForeignKey("Customer")]
public int CustomerId { get; set;}
public virtual Customer Customer { get; set; }

The ForeignKey attribute isn't always required, depending on whether your foreign key and reference property names align to EF's conventions for such things, but I find it easier and less error-prone to just be explicit about my intentions. ForeignKey属性并不总是必需的,这取决于您的外键和引用属性名称是否与EF的约定对齐,但我发现它更容易,更不容易出错,只是明确我的意图。

A foreign key must reference a candidate key of another table (usually the PK, which can be DB generated). 外键必须引用另一个表的候选键(通常是PK,可以生成DB)。 The FK is dependent on the values in another table and clearly cannot be generated by the table that owns it. FK依赖于另一个表中的值,显然不能由拥有它的表生成。

What you're looking to do is called "Shared Primary Key". 您要做的事情称为“共享主键”。 It looks like you were close - you just have the ID generation backwards and are missing a ForeignKeyAttribute on the required entity. 看起来你很接近 - 你只需要向后生成ID,并且在所需实体上缺少ForeignKeyAttribute

If License is the dependent entity, you want it to look like so: 如果License是从属实体,您希望它看起来像这样:

public class License : Entity
{
    [Key, ForeignKey("Customer")]
    public int LicenseId { get; set; } // consider renaming to CustomerId

    // other properties here
    ...

    public virtual Customer Customer { get; set; }
}

public class Customer : Entity
{
    [Key, ForeignKey( "License" )]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int CustomerId { get; set;}

    // other properties here
    ...

    public virtual License License { get; set; }
}

If you so desire, this can be used for Table Splitting , where you split the fields of a single DB table between two or more entities (you can do this with the above by specifying the same table name for each entity either via TableAttribute or FluentAPI calls). 如果您愿意,可以将其用于Table Splitting ,在这里您可以在两个或多个实体之间拆分单个数据库表的字段(您可以通过以上方式执行此操作,方法是通过TableAttribute或FluentAPI为每个实体指定相同的表名称调用)。 This is useful if you have a large field that you want to be able to selectively load, for example. 例如,如果您希望能够选择性地加载大字段,这将非常有用。

Code first migrations does not consider to provide integrity updates for your current database records when generate a new migration script to be applied. 在生成要应用的新迁移脚本时,代码优先迁移不考虑为当前数据库记录提供完整性更新。 You might have to add extra SQL commands in the Up() method of the migration file by means of using Sql("...") functions that will update current database rows before try to apply your migration. 您可能必须通过使用Sql(“...”)函数在迁移文件的Up()方法中添加额外的SQL命令,这些函数将在尝试应用迁移之前更新当前数据库行。

Assume that you would like to apply a new referantial constraint into a database. 假设您要将新的referantial约束应用于数据库。 You don't get an error message while generating your migration file even if your reference file does not contain corresponding rows. 即使您的引用文件不包含相应的行,生成迁移文件时也不会收到错误消息。 But you get the error 1 (your first message) while executing the script on the package manager console. 但是在程序包管理器控制台上执行脚本时会收到错误1(您的第一条消息)。

For that purpose, you should add Sql("INSERT INTO .... ") and/or Sql("UPDATE .....") commands at the top of your Up() method that would modify your current database rows with correct values. 为此,您应该在Up()方法的顶部添加Sql(“INSERT INTO ....”)和/或Sql(“UPDATE .....”)命令,这些命令将修改您当前的数据库行。正确的价值观

Similarly, you might have to delete some records from database depending on your structural changes. 同样,您可能必须从数据库中删除一些记录,具体取决于您的结构更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 实体框架代码优先-具有循环关系的奇怪行为 - Entity Framework Code First - strange behaviour with circular relationship 如何在实体代码优先中以多对多关系为数据库播种 - How to seed the database with a many-to-many relationship in Entity Code First 实体框架-一对一-ReferentialConstraint映射到商店生成的列 - Entity Framework - One-to-One - ReferentialConstraint is mapped to a store-generated column C#-实体框架-大种子数据代码优先 - C# - Entity Framework - Large seed data code-first 实体框架代码优先:如何为数据进行单元测试 - Entity Framework Code First: How to seed a database for unit testing 如何为复杂关系建立实体模型框架的种子 - How to seed this entity model framework for complex relationship 代码优先迁移实体框架种子错误 - Code First Migration Entity Framework Seed Error 实体框架代码第一个来自数据库的种子 - Entity Framework Code First Seed From Database Code First Entity Framework中的Seed方法未填充多对多关系中的链接表 - The link table in Many-to-many relationship is not populated by the Seed method in Code First Entity Framework 如何首先在实体框架代码中为几个表播种标识种子值 - How to seed identity seed value in entity framework code first for several tables
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM