EF(Entity Framework)是微軟標志性且成熟的ORM,從之前的.NET Framework時代就已經很常見了,但是給人的感覺還是偏“重”,性能被不少人吐槽,倒是像Dapper這類更輕量級的更受大家待見。但是進入.NET Core時代后,EF隨之進化為EF Core(Entity Framework Core),除了是為ASP.NET Core量身打造外,在性能和功能上微軟也是做了很多優化,已經算是一款十分實用的ORM了。在寫本文時,EF Core目前處於3.x的版本階段,並且支持很多主流數據庫MySQL,SqlServer,Oracle,SQLite等等。本文討論的主要對象就是EF Core和Oracle的搭配,雖然目前是支持Oracle數據庫,但目前Oracle官方提供的Oracle.EntityFrameworkCore庫依然是依賴於EF Core的2.1版本,並且在使用中也不像EF Core和親兒子SqlServer配合的那樣絲滑,其中有不少坑。Oracle目前已經將Oracle.EntityFrameworkCore更新到了3.19.80版本,提升了之前版本的穩定性,並且支持EF Core的新版本,可以說解決了之前一直困擾我的兼容性問題。這里做一些簡單的記錄,也有可能是本人才疏學淺,打開的方式不對,如有錯誤歡迎指正。
本文Demo源碼地址:https://github.com/Xuhy0826/EFCoreTest (2020-09-09更新)
配置
若要使用EF Core操作Oracle數據庫,首先需要安裝Oracle.EntityFrameworkCore。可以直接在NuGet上直接搜索安裝即可。
按照慣例創建Context類繼承DbContext和其他的數據庫沒有區別,但是我們在配置連接字符串的時候的同時需要為其指定Oracle數據庫的版本,傳入“11”代表11g,傳入“12”代表12c。
1 public class MyContext : DbContext 2 { 3 public MyContext(DbContextOptions options) : base(options) 4 { } 5 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 6 { 7 optionsBuilder.UseOracle( 8 "DATA SOURCE=LOCALHOST:1521/ORACLE1;USER ID= 你的ID ;PASSWORD=你的密碼;" 9 , b => b.UseOracleSQLCompatibility("12")); //指定數據庫版本 10 } 11 }
如果我們在調試期間希望EF Core輸出執行的Sql,我使用的方式自定義一個日志類,將Sql輸出到Console中。
1 public class EFLoggerProvider : ILoggerProvider 2 { 3 public ILogger CreateLogger(string categoryName) => new EFLogger(categoryName); 4 public void Dispose() { } 5 } 6 public class EFLogger : ILogger 7 { 8 private readonly string categoryName; 9 public EFLogger(string categoryName) => this.categoryName = categoryName; 10 public IDisposable BeginScope<TState>(TState state) => null; 11 public bool IsEnabled(LogLevel logLevel) => true; 12 public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) 13 { 14 //ef core執行數據庫查詢時的categoryName為Microsoft.EntityFrameworkCore.Database.Command,日志級別為Information 15 if (categoryName == "Microsoft.EntityFrameworkCore.Database.Command") 16 { 17 var logContent = formatter(state, exception); 18 Console.WriteLine(); 19 Console.ForegroundColor = ConsoleColor.Green; 20 Console.WriteLine(logContent); 21 Console.ResetColor(); 22 } 23 } 24 }
並在配置時將其注冊進去。
1 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 2 { 3 //配置數據庫連接 4 optionsBuilder.UseOracle( 5 "DATA SOURCE=LOCALHOST:1521/ORACLE1;USER ID= 你的ID ;PASSWORD=你的密碼;" 6 , b => b.UseOracleSQLCompatibility("12")); 7 //配置Sql輸出控制台 8 var loggerFactory = new LoggerFactory(); 9 loggerFactory.AddProvider(new EFLoggerProvider()); 10 optionsBuilder.UseLoggerFactory(loggerFactory); 11 12 base.OnConfiguring(optionsBuilder); 13 }
由於是演示Demo,我們在開發ASP.NET Core項目時一般都是使用依賴注入來注冊。如果使用依賴注入的寫法,可以這樣寫。
1 services.AddDomainContext(builder => 2 { 3 //配置數據庫連接 4 builder.UseOracle( 5 "DATA SOURCE=LOCALHOST:1521/ORACLE1;USER ID= 你的ID ;PASSWORD=你的密碼;" 6 , b => b.UseOracleSQLCompatibility(version)); 7 //配置Sql輸出控制台 8 var loggerFactory = new LoggerFactory(); 9 loggerFactory.AddProvider(new EFLoggerProvider()); 10 builder.UseLoggerFactory(loggerFactory); 11 });
查詢數據
在執行查詢時,需要注意的有兩點,一是Table映射的實體類的屬性名稱如果不顯式指定的話需要使用全大寫的形式,不然會報錯“ORA-00904:標識符無效”。因為生成的Sql中是將所有的字段名都用雙引號修飾的,而Oracle中存儲字段名默認都是采用全大寫的形式的。
所以如果我們不想將實體類所有的屬性名稱寫成大寫,就需要顯式的指定映射的column的名稱。可以使用DataAnnotations的特性標注所有屬性,或者在Context中指定列映射。另外如果執行查詢時需要指定Schema,也像下面這樣配置。
方法1:
1 protected override void OnModelCreating(ModelBuilder modelBuilder) 2 { 3 //判斷當前數據庫是Oracle 4 if (this.Database.IsOracle()) 5 { //如果需要,手動添加Schema名稱,如果是默認或者表前不需要Schema名就可以不用配置 6 modelBuilder.HasDefaultSchema("XUHY"); 7 } 8 modelBuilder.Entity<Employee>(entity => 9 { 10 entity.ToTable("EMPLOYEE"); 11 entity.Property(e => e.Id).IsRequired();12 //列映射 13 entity.Property(e => e.Id).HasColumnName("ID"); 14 entity.Property(e => e.BirthDay).HasColumnName("BIRTHDAY"); 15 entity.Property(e => e.Department).HasColumnName("DEPARTMENT"); 16 entity.Property(e => e.EmployeeNo).HasColumnName("EMPLOYEENO"); 17 entity.Property(e => e.IsValid).HasColumnName("ISVALID"); 18 entity.Property(e => e.Name).HasColumnName("NAME"); 19 }); 20 base.OnModelCreating(modelBuilder); 21 }
方法2:
1 [Table("EMPLOYEE ")] //指定數據庫對應表名 2 public class Employee 3 { 4 [Key] //主鍵 5 [Column("ID")] //指定數據庫對應表欄位名稱 6 public long Id { get; set; } 7 [Column("EMPLOYEENO")] 8 public int EmployeeNo { get; set; } 9 [Column("NAME")] 10 public string Name { get; set; } 11 [Column("BIRTHDAY")] 12 public DateTime BirthDay { get; set; } 13 [Column("DEPARTMENT")] 14 public string Department { get; set; } 15 [Column("ISVALID")] 16 public bool IsValid { get; set; } 17 }
經過上面的這些配置,便可以正常的執行數據的查詢了。
1 await using var context = new MyContext(options); 2 var employeeCollection = await context.Employee.AsNoTracking().ToListAsync(); 3 //var employeeCollection = await context.Employee.OrderBy(e => e.EmployeeNo).AsNoTracking().Skip(0).Take(5).ToListAsync();//分頁 4 foreach (var employee in employeeCollection) 5 { 6 Console.WriteLine($"{employee.Name} | {employee.EmployeeNo} | {employee.Department}"); 7 } 8 9 Console.ReadLine();
插入數據
插入數據倒是和其他數據庫差別不大,但是由於Oracle天生是沒有自增Id的,如果我們設計的表的主鍵需要采用自增鍵就需要使用Sequence代替。在這種情況下就需要為實體類的主鍵指定Sequence的名稱了。
1 modelBuilder.Entity<Employee>(entity => 2 { 3 entity.ToTable("EMPLOYEE"); 4 entity.Property(e => e.Id).IsRequired(); 5 //!!!指定需要關聯的序列名稱!!! 6 entity.Property(e => e.Id).UseHiLo("SEQ_EMPLOYEE_ID"); 7 //列映射 8 entity.Property(e => e.Id).HasColumnName("ID"); 9 entity.Property(e => e.BirthDay).HasColumnName("BIRTHDAY"); 10 entity.Property(e => e.Department).HasColumnName("DEPARTMENT"); 11 entity.Property(e => e.EmployeeNo).HasColumnName("EMPLOYEENO"); 12 entity.Property(e => e.IsValid).HasColumnName("ISVALID"); 13 entity.Property(e => e.Name).HasColumnName("NAME"); 14 });
隨后便可以執行插入數據了。
1 var employee = new Employee() 2 { 3 EmployeeNo = 6, 4 Name = "老周", 5 Department = "d6", 6 BirthDay = DateTime.Now.AddYears(-40), 7 IsValid = true 8 }; 9 await using var context = new MyContext(options); 10 var res = await context.Employee.AddAsync(employee); 11 await context.SaveChangesAsync(); 12 Console.WriteLine("添加成功"); 13 Console.ReadLine();
以上就是簡單總結的使用EF Core操作Oracle數據庫時的一些比較特別的地方,當然本人仍在探索實踐中,如有新發現也會抽空更新上來。