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

tek*_*irl 5 c# ef-code-first asp.net-mvc-4 ef-migrations entity-framework-6

我是EF Code First的新手,我正在尝试使用代码优先迁移为我的数据库添加一些数据.到目前为止,我已设法解决了几个错误,但现在我陷入困境,无法找到答案.从我的代码更新数据库时,我有两个问题.

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

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

有没有办法可以使用db生成的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
}
Run Code Online (Sandbox Code Playgroud)
  1. 如果我将License-Customer更改为多对多关系(我不想要),我会收到以下错误: 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
Run Code Online (Sandbox Code Playgroud)

这是我一直使用的代码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);
}
Run Code Online (Sandbox Code Playgroud)

我也试过改变后的第一部分//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();
Run Code Online (Sandbox Code Playgroud)

我非常感谢您的帮助,以便我可以使用适当的数据为我的数据库提供支持.

非常感谢你的帮助,对于长篇文章感到抱歉.

---更新---

在按照下面的Chris Pratt的优秀答案后,我现在得到以下错误: Conflicting changes detected. This may happen when trying to insert multiple entities with the same key.

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

有人能帮忙吗?

Chr*_*att 12

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

  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()
Run Code Online (Sandbox Code Playgroud)

现在,您已经为所有客户提供了照顾,并且他们每个人都会以这种或那种方式获得他们身份的价值.然后,开始挖掘您的层次结构:

// 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]);
Run Code Online (Sandbox Code Playgroud)

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 },
Run Code Online (Sandbox Code Playgroud)

而下面的就不能工作:

// 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] },
Run Code Online (Sandbox Code Playgroud)

当然,你实际上没有CustomerId财产License,但你应该.虽然EF会在没有它的情况下自动生成一个列来保存关系,但是如果没有显式属性,你实际上永远无法获得外键值,并且由于更多的原因,能够使用实际的外键是非常有益的.只需遵循此约定即可获得所有参考属性:

[ForeignKey("Customer")]
public int CustomerId { get; set;}
public virtual Customer Customer { get; set; }
Run Code Online (Sandbox Code Playgroud)

ForeignKey属性并不总是必需的,这取决于您的外键和引用属性名称是否与EF的约定对齐,但我发现它更容易,更不容易出错,只是明确我的意图.