一步步开发自己的博客 .NET版(9、从model first替换成code first 有关问题记录)
为什么要改用code first
用过code first的基本上都不会再想用回model first或是db first(谁用谁知道)。不要问我为什么不一开始就直接使用code first,因为那个时候我还不会(甚至还把model first当成了code first)。
因为工作中使用的就是code first,且越用越习惯,越用越喜欢。
原因如果:
- 再也用为每次生成那个笨重的edmx文件性急了
- 再也不用当心保存tt文件而丢失特性、注销、扩展方法了
- 再也不用为了使用微软的验证插件非得写Metadata文件了
- 再也不用为了扩展tt文件生成的实体类去写(partial)部分类了。
- 再也不用为了生成满足自己需要的实体而去修改那些坑爹的tt文件里面的语法代码了(如:默认每个实体继承一个父类)
- 再也不用为了查找edmx文件打不开,去编辑庞大的edmx文件中找那些坑爹的错误了。
- 等等还有些暂时没想到的....
说改就改
修改前实体:db first(由tt文件生成)
修改后实体:code first(完全手写)
然后把实体更新到数据库对应的表结构。执行命令Enable-Migrations
遇到问题:
The EntityFramework package is not installed on project ''.(原因:因为没有选择“默认项目”)
继续问题:
The project 'Blogs.Model' failed to build.(原因:没有建一个继承于DbContext的类)
ok,提示已经启用迁移。
然后我们执行命令:Add-Migration blogs
异常: 从数据库中获取提供程序信息时出错。这可能是 Entity Framework 使用的连接字符串不正确导致的。有关详细信息,请查看内部异常并确保连接字符串正确。
我的乖乖,我非常确定我们字符串链接是正确的啊。
最后确定忘记给数据连接上下文在构造函数中传入配置文件的数据库链接名。
public BlogDbContext() : base("HiBlogsTest") { }
再执行(Add-Migration blogs),再出错:
异常:无法加载指定的元数据资源。(百度之,原来是链接字符串有问题。http://www.cnblogs.com/chengxiaohui/articles/2106765.html)
<add name="HiBlogsTest" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient; provider connection string=" data source=.; initial catalog=HiBlogsTest; user id=sa; password=123qwe; MultipleActiveResultSets=True; App=EntityFramework"" providerName="System.Data.EntityClient" />
改成:(那一堆csdl、ssdl、msl什么都不要了,就留个简单的链接。干净)
<add name="HiBlogsTest" connectionString="Data Source=.;Initial Catalog=HiBlogsTest;User ID=sa;Password=123qwe;" providerName="System.Data.SqlClient" />
ok,终于没有看见红色的字了。
且看到了一个自动生成的blogs文件。且不管,看看数据库是否有表结构。
空空如也。(屁都没看到一个)(原因:BlogDbContext上下文中没有添加实体,没有告诉程序要生成哪些实体到数据库)
给BlogDbContext类添加数据代码:
public class BlogDbContext : DbContext { public BlogDbContext() : base("HiBlogsTest") { } public DbSet<BlogInfo> BlogInfos { get; set; } public DbSet<BlogComment> BlogComments { get; set; } public DbSet<BlogReadInfo> BlogReadInfos { get; set; } public DbSet<BlogTag> BlogTags { get; set; } public DbSet<BlogType> BlogTypes { get; set; } public DbSet<BlogUser> BlogUsers { get; set; } public DbSet<BlogUserInfo> BlogUserInfos { get; set; } }
然后执行 :Add-Migration blogs 再执行 update-database
终于看到表数据了。
有了表还不行,我们还没有主外键。
修改BlogDbContext如下:
public class BlogDbContext : DbContext { public BlogDbContext() : base("HiBlogsTest") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var entityBlogUser = modelBuilder.Entity<BlogUser>(); entityBlogUser.HasMany(p => p.BlogInfos).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")); entityBlogUser.HasRequired(p => p.BlogUserInfo).WithRequiredPrincipal(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")); entityBlogUser.HasMany(p => p.BlogTags).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")); entityBlogUser.HasMany(p => p.BlogTypes).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")); entityBlogUser.HasMany(p => p.BlogComments).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")); var entityBlogInfo = modelBuilder.Entity<BlogInfo>(); entityBlogInfo.HasMany(p => p.BlogTags).WithMany(t => t.BlogInfos) .Map(m => m.ToTable("BlogInfo_BlogTag")); entityBlogInfo.HasMany(p => p.BlogTypes).WithMany(t => t.BlogInfos) .Map(m => m.ToTable("BlogInfo_BlogType")); entityBlogInfo.HasMany(p => p.BlogComments).WithRequired(t => t.BlogInfo) .Map(m => m.MapKey("BlogInfoId")); entityBlogInfo.HasMany(p => p.BlogReadInfos).WithRequired(t => t.BlogInfo) .Map(m => m.MapKey("BlogInfoId")); } public DbSet<BlogInfo> BlogInfos { get; set; } public DbSet<BlogComment> BlogComments { get; set; } public DbSet<BlogReadInfo> BlogReadInfos { get; set; } public DbSet<BlogTag> BlogTags { get; set; } public DbSet<BlogType> BlogTypes { get; set; } public DbSet<BlogUser> BlogUsers { get; set; } public DbSet<BlogUserInfo> BlogUserInfos { get; set; } }
然后重新命令:Add-Migration blogs 再执行 update-database
又见错误:
将 FOREIGN KEY 约束 'FK_dbo.BlogInfo_dbo.BlogUser_BlogUserId' 引入表 'BlogInfo' 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。
无法创建约束。请参阅前面的错误消息。
于是,一个一个的外键删掉,又一个个的来建。终于发现:(下图是数据库关系图,mssql生成的)
百度之:(原来是为了约束联级删除数据做的约束。实在话,还没玩过联级删除了,说明这个需求应该不是很常用。找个方法禁用可否?)
直接加一个.WillCascadeOnDelete(false)就可以了。(http://www.cnblogs.com/chear/archive/2012/11/09/2762145.html)
public class BlogDbContext : DbContext { public BlogDbContext() : base("HiBlogsTest") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var entityBlogUser = modelBuilder.Entity<BlogUser>(); entityBlogUser.HasMany(p => p.BlogInfos).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")).WillCascadeOnDelete(false); //与上面等效 //modelBuilder.Entity<BlogInfo>().HasRequired(p => p.BlogUser).WithMany(t => t.BlogInfos) //以BlogUser为主表(BlogUserInfo为从表,建立外键) entityBlogUser.HasRequired(p => p.BlogUserInfo).WithRequiredPrincipal(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")).WillCascadeOnDelete(false); //等效于HasRequired(p => ).WithOptional(i => ); ////以BlogUserInfo为主表(BlogUser为从表,建立外键) //modelBuilder.Entity<BlogUser>().HasRequired(p => p.BlogUserInfo).WithRequiredDependent(t => t.BlogUser) //.Map(m => m.MapKey("BlogUserId")).WillCascadeOnDelete(false); //等效于 HasOptional(p => ).WithRequired(i => ); entityBlogUser.HasMany(p => p.BlogTags).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")).WillCascadeOnDelete(false); entityBlogUser.HasMany(p => p.BlogTypes).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")).WillCascadeOnDelete(false); entityBlogUser.HasMany(p => p.BlogComments).WithRequired(t => t.BlogUser) .Map(m => m.MapKey("BlogUserId")).WillCascadeOnDelete(false); var entityBlogInfo = modelBuilder.Entity<BlogInfo>(); entityBlogInfo.HasMany(p => p.BlogTags).WithMany(t => t.BlogInfos) .Map(m => m.ToTable("BlogInfo_BlogTag")); entityBlogInfo.HasMany(p => p.BlogTypes).WithMany(t => t.BlogInfos) .Map(m => m.ToTable("BlogInfo_BlogType")); entityBlogInfo.HasMany(p => p.BlogComments).WithRequired(t => t.BlogInfo) .Map(m => m.MapKey("BlogInfoId")).WillCascadeOnDelete(false); entityBlogInfo.HasMany(p => p.BlogReadInfos).WithRequired(t => t.BlogInfo) .Map(m => m.MapKey("BlogInfoId")).WillCascadeOnDelete(false); } public DbSet<BlogInfo> BlogInfos { get; set; } public DbSet<BlogComment> BlogComments { get; set; } public DbSet<BlogReadInfo> BlogReadInfos { get; set; } public DbSet<BlogTag> BlogTags { get; set; } public DbSet<BlogType> BlogTypes { get; set; } public DbSet<BlogUser> BlogUsers { get; set; } public DbSet<BlogUserInfo> BlogUserInfos { get; set; } }
然后重新命令:Add-Migration blogs 再执行 update-database
完美,表结构过来了。表关系过来了。(接下来就是该代码了,因为表名做了小的改动,字段也做了少许调整所以改的东西还真不少。整整改了一天时间。)
现在回过头来想想,之前是先model first之后小许改动就用的db first。以前怎么没有遇到过(将 FOREIGN KEY 约束 'FK_dbo.BlogInfo_dbo.BlogUser_BlogUserId' 引入表 'BlogInfo' 可能会导致循环或多重级联路径。请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。
无法创建约束。请参阅前面的错误消息。)这个错误。好奇心驱使,觉得看看以前的代码的edmx是怎么管理这种关系的。
很惊奇的发现,完全没有问题。于是,不死心看看数据库里面是不是有什么蹊跷。
搜噶,原来如此。通过model first生成的主外键关系默认就没有设计联级删除,而code first默认设置就是联级删除。
以上内容,都是我胡说八道。谢谢您的阅读,希望对您有那么一点点作用。
Hi-Blogs源码地址:http://git.oschina.net/zhaopeiym/Hi-Blogs
最近因为工作实在太慢,开源博客长久没有更新。今天突然来回翻了好几遍,发现半年前的自己写的代码是如此的不堪入目。
今天仅仅只是把db first改成了code first,发霉的代码我还得找个时间好好重构重构。
- 20楼梁逸晨
- entity framework core 中,可以选择不加载migration模块,真正纯净的codefirst,_migrationhistory表都可以不要了,假如以后数据库结构有变动的话,手动到数据库里面修改结构(别说什么生产环境……、大规模……、团队……,不信你一次能改多少条)。
- Re: 农码一生
- @梁逸晨,引用entity framework core 中,可以选择不加载migration模块,真正纯净的codefirst,_migrationhistory表都可以不要了,假如以后数据库结构有变动的话,手动到数据库里面修改结构(别说什么生产环境……、大规模……、团队……,不信你一次能改多少条)。 ,手动改数据库表结构,总感觉还是有点麻烦。,执行两条命令同步了数据库结构,这感觉是超爽的啊,特别是新项目的开发期!
- 19楼懒得安分
- 最近项目也打算用code first。支持下!
- Re: 农码一生
- @懒得安分,引用最近项目也打算用code first。支持下! ,,感谢支持!
- 18楼最爱晴天
- 楼主可以看下使用EF Power Tools来逆向生成,另外我请教下楼主,你们公司如果数据库结构变更的话,是怎么更新到外网的呢?
- Re: 农码一生
- @最爱晴天,引用楼主可以看下使用EF Power Tools来逆向生成,另外我请教下楼主,你们公司如果数据库结构变更的话,是怎么更新到外网的呢? ,1、EF Power Tools 不知道是什么(等会去了解下,呵呵),2、数据结构变更发布外网问题。,我们项目还在开发中,没有发布外网。如果内网可以直接访问服务器还好说,如果必须通过中间环境发布外网。还没有什么好的解决办法。(可以在中间环境安装vs,然后执行迁移命令),你们是怎么解决的?
- 17楼农码一生
- 比如?,引用多人协作,从设计到开发到单元测试,项目上线后期维护,感觉这些和code first方式并没有关系啊,,如果是项目大 用什么比较合适?又或者直接用ADO.NET?
- 16楼DJLNET
- entity framework power tools for code first
- Re: 农码一生
- @DJLNET,引用entity framework power tools for code first ,晕、还有这东西。得试试,谢谢告知!
- 15楼请叫我头头哥
- 用code first实乃明智之举。
- Re: 农码一生
- @请叫我头头哥,引用用code first实乃明智之举。 ,谁用谁知道
- 14楼SeayXu
- 不错,支持下
- Re: 农码一生
- @SeayXu,引用不错,支持下 ,谢谢支持!
- 13楼麦克刘.Mr
- 项目小凑合,项目大了够呛
- Re: 完美到迷惘
- @麦克刘.Mr,引用项目小凑合,项目大了够呛 ,请教下大项目一般采用什么方式。,现在还只用过 database first。
- Re: 农码一生
- @麦克刘.Mr,引用项目小凑合,项目大了够呛 ,当然,项目越大,修改越麻烦,难度越大。
- Re: 冲杀
- @麦克刘.Mr,引用项目小凑合,项目大了够呛 ,同解!个人已经遇到过这种情况!
- 12楼xchsp
- 一步步开发自己的博客 .NET版(9、从model first替换成code first 问题记录),多人开源博客,谢谢分享。
- 11楼会长
- 有没有rss订阅功能呢
- Re: 农码一生
- @会长,引用有没有rss订阅功能呢 ,还没做。
- 10楼刘泽华
- mark 最近在写基于sqlite 的code first ,小项目,不喜欢动,sql server 这种大数据集,嫌麻烦
- 9楼流浪的程序员
- 觉得以前的代码写的渣,是因为进步了
- Re: 农码一生
- @流浪的程序员,引用觉得以前的代码写的渣,是因为进步了 ,也许吧,哈哈
- 8楼h82258652
- 同喜欢code first,比起model first和database first,整个项目要相对干净得多,而且还能用Fluent API来扩展(用于一些不能修改model的项目)
- Re: 农码一生
- @h82258652,引用同喜欢code first,比起model first和database first,整个项目要相对干净得多,而且还能用Fluent API来扩展(用于一些不能修改model的项目) ,哈哈,谁用谁知道!
- 7楼lk_
- 爱慕爱 啊~mark
- 6楼九九哥
- 数据库经常变更的话,就需要经常去组数据库迁移么
- Re: 农码一生
- @九九哥,引用数据库经常变更的话,就需要经常去组数据库迁移么 ,是先改实体,然后改数据库。,(Add-Migration blogs、update-database两句命令搞的,从未如此简单、方便过!)
- 5楼枫伶忆
- 又开始写博客了,最近没有忙成狗么!!
- Re: 农码一生
- @枫伶忆,引用又开始写博客了,最近没有忙成狗么!! ,忙与不忙,都是老板一句话!(什么程度的压榨而已)
- 4楼ZuQing
- 赞一个
- Re: 农码一生
- @ZuQing,引用赞一个 ,谢谢支持。
- Re: 贤狼赫萝
- @ZuQing,卧槽 张祖清?
- 3楼gyxyhl
- 楼主,最近遇到一个问题不会解决,请问怎么处理?,,update xxxx set count=count+1 where id=1,,这个用ef怎么写啊?,,谢谢了
- Re: 农码一生
- @gyxyhl,引用楼主,最近遇到一个问题不会解决,请问怎么处理?,,update xxxx set count=count+1 where id=1,,这个用ef怎么写啊?,,谢谢了 ,先查再改 不就行了么
- 2楼Y2zz
- 不管项目大小,只要用了存储过程,可以放弃了
- 1楼任意球
- 从大项目转移确实够呛