EF如何确保每次都实时查数据库中数据、EF更新如何做到只更新部分或全部字段、EF恢复实体初始值、及 “EF Core 中多次查询实体数据,DbContext跟踪实体的情况(不会每次都从数据库查询)”

一、 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值