EF里单个实体的增查改删以及主从表关联数据的各种增删改查

简介: 原文:EF里单个实体的增查改删以及主从表关联数据的各种增删改查本文目录 EF对单个实体的增查改删 增加单个实体 查询单个实体 修改单个实体 删除单个实体 EF里主从表关联数据的各种增删改查 增加(增加从表数据、增加主从表数据) 查询(查询导航属性为集合、查询导航属性...
原文: EF里单个实体的增查改删以及主从表关联数据的各种增删改查

本文目录


注:本章节多次演示了各种删除,若要重复查看效果,需解开注释初始化数据的方法。

一、EF对单个实体的增查改删

增加单个实体:

        /// <summary>
        /// 增加单个实体
        /// </summary>
        private static void AddMachuPicchu()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var mauchuPicchu = new DbContexts.Model.Destination
                {
                    Name = "Machu Picchu",
                    Country = "Peru"
                };
                context.Destinations.Add(mauchuPicchu);
                context.SaveChanges();
            }
        }

产生的insert sql:

exec sp_executesql N'insert [baga].[Locations]([LocationName], [Country], [Description], [Photo], [TravelWarnings], [ClimateInfo])
values (@0, @1, null, null, null, null)
select [LocationID]
from [baga].[Locations]
where @@ROWCOUNT > 0 and [LocationID] = scope_identity()',N'@0 nvarchar(200),@1 nvarchar(max) ',@0=N'Machu Picchu',@1=N'Peru'

查询单个实体:

        /// <summary>
        /// 查询单个实体
        /// </summary>
        private static void GetGreatBarrierReef()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var destination = context.Destinations.Find(4);
                Console.WriteLine(destination.Name);
            }
        }

生成的select sql(find方法生成的查询sql略复杂点,普通的linq查询或者Lambda表达式写法就简单许多了):

exec sp_executesql N'SELECT 
[Limit1].[LocationID] AS [LocationID], 
[Limit1].[LocationName] AS [LocationName], 
[Limit1].[Country] AS [Country], 
[Limit1].[Description] AS [Description], 
[Limit1].[Photo] AS [Photo], 
[Limit1].[TravelWarnings] AS [TravelWarnings], 
[Limit1].[ClimateInfo] AS [ClimateInfo]
FROM ( SELECT TOP (2) 
    [Extent1].[LocationID] AS [LocationID], 
    [Extent1].[LocationName] AS [LocationName], 
    [Extent1].[Country] AS [Country], 
    [Extent1].[Description] AS [Description], 
    [Extent1].[Photo] AS [Photo], 
    [Extent1].[TravelWarnings] AS [TravelWarnings], 
    [Extent1].[ClimateInfo] AS [ClimateInfo]
    FROM [baga].[Locations] AS [Extent1]
    WHERE [Extent1].[LocationID] = @p0
)  AS [Limit1]',N'@p0 int',@p0=4

修改单个实体:

        /// <summary>
        /// 修改单个实体
        /// </summary>
        private static void ChangeGrandCanyon()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var canyon = (from d in context.Destinations
                              where d.Name == "Grand Canyon"
                              select d).Single();
                canyon.Description = "227 mile long canyon.";
                context.SaveChanges();
            }
        }

产生的update sql:

exec sp_executesql N'update [baga].[Locations]
set [Description] = @0
where ([LocationID] = @1)
',N'@0 nvarchar(500),@1 int',@0=N'227 mile long canyon.',@1=1

删除单个实体:

        /// <summary>
        /// 删除单个实体
        /// </summary>
        private static void DeleteWineGlassBay()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var bay = (from d in context.Destinations
                           where d.Name == "Wine Glass Bay"
                           select d).Single();
                context.Destinations.Remove(bay);
                context.SaveChanges();
            }
        }

产生的delete sql:

exec sp_executesql N'delete [baga].[Locations]
where ([LocationID] = @0)',N'@0 int',@0=3

