一、 EF中每次查询都通过数据库,而不通过DBContext缓存的方法:
在Entity Framework (EF) 中,默认情况下,当你使用 “db.Set<T>().Find(id)【返回实体】” 或 “db.Set<T>().Where(condition)【返回IQueryable<T>,再FirstOrDefault()获取实体】” 方法查询实体时,如果DBContext上下文中已经存在相应的实体,它将返回缓存中的实体,而不会去数据库中查询。但如果你需要确保每次都从数据库中查询,不使用缓存,你可以使用 AsNoTracking
查询
using (var context = new YourDbContext())
{
var entity = context.Set<YourEntity>()
.AsNoTracking()
.Find(id);
// 这里的entity是从数据库中查询的,不会跟踪变化
}
二、 EF更新语句“只更新部分字段”和“更新所有字段”的可采用的方法:
假如说有个实体UserInfo ,其中的UserId是主键,定义个实体如下
UserInfo userInfonew = new UserInfo()
{
UserId=userInfo.UserId,
Email = userInfo.Email,
FirstName = userInfo.FirstName,
LastName = userInfo.LastName,
LastUpdateBy = GetCurrentUserGuid(),
LastUpdate = DateTime.Now
};
1、如果全部字段需要更新可以使用如下方法:
db.Entry(userInfonew).State = EntityState.Modified;
db.SaveChanges();
注意:
如上直接通过“db.Entry(userInfonew).State = EntityState.Modified”设置实体的State, 将会使实体的所有字段Property的IsModified都为True(这大概也解释了为什么直接设置实体State属性为Modifid会导致最终的SQL会是更新全部的字段);
而如果只是通过 “实体对象.字段=值”(如“userInfonew.Email="abc@qq.com"”)更新实体字段值,那么虽然也会使得db.Entry(userInfonew).State 变更为 EntityState.Modified,但这时实体的字段中只有Email字段状态为被修改,即:db.Entry(userInfonew).Property(x => x.Email).IsModified为true, 而其余字段的状态依然为未被修改的状态,比如:db.Entry(userInfonew).Property(x => x.FirstName).IsModified这时依然为false, 而在这种通过更改部分字段值导致读取 db.Entry(userInfonew).State已经是 EntityState.Modified的情况下,再次用 “db.Entry(userInfonew).State = EntityState.Modified”赋值,虽然实体的State本来就是EntityState.Modified赋值没变,但这时已经把所有字段的IsModified属性都设置成true了,即会造成: “db.Entry(userInfonew).Property(x=>x.FirstName).IsModified也变成了true【其余字段也同样】”
2、假如我们想更新部分字段,如只想更新Email的信可以采用以下方法:
db.UserInfoes.Attach(userInfonew);
db.Entry(userInfonew).Property(x => x.Email).IsModified = true;db.SaveChanges();
三、将实体所有字段都恢复到未修改前的值,即:第一次加载从数据库获取的初始值或执行Attach时值:
方法:db.Entry(userInfonew).State = EntityState.Unchanged;
设置实体State为Unchanged 不仅是改变了实体状态,还会将实体全部字段值都恢复到“刚从DB读取到的值” 或者 “执行完Attach实体时对应实体的字段值” (下文转载中也有对该特点的详细案例)
(以上总结都是经过实验验证过的)
下文转载自: https://www.cnblogs.com/OpenCoder/p/9839588.html
EF Core 中多次从DBContext查询实体数据,DbContext跟踪实体的情况
使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下。
数据库
首先我们的数据库中有一个Person表,其建表脚本如下:
CREATE TABLE [dbo].[Person]( [ID] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](50) NULL, [Age] [int] NULL, [CreateTime] [datetime] NULL, CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
其中ID是自增的主键,Name是一个Person的名字,Age是一个Person的年龄,CreateTime表示数据是何时创建的
其次我们的数据库中还有一个Book表,其建表脚本如下:
CREATE TABLE [dbo].[Book]( [ID] [int] IDENTITY(1,1) NOT NULL, [PersonID] [int] NULL, [BookName] [nvarchar](50) NULL, [BookDescription] [nvarchar](50) NULL, CONSTRAINT [PK_Book] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[Book] WITH CHECK ADD CONSTRAINT [FK_Book_Person] FOREIGN KEY([PersonID]) REFERENCES [dbo].[Person] ([ID]) GO ALTER TABLE [dbo].[Book] CHECK CONSTRAINT [FK_Book_Person] GO
其中ID是自增的主键,PersonID为外键,其对应Person表的主键列ID,BookName是一本书的名字,BookDescription是一本书的描述信息
Person表和Book表之间是一对多关系,外键FK_Book_Person,通过Person表的主键列ID和Book表的外键列PersonID关联,为强制约束(WITH CHECK),没有级联更新和级联删除。
实体
新建一个.NET Core控制台项目,我们在EF Core中用Scaffold-DbContext指令自动生成实体。
Person实体的代码如下:
using System; using System.Collections.Generic; namespace EFCoreMultipleSelections.Entities { public partial class Person { public Person() { Book = new HashSet<Book>(); } public int Id { get; set; } public string Name { get; set; } public int? Age { get; set; } public DateTime? CreateTime { get; set; } public ICollection<Book> Book { get; set; } } }
其每一个属性都和数据库Person表的列对应,导航属性Book是ICollection<Book>类型的集合,表示一个Person实体包含多个Book实体。
Book实体的代码如下:
using System; using System.Collections.Generic; namespace EFCoreMultipleSelections.Entities { public partial class Book { public int Id { get; set; } public int? PersonId { get; set; } public string BookName { get; set; } public string BookDescription { get; set; } public Person Person { get; set; } } }
其每一个属性都和数据库Book表的列对应,导航属性Person是一个Person实体,表示一个Book实体对应一个Person实体。
Scaffold-DbContext指令生成的DbContext类TestDBContext如下:
using EFCoreMultipleSelections.Logger; using Microsoft.EntityFrameworkCore; namespace EFCoreMultipleSelections.Entities { public partial class TestDBContext : DbContext { public TestDBContext() { } public TestDBContext(DbContextOptions<TestDBContext> options) : base(options) { } public virtual DbSet<Book> Book { get; set; } public virtual DbSet<Person> Person { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseLoggerFactory(new EFLoggerFactory()); optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=1qaz!QAZ;Database=TestDB"); } } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Book>(entity => { entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.BookDescription).HasMaxLength(50); entity.Property(e => e.BookName).HasMaxLength(50); entity.Property(e => e.PersonId).HasColumnName("PersonID"); entity.HasOne(d => d.Person) .WithMany(p => p.Book) .HasForeignKey(d => d.PersonId) .HasConstraintName("FK_Book_Person"); }); modelBuilder.Entity<Person>(entity => { entity.Property(e => e.Id).HasColumnName("ID"); entity.Property(e => e.CreateTime).HasColumnType("datetime"); entity.Property(e => e.Name).HasMaxLength(50); }); } } }
可以看到没什么特别的,与数据库中Person表和Book表一致,Fluent API设置了它们之间的一对多关系和外键。
测试
.NET Core控制台项目中Program类的代码如下:
using EFCoreMultipleSelections.Entities; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; namespace EFCoreMultipleSelections { class Program { /// <summary> /// 初始化数据库Person表和Book表的数据,删除数据库中Person表和Book表的老数据,并重新插入三条Person数据和三条Book数据 /// </summary> static void InitData() { using (TestDBContext dbContext = new TestDBContext()) { dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Book]"); dbContext.Database.ExecuteSqlCommand("DELETE FROM [dbo].[Person]"); var jim = new Person() { Name = "Jim", Age = 20, CreateTime = DateTime.Now }; var tom = new Person() { Name = "Tom", Age = 25, CreateTime = DateTime.Now }; var bill = new Person() { Name = "Bill", Age = 30, CreateTime = DateTime.Now }; //Tom拥有三本书 tom.Book = new List<Book>() { new Book(){ BookName="Chinese", BookDescription="Chinese"}, new Book(){ BookName="English", BookDescription="English"}, new Book(){ BookName="Japanese", BookDescription="Japanese"} }; dbContext.Person.Add(jim); dbContext.Person.Add(tom); dbContext.Person.Add(bill); dbContext.SaveChanges(); } } /// <summary> /// 两次从数据库中查询Person实体tom,更改其Age属性值 /// </summary> static void SelectData() { using (TestDBContext dbContext = new TestDBContext()) { var tom = dbContext.Person.First(p => p.Name == "Tom");//第一次从数据库中查询Person实体tom Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//由于此时Person实体tom才从数据库中被查出来,所以此时其EntityState为Unchanged Console.WriteLine($"Tom's age is : {tom.Age.ToString()} before change");//此时Person实体tom的Age输出为25,和数据库初始化数据一样 tom.Age = tom.Age + 10;//更改Person实体tom的Age,增加10 Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after change");//此时Person实体tom的Age输出为35,为加10后的值 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//调用DbContext.Entry()方法后,DbContext发现了Person实体tom的Age值已经被更改,所以这里Person实体tom的EntityState为Modified dbContext.Database.ExecuteSqlCommand("UPDATE [dbo].[Person] SET AGE=AGE+30 WHERE [Name]=N'Tom'");//执行这行代码后,数据库Person表中,Tom的Age会变为55 Console.WriteLine(); var tomAgain = dbContext.Person.First(p => p.Name == "Tom");//第二次从数据库中查询Person实体tom,但赋值给另一个变量tomAgain Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after change");//此时Person实体tom的Age输出还是为35,并不是数据库中Person表的实际值55 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//此时Person实体tom的EntityState还是为Modified Console.WriteLine($"TomAgin's age is : {tomAgain.Age.ToString()}");//Person实体tomAgain的Age输出为35,并不是数据库中Person表的实际值55 Console.WriteLine($"TomAgin's entity state is : {dbContext.Entry(tomAgain).State.ToString()}");//Person实体tomAgain的EntityState为Modified Console.WriteLine($"Whether Tom is TomAgin ? {(tom == tomAgain).ToString()}");//输出为true,表明tom和tomAgain指向的是同一个实体对象 } } /// <summary> /// 两次从数据库中查询Person实体tom,更改其Age属性值,在第二次查询前,将Person实体tom的EntityState设置回Unchanged /// </summary> static void SelectDataWithUnchanged() { using (TestDBContext dbContext = new TestDBContext()) { var tom = dbContext.Person.First(p => p.Name == "Tom");//第一次从数据库中查询Person实体tom Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//由于此时Person实体tom才从数据库中被查出来,所以此时其EntityState为Unchanged Console.WriteLine($"Tom's age is : {tom.Age.ToString()} before change");//此时Person实体tom的Age输出为25,和数据库初始化数据一样 tom.Age = tom.Age + 10;//更改Person实体tom的Age,增加10 Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after change");//此时Person实体tom的Age输出为35,为加10后的值 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//调用DbContext.Entry()方法后,DbContext发现了Person实体tom的Age值已经被更改,所以这里Person实体tom的EntityState为Modified dbContext.Database.ExecuteSqlCommand("UPDATE [dbo].[Person] SET AGE=AGE+30 WHERE [Name]=N'Tom'");//执行这行代码后,数据库Person表中,Tom的Age会变为55 Console.WriteLine(); dbContext.Entry(tom).State = EntityState.Unchanged;//更改Person实体tom的EntityState为Unchanged Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after unchanged");//此时Person实体tom的Age属性值变回25,为第一次刚从数据库中查询出来时的值(var tom = dbContext.Person.First(p => p.Name == "Tom")) Console.WriteLine(); var tomAgain = dbContext.Person.First(p => p.Name == "Tom");//第二次从数据库中查询Person实体tom,但赋值给另一个变量tomAgain Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after second query");//此时Person实体tom的Age输出还是为25,并不是数据库中Person表的实际值55 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//此时Person实体tom的EntityState还是为Unchanged Console.WriteLine($"TomAgin's age is : {tomAgain.Age.ToString()}");//Person实体tomAgain的Age输出为25,并不是数据库中Person表的实际值55 Console.WriteLine($"TomAgin's entity state is : {dbContext.Entry(tomAgain).State.ToString()}");//Person实体tomAgain的EntityState为Unchanged Console.WriteLine($"Whether Tom is TomAgin ? {(tom == tomAgain).ToString()}");//输出为true,表明tom和tomAgain指向的是同一个实体对象 } } /// <summary> /// 两次从数据库中查询Person实体tom,更改其Age属性值,在第二次查询前,调用DbContext.Attach方法,将Person实体tom重新Attach到DbContext被跟踪的实体集合中 /// </summary> static void SelectDataWithAttach() { using (TestDBContext dbContext = new TestDBContext()) { var tom = dbContext.Person.First(p => p.Name == "Tom");//第一次从数据库中查询Person实体tom Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//由于此时Person实体tom才从数据库中被查出来,所以此时其EntityState为Unchanged Console.WriteLine($"Tom's age is : {tom.Age.ToString()} before change");//此时Person实体tom的Age输出为25,和数据库初始化数据一样 tom.Age = tom.Age + 10;//更改Person实体tom的Age,增加10 Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after change");//此时Person实体tom的Age输出为35,为加10后的值 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//调用DbContext.Entry()方法后,DbContext发现了Person实体tom的Age值已经被更改,所以这里Person实体tom的EntityState为Modified dbContext.Database.ExecuteSqlCommand("UPDATE [dbo].[Person] SET AGE=AGE+30 WHERE [Name]=N'Tom'");//执行这行代码后,数据库Person表中,Tom的Age会变为55 Console.WriteLine(); dbContext.Attach(tom);//调用DbContext.Attach方法,将Person实体tom重新Attach到DbContext被跟踪的实体集合中 Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after attach");//此时Person实体tom的Age输出还是为35,为加10后的值 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()} after attach"); //由于上面调用了DbContext.Attach方法,此时Person实体tom的EntityState变回Unchanged Console.WriteLine(); var tomAgain = dbContext.Person.First(p => p.Name == "Tom");//第二次从数据库中查询Person实体tom,但赋值给另一个变量tomAgain Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after second query");//此时Person实体tom的Age输出还是为35,并不是数据库中Person表的实际值55 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//此时Person实体tom的EntityState还是为Unchanged Console.WriteLine($"TomAgin's age is : {tomAgain.Age.ToString()}");//Person实体tomAgain的Age输出为35,并不是数据库中Person表的实际值55 Console.WriteLine($"TomAgin's entity state is : {dbContext.Entry(tomAgain).State.ToString()}");//Person实体tomAgain的EntityState为Unchanged Console.WriteLine($"Whether Tom is TomAgin ? {(tom == tomAgain).ToString()}");//输出为true,表明tom和tomAgain指向的是同一个实体对象 } } /// <summary> /// 两次从数据库中查询Person实体tom,更改其Age属性值,在第二次查询前,将Person实体tom从DbContext被跟踪的实体集合中Detach掉 /// </summary> static void SelectDataWithDetach() { using (TestDBContext dbContext = new TestDBContext()) { var tom = dbContext.Person.First(p => p.Name == "Tom");//第一次从数据库中查询Person实体tom Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//由于此时Person实体tom才从数据库中被查出来,所以此时其EntityState为Unchanged Console.WriteLine($"Tom's age is : {tom.Age.ToString()} before change");//此时Person实体tom的Age输出为25,和数据库初始化数据一样 tom.Age = tom.Age + 10;//更改Person实体tom的Age,增加10 Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after change");//此时Person实体tom的Age输出为35,为加10后的值 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//调用DbContext.Entry()方法后,DbContext发现了Person实体tom的Age值已经被更改,所以这里Person实体tom的EntityState为Modified dbContext.Database.ExecuteSqlCommand("UPDATE [dbo].[Person] SET AGE=AGE+30 WHERE [Name]=N'Tom'");//执行这行代码后,数据库Person表中,Tom的Age会变为55 dbContext.Entry(tom).State = EntityState.Detached;//从DbContext被跟踪的实体集合中Detach Person实体tom,之后DbContext不再跟踪Person实体tom Console.WriteLine(); var tomAgain = dbContext.Person.First(p => p.Name == "Tom");//第二次从数据库中查询Person实体tom,但赋值给另一个变量tomAgain Console.WriteLine($"Tom's age is : {tom.Age.ToString()} after change");//此时Person实体tom的Age输出还是为35 Console.WriteLine($"Tom's entity state is : {dbContext.Entry(tom).State.ToString()}");//此时Person实体tom的EntityState是Detached,因为前面我们通过改变Person实体tom的State属性,将其从DbContext中Detach掉了 Console.WriteLine($"TomAgin's