EntityFramework6学习总结6(导航属性-新增、事务的使用)

带有导航属性的字段(主外键关系),主子表同时插入数据,子表可以使用主表的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开启事物。

<think>嗯,用户问的是在.NET WebAPI项目中使用Entity Framework 9的ExecuteUpdate方法来更新整个列表。首先,我需要确认用户的具体需求。他们可能有一个实体列表,想要批量更新这些实体,而不是逐条更新,这样可以提高效率。 首先,我得回忆一下EF Core 7之后引入的ExecuteUpdate和ExecuteDelete方法,这些方法允许批量操作,而不需要先查询数据。但用户提到的是EF9,可能需要确认是否有新变化,不过目前EF Core 8是最新版本,可能用户是指EF Core 7或8,这里暂时假设是EF Core的版本。 接下来,用户想要更新整个列表,意味着可能有一个实体集合,每个实体都有需要更新的字段。例如,假设有一个Student实体,需要根据列表中的每个对象更新数据库中的对应记录。 使用ExecuteUpdate通常需要构建一个LINQ查询,对每个实体设置要更新的字段。但这里有个问题,因为ExecuteUpdate是批量操作,通常用于更新符合特定条件的所有记录,比如所有年龄大于20的学生。但如果每个实体的更新值不同,可能需要不同的处理方式。 这时候,可能需要考虑是否真的需要批量更新所有不同值的记录,因为ExecuteUpdate通常用于将相同的更新应用到符合条件的一组记录。如果每个实体需要不同的更新,可能需要逐条处理,或者寻找其他方法,比如使用原生SQL或存储过程,或者检查是否有其他ORM功能支持。 不过用户可能的情况是,虽然有一个列表,但所有记录都需要更新相同的字段,只是值不同。例如,从外部接收了一个列表,每个项都有ID和新的值,需要根据ID更新对应的记录。这时候可能需要遍历列表,对每个ID执行单独的ExecuteUpdate操作。例如: foreach (var item in list) { await dbContext.Students .Where(s => s.Id == item.Id) .ExecuteUpdateAsync(setters => setters .SetProperty(s => s.Name, item.Name) .SetProperty(s => s.Age, item.Age)); } 但这样的话,每个循环都会生成一个UPDATE语句,虽然比先查询再SaveChanges高效,但如果有大量记录,可能还是不够高效。这时候可能需要考虑使用原生SQL批量更新,或者使用EF Core 8的复杂类型和表值参数,但可能超出ExecuteUpdate的范围。 或者,用户可能误解了需求,希望一次性更新所有记录的某些字段为相同的值。例如,将所有学生的状态设置为已激活。这时候可以这样写: dbContext.Students.ExecuteUpdate(s => s.SetProperty(s => s.IsActive, true)); 但用户的问题是关于更新整个列表,可能每个列表项对应不同的实体,这时候需要更仔细的处理。 可能用户需要的是根据提供的列表中的每个对象,更新数据库中对应的实体。这时候需要遍历列表,逐个构建ExecuteUpdate查询,或者如果支持的话,使用更高效的批量操作方式。不过EF Core目前可能没有内置的方法来一次性处理这种情况,可能需要第三方库如EF Plus,或者使用ExecuteUpdate结合Case语句,但这可能比较复杂。 总结来说,用户可能需要遍历列表中的每个对象,根据ID或其他条件构建ExecuteUpdate语句,逐个执行。或者,如果所有更新都是相同的操作,可以一次性应用。需要根据具体情况选择方法,并注意性能影响。</think>在.NET WebAPI项目中,使用Entity Framework Core 9的`ExecuteUpdate`方法批量更新整个列表时,需明确**批量操作的条件和更新逻辑**。以下是分步说明: --- ### 1. **理解`ExecuteUpdate`的用途** - `ExecuteUpdate`是EF Core 7+新增的**批量更新方法**,直接生成`UPDATE`语句,无需加载实体到内存。 - 适用于**条件明确的批量更新**,例如“将所有符合条件的记录的字段A设为值B”。 - **注意**:若列表中每个实体的更新值不同,需遍历列表逐个处理。 --- ### 2. **场景假设** 假设有一个`Student`实体列表,需根据列表中每个对象的`Id`更新数据库中对应的记录: ```csharp public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } } ``` --- ### 3. **批量更新相同值** 若需将列表中所有记录的某个字段设为相同值(例如将所有年龄设为20): ```csharp await dbContext.Students .Where(s => yourListOfIds.Contains(s.Id)) // 根据条件筛选 .ExecuteUpdateAsync(setters => setters .SetProperty(s => s.Age, 20) // 统一更新Age字段 ); ``` --- ### 4. **逐条更新不同值** 若列表中每个实体的更新值不同,需遍历列表,逐个更新: ```csharp foreach (var student in yourStudentList) { await dbContext.Students .Where(s => s.Id == student.Id) .ExecuteUpdateAsync(setters => setters .SetProperty(s => s.Name, student.Name) .SetProperty(s => s.Age, student.Age) ); } ``` --- ### 5. **性能优化建议** - **减少数据库往返次数**:若列表较大,逐条更新可能效率低。可考虑: - 使用原生SQL或存储过程实现批量操作。 - 结合`DbContext`的`AddRange`/`UpdateRange`(需先加载实体)。 - **事务控制**:确保批量操作的原子性: ```csharp using var transaction = await dbContext.Database.BeginTransactionAsync(); try { // 执行更新操作 await transaction.CommitAsync(); } catch { await transaction.RollbackAsync(); throw; } ``` --- ### 6. **注意事项** - **条件必须明确**:`ExecuteUpdate`要求`WHERE`条件精准,否则可能误更新其他记录。 - **不支持导航属性**:只能更新当前实体直接映射的字段。 - **并发控制**:若涉及高并发场景,需结合`Version`字段或乐观锁机制。 --- ### 总结 - **统一更新**:直接用`ExecuteUpdate` + `Where`筛选。 - **差异更新**:遍历列表逐个处理,或结合其他批量操作库(如[EF Core BulkExtensions](https://github.com/borisdj/EFCore.BulkExtensions))。 - 根据场景选择最优方案,平衡性能与代码简洁性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值