删除方法这样写可能有点效率问题:要删除一个实体,只要知道它的id就可以了,但是上面的方法却先加载了这个实体到内存中,这个是多余的步骤。使用attach方法改进:

        /// <summary>
        /// 删除单个实体attach
        /// </summary>
        private static void DeleteWineGlassBayAttach()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var toDelete = new DbContexts.Model.Destination { DestinationId = 2 };
                context.Destinations.Attach(toDelete);  //attach

                context.Destinations.Remove(toDelete);
                context.SaveChanges();
            }
        }

自然就没有了先加载实体到内存中的sql,只有一个简单的删除sql。attach方法是让EF知道DestinationId为2的实体是一个存在的实体。当然不使用attach,直接调用Remove方法删除会报一个InvalidOperationException错:无法删除此对象,因为未在 ObjectStateManager中找到它。
attach中文意为“连接、附上、贴上”等意思。
注:Attach的实体事先不能已经在内存中,否则上下文会追踪到两个相同键名的实体,并且会报一个InvalidOperationException错:An object with the same key already exists in the ObjectStateManager.

还有一种不加载实体到内存就可以删除实体的简单方法,用EF直接执行sql:

        /// <summary>
        /// 删除单个实体ExecuteSqlCommand
        /// </summary>
        private static void DeleteWineGlassBayExecuteSqlCommand()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                context.Database.ExecuteSqlCommand("delete from baga.Locations where LocationName = 'Hawaii'");
            }
        }

可见,都不需要调用上下文的SaveChanges方法了,因为是直接执行sql,所以并不需要被数据库上下文跟踪到任何状态。
ok,对于单个的增删改查就是这么简单,有Linq的写法,也有Lambda表达式的写法,都很简单,下面看复杂点的。

 

二、主从表关联数据的各种增删改查

1.增加
主从表数据的添加分为:仅添加从表数据、添加主表同时增加相关联的从表数据

仅添加从表数据:

        /// <summary>
        /// 添加从表数据
        /// </summary>
        private static void NewGrandCanyonResort()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var resort = new DbContexts.Model.Resort
                {
                    Name = "Pete's Luxury Resort"
                };
                context.Lodgings.Add(resort);

                var canyon = (from d in context.Destinations
                              where d.Name == "Grand Canyon"
                              select d).Single();
                canyon.Lodgings.Add(resort);
                context.SaveChanges();
            }
        }

Lodging是住宿类,有两个类继承本类,分别Resort度假村类和Hostel宿舍类。上面的方法添加了一个Grand Canyon景点的度假村,Name是Pete's Luxury Resort。这里的Grand Canyon是主表数据,Pete's Luxury Resort是从表数据。跟踪到的sql:

exec sp_executesql N'insert [dbo].[Lodgings]([Name], [Owner], [MilesFromNearestAirport], [destination_id], [PrimaryContactId], [SecondaryContactId], [Entertainment], [Activities], [MaxPersonsPerRoom], [PrivateRoomsAvailable], [Discriminator])
values (@0, null, @1, @2, null, null, null, null, null, null, @3)
select [LodgingId]
from [dbo].[Lodgings]
where @@ROWCOUNT > 0 and [LodgingId] = scope_identity()',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 nvarchar(128)',@0=N'Pete''s Luxury Resort',@1=0,@2=1,@3=N'Resort'

这跟单个实体的增加区别就是这样增加出来的数据外键值是有的,不是一个独立的实体了,是一个指向主表某条数据的从表数据。并且注意看sql,最后一列是Discriminator,是用来区分属于哪个表的数据,因为Resort度假村类跟Destination没有直接的关系,而是继承的住宿类Lodging跟Destination是多对一的关系。具体点这里了解。

