c# - Entity Framework 6 代码优先 : How to seed data with a 'circular' relationship and store-generated columns?

标签 c# asp.net-mvc-4 ef-code-first entity-framework-6 entity-framework-migrations

我是 EF Code First 的新手,我正在尝试使用代码优先迁移将一些数据植入我的数据库。到目前为止,我已经设法解决了几个错误,但现在我卡住了,一直找不到答案。从我的代码更新数据库时遇到两个问题。

我有几个具有各种多对多和一对一关系的对象,其中一些最终创建了一个圆圈。当我尝试为数据库播种时,我不确定这是否是第二个问题的原因。

  1. 我遇到的第一个错误是:A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'LicenseId'.

有没有办法可以使用数据库生成的 ID 作为外键?只是我创建/插入对象的顺序吗? (见下面的播种代码)

如果我不使用 [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. 如果我将许可客户更改为多对多关系(我不希望这样),我会收到以下错误:Cannot insert duplicate key row in object 'dbo.Users' with unique index 'IX_Username'.

这些是我所有的关系:

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

这是我在 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);
}

我还尝试更改 //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();

我将非常感谢这方面的帮助,这样我就可以用适当的数据为我的数据库做种。

非常感谢您的帮助,很抱歉发了这么长的帖子。

---更新---

在遵循以下 Chris Pratt 的出色回答后,我现在收到以下错误: Conflicting changes detected. This may happen when trying to insert multiple entities with the same key.

这是我的播种新代码和所有涉及的模型:https://gist.github.com/jacquibo/c19deb492ec3fff0b5a7

有人可以帮忙吗?

最佳答案

播种可能有点复杂,但您只需要记住以下几点:

  1. 任何使用 AddOrUpdate 添加的内容都将被添加或更新。但其他任何内容都将添加,而不是更新。在您的代码中,您唯一使用 AddOrUpdate 的是 Customer

  2. EF 将在 AddOrUpdate 项目被添加时添加相关项目,但它会在该项目被更新时忽略这些关系。

    <

基于此,如果您要像这样添加对象层次结构,则必须格外小心。首先,您需要在层次结构的级别之间调用 SaveChanges,其次,您需要使用 id,而不是关系。例如:

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()

现在您已照顾好所有客户,并且他们每个人都将以某种方式为他们的 ID 赋值。然后,开始深入了解您的层次结构:

// 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]);

如果您正在处理层次结构中同一级别的对象,则无需调用 SaveChanges,直到您定义了所有对象。但是,如果您有一个引用类似 License 的实体,那么您可能希望在添加这些内容之前再次调用 SaveChanges

另外,请注意我切换到设置 ID 而不是关系。这样做将允许您稍后通过更改 id 来更新关系。例如:

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

而以下将工作:

// 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] },

当然,您在 License 上实际上没有 CustomerId 属性,但您应该有。虽然 EF 会自动生成一个列来保存没有它的关系,但如果没有显式属性,您永远无法真正获得外键值,并且出于比这本身更多的原因,能够使用实际的外键是非常有益的。只需对所有引用属性遵循此约定:

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

ForeignKey 属性并不总是必需的,这取决于您的外键和引用属性名称是否符合 EF 对此类事物的约定,但我发现它更容易且不易出错明确说明我的意图。

关于c# - Entity Framework 6 代码优先 : How to seed data with a 'circular' relationship and store-generated columns?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26551762/

相关文章:

c# - x :Bind in windows 10 Mode One way

c# - EF 代码优先外键关系

asp.net - 未找到 View 'Index' 或其主视图或没有 View 引擎支持搜索的位置。在 IIS 8.5 上部署 MVC5/MVC4 后

c# - 创建一个帮助程序来覆盖 CheckboxFor

asp.net-mvc - 以下部分已定义但尚未为布局页面 "~/Views/Shared/SiteLayout.cshtml": "Scripts" 呈现

asp.net-mvc - 在 Entity Framework 代码优先中为同一个表定义多个外键

C# "cached debug log"

c# - 多播委托(delegate) : Different behavior when adding method references to a delegate in other methods

asp.net-mvc - Entity Framework DbSet.Find 抛出异常

c# - Entity Framework 映射到用于读取的 View 和用于 CUD 的表