入門EF Core
我們將開始真正的EF之旅了,這裡使用SqlServer資料,然後DbFirst;
為嘛使用SqlServer,目前公司的整體業務全部在SqlSever,所以很多產品業務都是依託於這個,當然也在考慮做資料庫切換,切換EF Core就是開始,為後續做好準備,目前SqlServer的linux叢集部署太麻煩了,至少我是這樣認為的,而且很多客戶也都人格上排斥 .... 說多了都是淚 ....
然後就是DbFirst,公司是業務型公司,注重業務需求的設計,所以在需求開發之前,表結構的設計基本上都已經確定,基於現在的業務以及背景,可能DbFirst更加適合,當然Code First也不會丟掉的
一、安裝 EF Core
新建類庫,用來引用 Microsoft.EntityFrameworkCore.SqlServer
如果在專案中有類似的第三方程式集引用,建議放入統一的程式集,這樣不用到處維護引用資訊;而且有利於後期解耦,其他業務相關的都依賴統一介面就可以,這樣具體的實現引用只是一個外掛而已;
二、生成資料表結構
為了做測試,這裡生成兩張表,TestTable,以及TestTableDetail,用來模擬主從表的場景;一步步來啊
CREATE TABLE [dbo].[TestTable](
[Id] [INT] NOT NULL,
[Name] [NVARCHAR](200) NOT NULL,
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]
CREATE TABLE [dbo].[TestTableDetail](
[Id] [INT] NOT NULL,
[PID] [INT] NOT NULL,
[Name] [NVARCHAR](200) NOT NULL,
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]
三、建立實體物件
建立實體物件,用來與資料庫的物件進行匹配
[Table("TestTable")]
public class TestTable
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
[Table("TestTableDetail")]
public class TestTableDetail
{
[Key]
public int Id { get; set; }
public int PID { get; set; }
public string Name { get; set; }
}
四、建立DbContext上下文
/// <summary>
/// 自定義 資料上下文
/// </summary>
public class MyDbContext : DbContext
{
public DbSet<TestTable> TestTables { get; set; }
public DbSet<TestTableDetail> TestTableDetails { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//寫入連線字串
optionsBuilder.UseSqlServer("Data Source=.\\SQLSERVER2014;Initial Catalog=EfCore.Test;User ID=sa;Pwd=1");
}
}
DbContext是EF運算元據庫的視窗,我們將為每個表來建立一個DbSet<>泛型類屬性,用來操作具體的表物件;DbSet支援Linq操作;這裡兩個知識點:
1.如何配置連線字串
如果是Asp.net Core程式,通常配置在Startup.cs中,需要匯入 Microsoft.Extensions.Configuration 名命空間方可使用,按如下方式進行註冊
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<BloggingContext>(options => options.UseSqlServer(Configuration.GetConnectionString("BloggingDatabase")));
}
看下原始碼
EFCore.SqlServer
public static DbContextOptionsBuilder UseSqlServer(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
[NotNull] string connectionString,
[CanBeNull] Action<SqlServerDbContextOptionsBuilder> sqlServerOptionsAction =
null)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
Check.NotEmpty(connectionString, nameof(connectionString));
var extension =
(SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnectionString(connectionString);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
ConfigureWarnings(optionsBuilder);
sqlServerOptionsAction?.Invoke(new
SqlServerDbContextOptionsBuilder(optionsBuilder));
return optionsBuilder;
}
對DbContextOptionsBuilder進行擴充套件,提供了UseSqlServer方法,連線資訊的提供都是通過 DbContextOptionsBuilder 來實現
針對WinForms以及WPF應用呢?我測試驗證的就是控制檯應用
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//寫入連線字串
optionsBuilder.UseSqlServer("Data Source=.\\SQLSERVER2014;Initial Catalog=EfCore.Test;User ID=sa;Pwd=1");
}
這裡我是固定了連線字串,可以根據配置檔案來寫入了;可以看到也是對 DbContextOptionsBuilder 的 UseSqlSerer方法的呼叫;
檢視原始碼:
DbContext
//定義虛方法OnConfiguring的位置
protected internal virtual void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
在InternalServiceProvider中進行了初始化呼叫,呼叫了OnConfiguring,從而進行了連線字串的賦值;
2.是否需要為每個類都定義DbSet屬性
如果業務系統過大,我們真的會定義一個DbContext,然後將所有Entity定義成DbSet<>?應該不會,這時我們可以通過反射來實現;
1.通過實現一個反射讀取類,來動態讀取實體類,也就是讀取類具備 “Table”屬性的目標類,或者自己整合一個父類用來識別實體類;
2.將讀取到的實體類,動態加入到DbContext的實體模型中;
通過 重寫 OnModelCreating 方法;微軟官方文件地址:https://docs.microsoft.com/zh-cn/ef/core/modeling/
通過呼叫ModelBuilder.Entity方法直接貼程式碼:
/// <summary>
/// 自定義 資料上下文
/// </summary>
public class DynamicDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//寫入連線字串
optionsBuilder.UseSqlServer("Data Source=.\\SQLSERVER2014;Initial Catalog=EfCore.Test;User ID=sa;Pwd=1");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.ExportedTypes)
{
if (type.IsClass && type != typeof(EntityBase) && typeof(EntityBase).IsAssignableFrom((Type) type))
{
var method = modelBuilder.GetType().GetMethods().FirstOrDefault(x => x.Name == "Entity");
if (method != null)
{
method = method.MakeGenericMethod(type);
method.Invoke(modelBuilder, null);
}
}
}
base.OnModelCreating(modelBuilder);
}
}
整體思路還是兩步走,先找到實體的實現類,然後通過呼叫DbContenxt的Entity方法;我們來看下DbContext原始碼;
最後通過Metadata.AddEntityType加入到實體模型,Metadata用來儲存實體後設資料;
還有兩外一種實現方式,其實也就是衍生的方式了,因為檢視原始碼得知最後實體被加入到了Medel中,那何不直接加入呢?
呼叫程式碼:
var myDbContext = new DynamicDbContext();
var list = myDbContext.Set<TestTable>().ToList();
Console.WriteLine($"TestTable Count: {list.Count}");
if (!list.Any()) return;
Console.WriteLine($"TestTable Detail ---------------- ");
foreach (var item in list)
{
Console.WriteLine($"ID : {item.Id} , Name : {item.Name}");
}
Console.WriteLine($"------------------------");
來看下執行效果吧....
這裡的實現讓我想到了ABP資料倉儲的實現,ABP為每個實體建立一個倉儲物件,不需要手動一個個建立,可以檢視我的ABP系列 => ABP 資料訪問 - IRepository 倉儲 ,可以參考ABP倉儲管理的思想;大家也可以去看下
五、資料訪問
好了,回到最初的實現思路上,前面的準備工作都做的差不多了,該正式跑一把資料了....
public static void Query_查詢資料_全量查詢()
{
var myDbContext = new MyDbContext();
var list = myDbContext.TestTables.ToList();
Console.WriteLine($"TestTable Count: {list.Count}");
if (!list.Any()) return;
Console.WriteLine($"TestTable Detail ---------------- ");
foreach (var item in list)
{
Console.WriteLine($"ID : {item.Id} , Name : {item.Name}");
}
Console.WriteLine($"------------------------");
}
通過控制檯應用,執行上述方法,即可查詢到TestTable中的資料
到此我們基本就開始使用EF Core,實現了資料訪問;後續將開始對EF的其他使用持續進行分析,以及一些高階應用;
文章後面附上EFCore的原始碼地址,一起看原始碼,一起學習 https://github.com/dotnet/efcore
幫助部落格園推廣下:https://www.cnblogs.com/cmt/p/14003277.html