添加主表数据同时添加相关联的从表数据:

        /// <summary>
        /// 添加主表数据同时添加相关联的从表数据
        /// </summary>
        private static void AddSingleAndRelatedData()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var destination = new DbContexts.Model.Destination
                {
                    Name = "AnHui HuangShan",
                    Lodgings = new List<DbContexts.Model.Lodging>
                    { 
                        new DbContexts.Model.Lodging {Name="HuangShan Hotel"},
                        new DbContexts.Model.Lodging {Name="YingKeSong Hotel"}
                    }
                };
                context.Destinations.Add(destination);
                context.SaveChanges();
            }
        }

监控到三段sql,分别是添加主表数据,和两条添加相关联的从表数据,它们是通过外键destination_id相关联的:

exec sp_executesql N'insert [baga].[Locations]([LocationName], [Country], [Description], [Photo], [TravelWarnings], [ClimateInfo])
values (@0, null, null, null, null, null)
select [LocationID]
from [baga].[Locations]
where @@ROWCOUNT > 0 and [LocationID] = scope_identity()',N'@0 nvarchar(200)',@0=N'AnHui HuangShan'
exec sp_executesql N'insert [dbo].[Lodgings]([Name], [Owner], [MilesFromNearestAirport], [destination_id], [PrimaryContactId], [SecondaryContactId], [Entertainment], [Activities], [MaxPersonsPerRoom], [PrivateRoomsAvailable], [Discriminator])
values (@0, null, @1, @2, null, null, null, null, null, null, @3)
select [LodgingId]
from [dbo].[Lodgings]
where @@ROWCOUNT > 0 and [LodgingId] = scope_identity()',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 nvarchar(128)',@0=N'HuangShan Hotel',@1=0,@2=5,@3=N'Lodging'
exec sp_executesql N'insert [dbo].[Lodgings]([Name], [Owner], [MilesFromNearestAirport], [destination_id], [PrimaryContactId], [SecondaryContactId], [Entertainment], [Activities], [MaxPersonsPerRoom], [PrivateRoomsAvailable], [Discriminator])
values (@0, null, @1, @2, null, null, null, null, null, null, @3)
select [LodgingId]
from [dbo].[Lodgings]
where @@ROWCOUNT > 0 and [LodgingId] = scope_identity()',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 nvarchar(128)',@0=N'YingKeSong Hotel',@1=0,@2=5,@3=N'Lodging'

注意看第一段sql,使用了scope_identity(),这个和ado.net里在每条insert的sql后加上;SELECT @@IDENTITY是一个意思,它会返回自增长的主键id。这里当然是需要返回主键id的,因为后面从表的数据需要用这个当外键。可以复制第一条sql到数据库环境里执行看看效果。

2.查找
根据主表找从表数据(显示加载:先Entry,然后Collection):
2013.09.04修改:之前描述的“根据主表找从表数据”有错误,Collection不是主表找从表,而是找导航属性是一个集合的。

        /// <summary>
        /// 查找导航属性为一个集合的
        /// </summary>
        private static void LoadRelateData()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var canyon = (from d in context.Destinations
                              where d.Name == "Grand Canyon"
                              select d).Single();

                context.Entry(canyon).Collection(d => d.Lodgings).Load();  //显示加载

                foreach (var lodging in context.Lodgings.Local)   //遍历的是内存中Lodgings的数据
                {
                    Console.WriteLine(lodging.Name);
                }
            }
        }

根据从表找主表数据(显示加载:先Entry,然后Reference):
2013.09.04修改:之前描述的“根据从表找主表数据”有错误,Reference不仅仅是根据从表找主表,而是找导航属性是一个实体对象的,常用于一对多关系里从表对象找主表对象,也可以用于一对一关系的查找。

        /// <summary>
        /// 查找导航属性为一个实体对象的
        /// </summary>
        private static void LoadPrimaryKeyData()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var lodging = context.Lodgings.First();
                //context.Entry(lodging).Reference(l => l.PrimaryContact).Load();
                context.Entry(lodging).Reference(l => l.Destination).Load();

                foreach (var destination in context.Destinations.Local)   //遍历的是内存中的Destinations数据
                {
                    Console.WriteLine(destination.Name);
                }
            }
        }

