带有导航属性的字段(主外键关系),主子表同时插入数据,子表可以使用主表的ID。
表关系如下:新闻表外键(新闻类别ID)对应到新闻类别表主键。
实例:
using (NewsDbContext dbContext = new NewsDbContext())
{
//打印sql信息
dbContext.Database.Log += c => Console.WriteLine($"sql:{c}");
NewsClassify newsClassify = new NewsClassify()
{
Name = "娱乐新闻",
Sort = 4,
Remark = "快乐就完事了"
};
News news = new News()
{
NewsClassifyId = newsClassify.Id,
Title = "Angelababy扮部落首领保卫粮食",
Image = "/NewsPic/20200908174647.jpg",
Contents = "6月25日,Angelababy工作室曝光了一组baby在《奔跑吧》中的新造型,这次她扮演起部落首领保卫粮食,画面中,baby身穿草绿色流苏套装,腰间挂着织网,为配合衣着,baby还特意挑染了头发,戴上孔雀花纹耳环,一股自然的气息扑面而来。",
PublishDate = DateTime.Now,
Remark = "哈哈哈哈"
};
dbContext.Set<NewsClassify>().Add(newsClassify);
dbContext.Set<News>().Add(news);
dbContext.SaveChanges();
}
查询结果:
上面实例可以说明,调用一次SaveChanges方法,如果两个表有主外键关系,可以使用自增的ID。
如果两张表没有主外键关系,其中一张表在新增数据时要使用另一张表的主键,如果仅调用一次SaveChanges方法是不能获取到的。
如下图所示,新闻类别表和日志表,Log表的OperateUserID字段值取NewsClassify表的ID。
实例:
using (NewsDbContext dbContext = new NewsDbContext())
{
//打印sql信息
dbContext.Database.Log += c => Console.WriteLine($"sql:{c}");
NewsClassify newsClassify = new NewsClassify()
{
Name = "教育新闻",
Sort = 5,
Remark = "教育新闻"
};
NewsLog newsLog = new NewsLog()
{
OperateDate=DateTime.Now,
OperateUserID= newsClassify.Id,
LogContent="新增了一条新闻类别数据"
};
dbContext.Set<NewsClassify>().Add(newsClassify);
dbContext.Set<NewsLog>().Add(newsLog);
dbContext.SaveChanges();
}
执行结果:
NewsClassify表出入了一条ID为6的数据,但是NewsLog表的OperateUserID字段为0,并没有取6。
如果要解决上面的问题,可以对两个表分别调用SaveChanges方法。
using (NewsDbContext dbContext = new NewsDbContext())
{
//打印sql信息
dbContext.Database.Log += c => Console.WriteLine($"sql:{c}");
NewsClassify newsClassify = new NewsClassify()
{
Name = "科技新闻",
Sort = 6,
Remark = "科技新闻"
};
dbContext.Set<NewsClassify>().Add(newsClassify);
dbContext.SaveChanges();
NewsLog newsLog = new NewsLog()
{
OperateDate = DateTime.Now,
OperateUserID = newsClassify.Id,
LogContent = "新增了一条新闻类别数据-科技新闻"
};
dbContext.Set<NewsLog>().Add(newsLog);
dbContext.SaveChanges();
}
执行结果:
这样就可以保证NewsLog的OperateUserID等于新增的NewsClassify的ID。
不过上面的方式存在一个问题,无法保证两个操作在同一个事务中,因为调用一次SaveChanges方法就会开启一个事务。
下面引入事务,首先引用一个程序集System.Transactions
实例:
using (NewsDbContext dbContext = new NewsDbContext())
{
using (TransactionScope transaction = new TransactionScope())
{
//打印sql信息
dbContext.Database.Log += c => Console.WriteLine($"sql:{c}");
NewsClassify newsClassify = new NewsClassify()
{
Name = "科技新闻",
Sort = 6,
Remark = "科技新闻"
};
dbContext.Set<NewsClassify>().Add(newsClassify);
dbContext.SaveChanges();
NewsLog newsLog = new NewsLog()
{
OperateDate = DateTime.Now,
OperateUserID = newsClassify.Id,
LogContent = "新增了一条新闻类别数据-科技新闻"
};
dbContext.Set<NewsLog>().Add(newsLog);
dbContext.SaveChanges();
transaction.Complete();//提交事务
}
}
执行结果:
TransactionScope不仅可以对同一个DbContext的多次SaveChanges提供事务。还可以对不同DbContext提供事务,实例如下:
using (NewsDbContext dbContext = new NewsDbContext())
using (NewsDbContext dbContext1 = new NewsDbContext())
{
using (TransactionScope transaction = new TransactionScope())
{
//打印sql信息
dbContext.Database.Log += c => Console.WriteLine($"sql:{c}");
dbContext1.Database.Log += c => Console.WriteLine($"sql:{c}");
NewsClassify newsClassify = new NewsClassify()
{
Name = "交通新闻",
Sort = 8,
Remark = "交通新闻"
};
dbContext.Set<NewsClassify>().Add(newsClassify);
dbContext.SaveChanges();
NewsLog newsLog = new NewsLog()
{
OperateDate = DateTime.Now,
OperateUserID = newsClassify.Id,
LogContent = "新增了一条新闻类别数据-交通新闻"
};
dbContext1.Set<NewsLog>().Add(newsLog);
dbContext1.SaveChanges();
transaction.Complete();//提交事物
}
}
执行结果:
上面的实例中开启了两个DbContext,dbContext和dbContext1。其中dbContext对NewsClassify表做操作,dbContext1对NewsLog表做操作。
然后调用transaction.Complete()对两个DbContext的修改提交到数据库中。
EF中也可以使用DbContextTransaction开启一个事物,实例如下:
public static void Show()
{
using (NewsDbContext dbContext = new NewsDbContext())
{
//打印sql信息
dbContext.Database.Log += c => Console.WriteLine($"sql:{c}");
{
DbContextTransaction trans = null;
try
{
trans = dbContext.Database.BeginTransaction();//开启事物
string sql = "SELECT * FROM News where Id=@Id";
SqlParameter parameter = new SqlParameter("@Id", 2);
List<News> newList = dbContext.Database.SqlQuery<News>(sql, parameter).ToList();
trans.Commit();//提交事物
foreach (var item in newList)
{
Console.WriteLine(item.Title);
}
}
catch (Exception ex)
{
if (trans != null)
{
trans.Rollback();
}
throw ex;
}
finally
{
trans.Dispose();
}
}
}
}
执行结果:
总结:
1.如果两个表有主外键关系,调用一次SaveChanges方法,可以使用自增的ID。
2.如果两个表没有主外键关系,调用一次SaveChanges方法,则不可以使用自增的ID。
3.一次SaveChanges方法为一个事务。
4.如果要使多个SaveChanges方法在一个事务中,可以使用TransactionScope手动开启一个事务,此方法开启事务的级别高于SaveChanges的事务。所以即使某个SaveChanges成功了,但是有任一SaveChanges失败,则成功的SaveChanges也不会更新到数据库中。只有调用了TransactionScope.Complete()才会把所有SaveChanges的更改保存到数据库中。
5.TransactionScope可以完成一个DbContext的多次SaveChanges。
6.TransactionScope可以完成不同DbContext的事务。
7.EF中也可以使用DbContextTransaction开启事物。