1.EF基本搭建
EF也用了好几年了,但是在日常开发的时候,有时候因为偷懒,有时候因为赶项目,很多代码,多半就是Ctrl+C和Ctrl+V,慢慢的一些代码怎么写都忘记了,虽然觉得很简单,但是就是记不起来怎么写,逐渐退化,所以记录一下,后续再赋值粘贴也好找一些,免得打开项目。
在此以.Net FramWork 控制台搭建简单的Demo使用的模式是DBFirst,个人觉得现在多半还是数据库设计和代码还是分开的。
1.创建数据库和表,这里使用关联表,一个主表
一个从表
,抽象出简单的业务关系为一个员工持有哪些设备,而关系是一对多。
--创建数据库
CREATE DATABASE EfDemo;
GO
USE EfDemo;
GO
--创建员工表
CREATE TABLE [dbo].[Employee]
([id] [INT] IDENTITY(1, 1) NOT NULL,
[Code] [NVARCHAR](20) NULL,
[Name] [NVARCHAR](20) NULL,
);
--创建设备表
CREATE TABLE [dbo].[Device]
([DeviceId] [INT] IDENTITY(1, 1) NOT NULL,
[id] [INT] NULL,
[DeviceName] [NVARCHAR](20) NULL
);
1.首先NuGet 安装EntityFramework,至于什么版本看一下介绍,选择对应框架的版本,我的是4.8安装的EF版本是6.2
2.在配置文件中设置连接字符串
<connectionStrings>
<add name="efConstr" connectionString="Data Source=192.168.0.106;Initial Catalog=EfDemo;User ID=sa;Password=sa@123456" providerName="System.Data.SqlClient" />
</connectionStrings>
3.创建员工实体类和设备实体类,并添加相关属性,添加2个方法添加和删除Device信息
[Table("Employee")] 表映射
[Key] 主键
[Required] 必填
[Column("Name")] 列名映射
[StringLength(1000)] 设置长度,如果是dbfirst记得与数据库长度匹配
[Table("Employee")]
public class EmployeeEntity
{
public EmployeeEntity()
{
DeviceEntities = new HashSet<DeviceEntity>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
[Column(Order = 1)]
[Required]
public int id { get; set; }
//将数据库字段Name映射别名为"mingzi "
[Column("Name")]
public string mingzi { get; set; }
public string Code { get; set; }
public virtual ICollection<DeviceEntity> DeviceEntities { get; set; }
public void AddDevice(DeviceEntity entity)
{
this.DeviceEntities.Add(entity);
}
public void RemoveDevice(DeviceEntity entity)
{
this.DeviceEntities.Remove(entity);
}
}
[Table("Device")]
public class DeviceEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int DeviceId { get; set; }
public int id { get; set; }
public string DeviceName { get; set; }
}
4.创建UserContext上下文,继承自DbContext
public class UserContext: DbContext
{
public UserContext()
: base("name=efConstr")
{
}
public virtual DbSet<EmployeeEntity> EmployeeEntities { get; set; }
}
2.EF查询
查询员工和设备数据,为了查询方便已经在实体模型上设置了,所以不需要连表查询,直接根据员工查出关联表的数据,我们想在程序中输出ef执行的sql日志可以使用
userContext.Database.Log = sql => { Console.WriteLine(sql); };
1.使用Find即时
查询id为1的数据,此时执行代码是直接实时查询数据库。
using (UserContext userContext = new UserContext())
{
var result = userContext.EmployeeEntities.Find(1);
}
2.使用Where延时查询
查询id大于0的数据,在执行循环
之前此时还未提交到数据库,得到的是一个IQueryable
可以使用ToList()
或者对IQueryable
进行循环就会直接提交到数据库
但是不建议直接使用ToList
public static List<EmployeeDto> SerachEmployeeInfo()
{
List<EmployeeDto> employees = new List<EmployeeDto>();
using (UserContext userContext = new UserContext())
{
//是一个IQueryable,此时并不会提交到数据库
var result = userContext.EmployeeEntities.Where(x => x.id > 0);
//虽然有多个但是只是拼接表达式,ToList时提交
result = result.Where(x => x.id == 2).ToList();
foreach (var item in result)
{
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.Id = item.id;
employeeDto.Name = item.mingzi;
employeeDto.Code = item.Code;
employeeDto.DeviceEntities = item.DeviceEntities.ToList();
employees.Add(employeeDto);
}
}
return employees;
}
3.使用连接查询
,默认为内连
,如果需要左连接
就需要将2个合并插入一个新的表,然后使用DefaultIfEmpty()
定义可为空
public static List<EmployeeDto> SerachEmployeeInfoByJoin()
{
List<EmployeeDto> employees = new List<EmployeeDto>();
using (UserContext userContext = new UserContext())
{
List<int> ins = new List<int> { 2 };
//使用内连接查询
var result = from u in userContext.EmployeeEntities
join c in userContext.DeviceEntities on u.id equals c.id
select new
{
Id = u.id,
Name = u.mingzi,
DeviceName = c.DeviceName
};
//使用左连接查询
var result = from u in userContext.EmployeeEntities
join c in userContext.DeviceEntities on u.id equals c.id
into leftTable
from lt in leftTable.DefaultIfEmpty()
select new
{
Id = u.id,
Name = u.mingzi,
DeviceName = c.DeviceName
};
//提交到数据库,并且遍历投影出的新的数据
foreach (var item in result)
{}
}
return employees;
}
4.使用EF执行sql语句,适合比较复杂的sql语句,主要依靠ExecuteSqlCommand
和SqlQuery
,调用存储过程也是同样的道理
public static int SerachEmployeeInfoBySql ()
{
using (UserContext userContext = new UserContext())
{
DbContextTransaction trans = null;
try
{
trans = userContext.Database.BeginTransaction();
string sqlExecute = "Update Employee set Name ='EF测试' WHERE Id=@Id";
//查询语句
//string sqlQuery = "Select*from Employee WHERE Id=@Id"
SqlParameter parameter = new SqlParameter("@Id", 1);
//执行ExecuteSqlCommand增删改
int executeResult = userContext.Database.ExecuteSqlCommand(sqlExecute, parameter);
//var result = userContext.Database.SqlQuery<EmployeeEntity>(sqlQuery, parameter).ToList<EmployeeEntity>();
trans.Commit();
return executeResult;
}
catch (Exception ex)
{
if (trans != null)
trans.Rollback();
throw ex;
}
finally
{
trans.Dispose();
}
}
}
3.EF增删改使用
1.新增一笔数据,这里使用的是异步方法,其实同步方法一样的道理,由于Employee
和Device
是主从关系,所以执行下面的代码同时会向Device
表也插入一条
- 1.如果在一个上下文中再次将mingzi 改为"同一上下文中再次赋值",那么就会修改前面插入的数据,因为在一个上下文中会进行
数据跟踪
static async Task AddEmployee()
{
using (UserContext userContext = new UserContext())
{
EmployeeEntity entity = new EmployeeEntity();
entity.mingzi = "新增数据1";
entity.Code = "A123";
//导航属性插入值
entity.AddDevice(new DeviceEntity { DeviceName = "ipad" });
userContext.EmployeeEntities.Add(entity);
//执行SaveChangesAsync()数据才会提交
await userContext.SaveChangesAsync();
//同上下文中再次赋值,就会修改mingzi的值
entity.mingzi = "同一上下文中再次赋值";
await userContext.SaveChangesAsync();
}
return result;
}
2.使用EF修改数据,如果直接修改是无效的
- 2.1.在同一上下文中修改,通常在工作中的做法是需要先把对象查询出来,然后修改对应的值。
同一上下文修改
static async Task UpdateEmployee()
{
using (UserContext userContext = new UserContext())
{
//先查询在修改
var content = await userContext.EmployeeEntities.FindAsync(7);
content.mingzi = "被修改的值";
await userContext.SaveChangesAsync();
}
}
- 2.2.在不同上下文中修改值,我们使用
Attach
的目的就是把一个没有被dbContext 跟踪的对象附加到新的上下文
中,使其被跟踪,必须先附加再赋值
才能生效,如果先赋值
那就需要在上下文中将对象的状态设置为EntityState.Modified
不同上下文修改
static async Task AddEmployee()
{
EmployeeEntity entity = null;
using (UserContext userContext = new UserContext())
{
entity = new EmployeeEntity();
entity.mingzi = "新增数据1";
entity.Code = "A123";
entity.AddDevice(new DeviceEntity { DeviceName = "ipad1" });
userContext.EmployeeEntities.Add(entity);
await userContext.SaveChangesAsync();
}
using (UserContext userContext = new UserContext())
{
//先附加再修改
userContext.EmployeeEntities.Attach(entity);
entity.mingzi = "不同上下文先附加再修改值";
await userContext.SaveChangesAsync();
//先修改再设置状态为Modified
entity.mingzi = "不同上下文先修改值再设置状态";
userContext.Entry<EmployeeEntity>(entity).State = EntityState.Modified;
await userContext.SaveChangesAsync();
}
}
- 2.3.按照上面的方式,EF在执行时生成的sql会将所有的字段都更新一遍,我们可以设置只对某个
属性字段
更新,在执行sql时只会更新那一个字段,前提需要对对象进行Attach
,然后再设置字段状态。
using (UserContext userContext = new UserContext())
{
entity.mingzi = "不同上下文修改值";
userContext.EmployeeEntities.Attach(entity);
//通知context mingzi属性被修改
userContext.Entry<EmployeeEntity>(entity).Property<string>("mingzi").IsModified = true;
await userContext.SaveChangesAsync();
}
3.删除数据的方式和修改类似,通用的是查询出来然后是使用Remove
,如果不同上下文,我们需要Attach
或者将状态更改为EntityState.Deleted
static async Task removeEmployee(int id)
{
using (UserContext userContext = new UserContext())
{
var emp = userContext.EmployeeEntities.Where(x => x.id == id).FirstOrDefault();
userContext.EmployeeEntities.Remove(emp);
await userContext.SaveChangesAsync();
}
}
- 3.1.不同上下文删除使用
附加
或者设置状态
static async Task removeEmployee(int id)
{
EmployeeEntity entity = null;
using (UserContext userContext = new UserContext())
{
entity = userContext.EmployeeEntities.Where(x => x.id == id).FirstOrDefault();
}
using (UserContext userContext = new UserContext())
{
//设置实体状态为删除
// userContext.Entry(entity).State = EntityState.Deleted;
//附加到当前上下文
userContext.EmployeeEntities.Attach(entity);
//执行删除
userContext.EmployeeEntities.Remove(entity);
await userContext.SaveChangesAsync();
}
}
4.Context上下文
1.下面在一个上下文里面执行多个操作,一次SaveChanges()
是保存全部的变化,假如中间有一个失败,那所有的都将失败,可以理解到SaveChanges
是事务的结束,所以不能全局使用
static void UpdateEmployee()
{
using (UserContext userContext = new UserContext())
{
try
{
var content = userContext.EmployeeEntities.Find(22);
content.mingzi = "一个上下文修改第一个值";
var content1 = userContext.EmployeeEntities.Find(23);
content1.mingzi = "一个上下文修改第二个值撒啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊撒啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊";
userContext.SaveChanges();
}
catch (Exception ex) { throw ex; }
}
}
2.不同的Context不能连接查询,除非load到内存之后再去操作,如果是多线程或者多个请求,最好多个Context
5.查询本地缓存
1.在EF中查询是有缓存的,例如下面在使用Where
时每一次都会去数据库查询,但是使用Find
时,他会现在本地内存中查找一次,如果前面查询有结果,那就直接使用,那么就有可能产生脏读
,但是对性能提升有帮助,使用时根据自身需求而定。
using (UserContext userContext = new UserContext())
{
var result = userContext.EmployeeEntities.Where(x => x.id < 3).ToList();
var result1 = userContext.EmployeeEntities.Where(x => x.id ==2).ToList();
var result2 = userContext.EmployeeEntities.Find(1);
var result3 = userContext.EmployeeEntities.Where(x => x.id == 1).ToList();
}
2.如果不希望查询出的结果在内存中缓存,可以使用AsNoTracking
不在内存中拷贝副本,直接返回,就算后续使用Find
也不会读取内存
using (UserContext userContext = new UserContext())
{
var result = userContext.EmployeeEntities.Where(x => x.id < 3).AsNoTracking().ToList();
}
``
###### 6.导航属性以及延迟加载
1.主从查询丢弃子表查询,在上下文中使用`LazyLoadingEnabled =fasle`
```cs
userContext.Configuration.LazyLoadingEnabled = false;
2.使用Include
,不延迟加载将数据一次性加载出来
using (UserContext userContext = new UserContext())
{
var result = userContext.EmployeeEntities.Include("DeviceEntities").Where(x=>x.id>0);
foreach (var item in result)
{
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.Id = item.id;
employeeDto.Name = item.mingzi;
employeeDto.Code = item.Code;
employeeDto.DeviceEntities = item.DeviceEntities.ToList();
employees.Add(employeeDto);
}
}
3.主从表的级联删除
,前提是需要修改数据库外键
的删除规则为级联
using (UserContext userContext = new UserContext())
{
entity = userContext.EmployeeEntities.Where(x => x.id == id).FirstOrDefault();
userContext.EmployeeEntities.Remove(entity);
userContext.SaveChanges();
}