这是EF标准的查询关联表的数据。如果不看官方的API,大家会怎么查呢?我想是这样:先拿到主表主键id,然后根据id使用find方法(甚至使用ExcuteSqlCommad发送sql)去从表里查,最后得到结果集。从表查主表也一样。这样写有什么不好呢?语句多了不少,其次不是EF建议的写法,我个人还是建议使用Entry配合Collection和Reference方法。

3.修改
修改从表的外键:

        /// <summary>
        /// 修改从表的外键
        /// </summary>
        private static void ChangeLodgingDestination()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var hotel = (from l in context.Lodgings
                             where l.Name == "Grand Hotel"
                             select l).Single();
                var reef = (from d in context.Destinations
                            where d.Name == "Great Barrier Reef"
                            select d).Single();

                hotel.Destination = reef;
                context.SaveChanges();
            }
        }

Grand Hotel本来的外键是LocationId为1的Grand Canyon,代码把它修改成到了LocationId为4的Great Barrier Reef下。生成的sql简单明了:

exec sp_executesql N'update [dbo].[Lodgings]
set [destination_id] = @0
where ([LodgingId] = @1)
',N'@0 int,@1 int',@0=4,@1=1

4.删除
删除分为:删除主从表关系、删除主表数据不删除相关联的从表数据、同时删除主从表数据(级联和不级联删除)、删除主表数据同时修改相关联的从表数据指向另一个主表实体

删除主从表关系:主从表的关系是通过从表的外键列确定的,只需要赋值从表外键列为null即可

        /// <summary>
        /// 删除主从表关系(ForeignKeys方式)
        /// </summary>
        private static void RemovePrimaryContactForeignKeys()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var davesDump = (from l in context.Lodgings
                                 where l.Name == "Dave's Dump"
                                 select l).Single();
                davesDump.PrimaryContactId = null;
                context.SaveChanges();
            }
        }

另一种方式:

        /// <summary>
        /// 删除主从表关系(Reference方式)
        /// </summary>
        private static void RemovePrimaryContactReference()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var davesDump = (from l in context.Lodgings
                                 where l.Name == "Dave's Dump"
                                 select l).Single();
                context.Entry(davesDump).Reference(l => l.PrimaryContact).Load();  //找主表数据
                davesDump.PrimaryContact = null;  //清空
                context.SaveChanges();
            }
        }

住宿类Lodging跟人类Person有一个多对一的关系,这个很好理解,一个人可以有多个酒店。Dave's Dump这个住宿的地方本来本来是PrimaryContactId为1,也就是PersionId为1的这个人的记录,上面的两个方法都是修改这个1为空,即这个Dave's Dump这个住宿的地方不属于任何人了。看看生成的sql:

exec sp_executesql N'update [dbo].[Lodgings]
set [PrimaryContactId] = null
where ([LodgingId] = @0)
',N'@0 int',@0=2

 

删除主表数据不删除相关联的从表数据:
ok,先介绍两个新的实体:

    /// <summary>
    /// 旅行类
    /// </summary>
    public class Trip
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid Identifier { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public string Description { get; set; }
        public decimal CostUSD { get; set; }
        [Timestamp]
        public byte[] RowVersion { get; set; }

        public int DestinationId { get; set; }
        [Required]
        public Destination Destination { get; set; }
        public List<Activity> Activities { get; set; }
    }
    /// <summary>
    /// 预约类
    /// </summary>
    public class Reservation
    {
        public Reservation()
        {
            Payments = new List<Payment>();
        }
        public int ReservationId { get; set; }
        public DateTime DateTimeMade { get; set; }
        public Person Traveler { get; set; }
        public Trip Trip { get; set; }
        public Nullable<DateTime> PaidInFull { get; set; }

        public List<Payment> Payments { get; set; }
    }

