1.EF基本搭建 EF也用了好幾年了,但是在日常開發的時候,有時候因為偷懶,有時候因為趕項目,很多代碼,多半就是Ctrl+C和Ctrl+V,慢慢的一些代碼怎麼寫都忘記了,雖然覺得很簡單,但是就是記不起來怎麼寫,逐漸退化,所以記錄一下,後續再賦值粘貼也好找一些,免得打開項目。 在此以.Net Fram ...
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();
}