更新时间:2023-02-15 18:03:19
模型种子数据
方法具有一些已知的局限性.最值得注意的是,它无法通过从导航属性推断关系来插入相关数据,并且必须显式添加外键值.有关详细信息,请参见-模型种子数据的限制
根据您的情况,***使用 取决于您在哪里以及如何使用它.我自己通过自定义初始化逻辑
方法,如此处所述- IHost
扩展方法(例如-
公共静态异步任务GenerateSeedDataAsync(此IHost主机){使用(var scope = host.Services.CreateScope()){var provider = scope.ServiceProvider;var dbCtx = provider.GetRequiredService< DbCtx>();dbCtx.Database.Migrate();等待AddEntities(dbCtx);}}
因此,每次应用程序启动时,我们都可以选择检查数据库是否需要种子,例如-
公共静态void Main(string [] args){IHost主机= CreateHostBuilder(args).Build();host.GenerateSeedDataAsync().Wait();host.Run();}
I have User entity
public class User
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public ICollection<Technology> Technologies { get; set; } = new List<Technology>();
}
I have Technology entity
public class Technology
{
public int TechnologyId { get; set; }
public string TitleTechnology { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
public ICollection<User> Users { get; set; } = new List<User>();
}
I want to create many-to-many relation, so I have such OnModelCreating
method:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var groupList = new List<Group>
{
new Group {GroupId = 1, TitleGroup = ".NET"}
};
var technologyList = new List<Technology>
{
new Technology {TechnologyId = 1, GroupId = 1, TitleTechnology = ".NET 5"},
new Technology {TechnologyId = 2, GroupId = 1, TitleTechnology = ".NET Framework 4.8"},
new Technology {TechnologyId = 3, GroupId = 1, TitleTechnology = "EF 6"},
new Technology {TechnologyId = 4, GroupId = 1, TitleTechnology = "ASP.NET MVC 5"}
};
var userList = new List<User>
{
new User
{
UserId = 1, FirstName = "Serhii", LastName = "Yurko", Email = "test", Password = "test",
Technologies = new List<Technology> {technologyList[0], technologyList[1]}
}
};
modelBuilder.Entity<Technology>().HasOne(exp => exp.Group).WithMany(exp => exp.Technologies).HasForeignKey(exp => exp.GroupId);
modelBuilder.Entity<User>().HasMany(p => p.Technologies).WithMany(p => p.Users)
.UsingEntity(j => j.ToTable("UserTechnology"));
modelBuilder.Entity<User>().HasData(userList);
modelBuilder.Entity<Group>().HasData(groupList);
modelBuilder.Entity<Technology>().HasData(technologyList);
base.OnModelCreating(modelBuilder);
}
When I want to create migration I receive such an exception -
The seed entity for entity type 'User' cannot be added because it has the navigation 'Technologies' set. To seed relationships, add the entity seed to 'TechnologyUser (Dictionary<string, object>)' and specify the foreign key values {'UsersUserId'}. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the involved property values.
How to create proper relations?
The Model seed data
approach has a few known limitations. Most notably, it cannot insert related data by inferring the relations from navigation properties, and you must explicitly add the foreign-key values. For details, see - Limitations of model seed data
In your case, it is better to go with the Custom initialization logic
approach, as described here - Custom initialization logic (don't forget to read the Warning
carefully).
An implementation of Custom initialization logic
approach :
For the Models -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Technology> Technologies { get; set; } = new List<Technology>();
}
public class Technology
{
public int Id { get; set; }
public string TitleTechnology { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
public ICollection<User> Users { get; set; } = new List<User>();
}
public class Group
{
public int Id { get; set; }
public string TitleGroup { get; set; }
public ICollection<Technology> Technologies { get; set; } = new List<Technology>();
}
with the configuration -
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Technology>()
.HasOne(exp => exp.Group)
.WithMany(exp => exp.Technologies)
.HasForeignKey(exp => exp.GroupId);
modelBuilder.Entity<User>()
.HasMany(p => p.Technologies)
.WithMany(p => p.Users);
}
a possible implementation might look like -
private static async Task AddEntities(DbCtx dbCtx)
{
List<Group> groupList;
if (!dbCtx.Groups.Any())
{
groupList = new List<Group>
{
new Group { TitleGroup = ".NET"}
};
dbCtx.Groups.AddRange(groupList);
}
else
{
groupList = dbCtx.Groups.ToList();
}
List<Technology> technologyList;
if (!dbCtx.Technologies.Any())
{
technologyList = new List<Technology>
{
new Technology { Group = groupList[0], TitleTechnology = ".NET 5"},
new Technology { Group = groupList[0], TitleTechnology = ".NET Framework 4.8"},
new Technology { Group = groupList[0], TitleTechnology = "EF 6"},
new Technology { Group = groupList[0], TitleTechnology = "ASP.NET MVC 5"}
};
dbCtx.Technologies.AddRange(technologyList);
}
else
{
technologyList = dbCtx.Technologies.ToList();
}
List<User> userList;
if (!dbCtx.Users.Any())
{
userList = new List<User>
{
new User
{
Name = "Serhii", Technologies = new List<Technology> { technologyList[0], technologyList[1]}
}
};
dbCtx.Users.AddRange(userList);
}
await dbCtx.SaveChangesAsync();
}
Its up to you where and how you use it. I myself use it from an IHost
extension method like -
public static async Task GenerateSeedDataAsync(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
var provider = scope.ServiceProvider;
var dbCtx = provider.GetRequiredService<DbCtx>();
dbCtx.Database.Migrate();
await AddEntities(dbCtx);
}
}
so that every time the application starts, we have the option to check whether the database needs seeding, like -
public static void Main(string[] args)
{
IHost host = CreateHostBuilder(args).Build();
host.GenerateSeedDataAsync().Wait();
host.Run();
}