没有配置任何Data Annotation和Fluent API。两个实体的关系是通过Reservation类的Trip导航属性确立的。很明显,这是一个一对一的关系,且预约类Reservation的外键Trip_Identifier是可空的(为何生成的外键名是Trip_Identifier?EF默认映射是取主表实体类名字加主键列),意思很明确,就是预约表Reservations的数据可以对应到旅行表Trip,也可以不对应:

在两张表任何一张上右键 - 关系查看下这个主外键关系的设置。删除规则为:不执行任何操作。意思是删除主表数据,对应的从表数据不会被删除;如果打开了级联删除,那么发送一条删除主表数据的sql到数据库,数据库不仅会删除主表数据,也会自动删除对应的从表数据。使用Optional系列方法配置的关系不会打开级联删除。要了解更多级联删除的知识请点击

看看这两张表在数据库里有的数据:

很明显,Reservations预约表的Trip_Identifier列(guid类型)指向了Trips表的主键列Identifier。试着删除:

        /// <summary>
        /// 不加载从表数据直接删除主表数据
        /// </summary>
        private static void DeleteTrip()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var trip = (from t in context.Trip
                            where t.Description == "Trip from the database"
                            select t).Single();

                context.Trip.Remove(trip);
                context.SaveChanges();
            }
        }

根据Description列的内容从数据库取出主表Trips的某条数据,然后直接调用上下文的Remove方法删除。程序跑起来会报一个DbUpdateException错:

违反了主外键的约束。这个很好理解:从表的某条数据指向主表的这条数据,主表的这条数据自然不能随便删除。修改下方法,删除主表某条数据,同时加载其关联的从表数据:

        /// <summary>
        /// 同时加载从表数据
        /// </summary>
        private static void DeleteTripLoadRelateData()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var trip = (from t in context.Trip
                            where t.Description == "Trip from the database"
                            select t).Single();

                var res = (from r in context.Reservations
                           where r.Trip.Description == "Trip from the database"
                           select r).Single();

                context.Trip.Remove(trip);
                context.SaveChanges();
            }
        }

看看这几行代码生成了多少sql:
1.查出主表数据:

SELECT TOP (2) 
[Extent1].[Identifier] AS [Identifier], 
[Extent1].[StartDate] AS [StartDate], 
[Extent1].[EndDate] AS [EndDate], 
[Extent1].[Description] AS [Description], 
[Extent1].[CostUSD] AS [CostUSD], 
[Extent1].[RowVersion] AS [RowVersion], 
[Extent1].[DestinationId] AS [DestinationId]
FROM [dbo].[Trips] AS [Extent1]
WHERE N'Trip from the database' = [Extent1].[Description]

2.查出从表数据:

SELECT TOP (2) 
[Extent1].[ReservationId] AS [ReservationId], 
[Extent1].[DateTimeMade] AS [DateTimeMade], 
[Extent1].[PaidInFull] AS [PaidInFull], 
[Extent1].[Traveler_PersonId] AS [Traveler_PersonId], 
[Extent1].[Trip_Identifier] AS [Trip_Identifier]
FROM  [dbo].[Reservations] AS [Extent1]
INNER JOIN [dbo].[Trips] AS [Extent2] ON [Extent1].[Trip_Identifier] = [Extent2].[Identifier]
WHERE N'Trip from the database' = [Extent2].[Description]

3.更新从表的外键为null:

exec sp_executesql N'update [dbo].[Reservations]
set [Trip_Identifier] = null
where (([ReservationId] = @0) and ([Trip_Identifier] = @1))
',N'@0 int,@1 uniqueidentifier',@0=1,@1='D928B2FE-C667-49C2-BEAC-E9425A12F580'

4.删除主表数据:

exec sp_executesql N'delete [dbo].[Trips]
where (([Identifier] = @0) and ([RowVersion] = @1))',N'@0 uniqueidentifier,@1 binary(8)',@0='D928B2FE-C667-49C2-BEAC-E9425A12F580',@1=0x00000000000007D1

看完了你肯定会想EF删除主表数据真麻烦:同时加载主表和从表的数据,然后设置从表外键为null让它不指向主表任何数据,然后再删除主表数据。
正常的思维删除主表数据是这样的:取出主表的主键字段,然后根据主键去从表里找,看看有没有相关联的数据,有就赋值外键为null,最后删除主表数据。写出来无非就是各种find,然后update,最后delete。这是正常的思维和写法,但是缺点很明显:比上面的方法多写了很多代码。
所以,还是按照EF的思路来:删除主表数据,就同时加载主表和从表数据到内存中再执行删除主表数据的操作。只需要直接调用Remove方法就好,EF自动把从表的相关数据外键列设置为null。

删除主表数据同时删除相关联的从表数据(级联删除)

        /// <summary>
        /// 显示加载从表数据
        /// </summary>
        private static void DeleteGrandCanyonLoadRelateData()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var canyon = (from d in context.Destinations
                              where d.Name == "Grand Canyon"
                              select d).Single();

                context.Entry(canyon).Collection(d => d.Lodgings).Load();  //显示加载
                //不调用Load,也可以先调用Query方法,在内存中执行需要的操作再把结果集加载到内存中,效率!比如:
                //context.Entry(canyon).Collection(d => d.Lodgings).Query().Where(l => l.Name.Contains("Hotel")).Load();
                context.Destinations.Remove(canyon);
                context.SaveChanges();
            }
        }

因为Destination类和Lodging类已经设置好了级联删除,所以直接找到主键删除即可,相关联的从表数据由数据库自动删除:

        /// <summary>
        /// 级联删除:不加载从表数据(数据库里必须设置是级联删除)
        /// </summary>
        private static void DeleteGrandCanyonWithoutLoadRelateData()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var canyon = (from d in context.Destinations
                              where d.Name == "Grand Canyon"
                              select d).Single();

                context.Destinations.Remove(canyon);
                context.SaveChanges();
            }
        }

删除主表数据同时删除相关联的从表数据(非级联删除)

标注每个从表的数据为删除状态,然后调用数据库上下文的SaveChanges方法:

        /// <summary>
        /// 普通删除:删除主表数据,同时标注从表数据为删除状态(数据库关闭了级联删除的情况,可以手动去数据库的外键关系修改,也可以Fluent API配置关闭级联删除)
        /// </summary>
        private static void DeleteGrandCanyonAndMarkChildEntitiesDeletion()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var canyon = (from d in context.Destinations
                              where d.Name == "Grand Canyon"
                              select d).Single();

                foreach (var lodging in canyon.Lodgings.ToList())
                {
                    context.Lodgings.Remove(lodging);   //先标记相关的从表数据为删除状态
                }
                context.Destinations.Remove(canyon);    //再标记主表数据为删除装填
                context.SaveChanges();   //执行上面的所有标记
            }
        }

 

删除主表数据同时修改相关联的从表数据指向另一个主表实体:

        /// <summary>
        /// 普通删除:删除主表数据,同时设置从表数据指向另一个主键(数据库默认打开关闭级联删除都可以)
        /// </summary>
        private static void DeleteGrandCanyonAndChangeChildEntitiesPrimaryKey()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                //找到要删除的主表数据
                var canyon = (from d in context.Destinations
                              where d.Name == "Grand Canyon"
                              select d).Single();
                //找到和主表数据相关的从表数据并修改其主键值,让这些相关的从表数据指向另一个存在的主表数据
                var hawaii = context.Destinations.Find(2);   //hawaii此时在数据库的主键是2(find方法生成的sql稍复杂,建议使用下面的普通写法)
                //var hawaii = (from d in context.Destinations
                //              where d.DestinationId == 2
                //              select d).Single();

                foreach (var lodging in canyon.Lodgings.ToList())
                {
                    lodging.Destination = hawaii;
                }
                //最后删除主表数据,可以此时只是单独的删除主表数据,它已经没有了相关的从表数据了
                context.Destinations.Remove(canyon);
                context.SaveChanges();
            }
        }

 

补充内容:以上所有的演示调用SaveChanges都是提交一个更改,试着提交多个操作:

        /// <summary>
        /// 一次提交多个修改
        /// </summary>
        private static void MakeMultipleChanges()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var niagaraFalls = new DbContexts.Model.Destination
                {
                    Name = "Niagara Falls",
                    Country = "USA"
                };
                context.Destinations.Add(niagaraFalls);
                var wineGlassBay = (from d in context.Destinations
                                    where d.Name == "Wine Glass Bay"
                                    select d).Single();
                wineGlassBay.Description = "Picturesque bay with beaches";
                context.SaveChanges();
            }
        }

增加一个Destinations表对象,又修改了一个对象,跟踪下sql发现很明确的是一条insert,一条update的sql。SaveChanges也是一个事务,如果一个不成功,那么所有都提交不成功。

仔细看上面的DbSet.Add方法可知,DbSet.Add方法返回的对象就是添加的实体对象,上面的Add方法返回的就是DbContexts.Model.Destination。这个给编码提供了很好的便利性,来看一个方法:

        /// <summary>
        /// 有就查询,没有就添加并查询
        /// </summary>
        private static void FindOrAddPerson()
        {
            using (var context = new DbContexts.DataAccess.BreakAwayContext())
            {
                var ssn = 123456789;
                var person = context.People.Find(ssn) ?? context.People.Add(new DbContexts.Model.Person
                {
                    SocialSecurityNumber = ssn,
                    FirstName = "Phelps",
                    LastName = "Michael"
                });
                Console.WriteLine(person.FirstName);
            }
        }

??表示前者如果为null就使用后者。很明显,库里不存在ssn为123456789的人,那么程序添加一个新的ssn为123456789的人,添加完毕,这个person对象就是刚刚调用Add方法添加的person。这里并没有调用SaveChanges方法,如果调用SaveChanges方法通过调用person.PersonId还可以获取自增长的主键id。详细了解请点园长dudu的文章

感谢耐心读完我的长文,希望你能从中有所感悟。本文源码

EF DbContext 系列文章导航
  1. EF如何操作内存中的数据和加载外键数据:延迟加载、贪婪加载、显示加载  本章源码
  2. EF里单个实体的增查改删以及主从表关联数据的各种增删改查  本章源码
  3. 使用EF自带的EntityState枚举和自定义枚举实现单个和多个实体的增删改查  本章源码
  4. EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态  本章源码
  5. EF里如何定制实体的验证规则和实现IObjectWithState接口进行验证以及多个实体的同时验证  本章源码
  6. 重写ValidateEntity虚方法实现可控的上下文验证和自定义验证  本章源码
目录
相关文章
|
12天前
|
关系型数据库 MySQL 数据库
ORM对mysql数据库中数据进行操作报错解决
ORM对mysql数据库中数据进行操作报错解决
36 2
|
11天前
|
JavaScript Java 关系型数据库
毕设项目&课程设计&毕设项目:基于springboot+vue实现的在线考试系统(含教程&源码&数据库数据)
本文介绍了一个基于Spring Boot和Vue.js实现的在线考试系统。随着在线教育的发展,在线考试系统的重要性日益凸显。该系统不仅能提高教学效率,减轻教师负担,还为学生提供了灵活便捷的考试方式。技术栈包括Spring Boot、Vue.js、Element-UI等,支持多种角色登录,具备考试管理、题库管理、成绩查询等功能。系统采用前后端分离架构,具备高性能和扩展性,未来可进一步优化并引入AI技术提升智能化水平。
毕设项目&课程设计&毕设项目:基于springboot+vue实现的在线考试系统(含教程&源码&数据库数据)
|
12天前
|
Java 关系型数据库 MySQL
毕设项目&课程设计&毕设项目:springboot+jsp实现的房屋租租赁系统(含教程&源码&数据库数据)
本文介绍了一款基于Spring Boot和JSP技术的房屋租赁系统,旨在通过自动化和信息化手段提升房屋管理效率,优化租户体验。系统采用JDK 1.8、Maven 3.6、MySQL 8.0、JSP、Layui和Spring Boot 2.0等技术栈,实现了高效的房源管理和便捷的租户服务。通过该系统,房东可以轻松管理房源,租户可以快速找到合适的住所,双方都能享受数字化带来的便利。未来,系统将持续优化升级,提供更多完善的服务。
毕设项目&课程设计&毕设项目:springboot+jsp实现的房屋租租赁系统(含教程&源码&数据库数据)
|
23天前
|
SQL 关系型数据库 MySQL
学成在线笔记+踩坑(3)——【内容模块】课程分类查询、课程增改删、课程计划增删改查,统一异常处理+JSR303校验
课程分类查询、课程新增、统一异常处理、统一封装结果类、JSR303校验、修改课程、查询课程计划、新增/修改课程计划
学成在线笔记+踩坑(3)——【内容模块】课程分类查询、课程增改删、课程计划增删改查,统一异常处理+JSR303校验
|
8天前
|
存储 API 数据库
QML使用Sqlite数据库存储ListModel数据
本文介绍了在QML中使用Sqlite数据库存储ListModel数据的方法,包括如何创建数据库、读取数据、动态添加和删除数据,以及如何在程序启动和退出时与数据库同步数据。
|
10天前
|
SQL 关系型数据库 MySQL
ThinkPHP6 连接使用数据库,增删改查,find,select,save,insert,insertAll,insertGetId,delete,update方法的用法
本文介绍了在ThinkPHP6框架中如何连接和使用数据库进行增删改查操作。内容包括配置数据库连接信息、使用Db类进行原生MySQL查询、find方法查询单个数据、select方法查询数据集、save方法添加数据、insertAll方法批量添加数据、insertGetId方法添加数据并返回自增主键、delete方法删除数据和update方法更新数据。此外,还说明了如何通过数据库配置文件进行数据库连接信息的配置,并强调了在使用Db类时需要先将其引入。
ThinkPHP6 连接使用数据库,增删改查,find,select,save,insert,insertAll,insertGetId,delete,update方法的用法
|
17天前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
阿里云数据库重磅升级!元数据服务OneMeta + OneOps统一管理多模态数据
|
27天前
|
SQL NoSQL Java
彻底革新你的数据库操作体验!Micronaut数据访问技巧让你瞬间爱上代码编写!
【9月更文挑战第10天】Java开发者们一直在寻找简化应用程序与数据库交互的方法。Micronaut作为一个现代框架,提供了多种工具和特性来提升数据访问效率。本文介绍如何使用Micronaut简化数据库操作,并提供具体示例代码。Micronaut支持JPA/Hibernate、SQL及NoSQL(如MongoDB),简化配置并无缝集成。通过定义带有`@Repository`注解的接口,可以实现Spring Data风格的命名查询。
49 6
|
2月前
|
SQL 存储 数据处理
"SQL触发器实战大揭秘:一键解锁数据自动化校验与更新魔法,让数据库管理从此告别繁琐,精准高效不再是梦!"
【8月更文挑战第31天】在数据库管理中,确保数据准确性和一致性至关重要。SQL触发器能自动执行数据校验与更新,显著提升工作效率。本文通过一个员工信息表的例子,详细介绍了如何利用触发器自动设定和校验薪资,确保其符合业务规则。提供的示例代码展示了在插入新记录时如何自动检查并调整薪资,以满足最低标准。这不仅减轻了数据库管理员的负担,还提高了数据处理的准确性和效率。触发器虽强大,但也需谨慎使用,以避免复杂性和性能问题。
37 1
下一篇
无影云桌面