通過EF 作為操作數據庫的工具有一段時間了,也做了幾個相對不大的項目,慢慢的也對EF的使用摸索出來了一些規則,雖然說不是技術難點,但是,我說的是但是,能夠提高我們開發效率的棉花糖有時我們還是必須要吃的,因為他確實很甜很甜。現在Ef已經更新到6.1.1了,從原來的5.0 到現在也不過是短短的一年多,所以說Ef的生命力還是很強的。什么 你要我對比一下EF和NHibernate的優缺點,這不是本文的重點,我只說一句,EF側重代碼配置,NHibernate 側重配置文件配置,但是說哪種好,蘿卜白菜 各有所愛吧。
剛開始使用EF操作數據表我們是這樣使用的,通過在DBContext中添加Dbset<T> 這種形式的屬性來實現,但是,我說的是但是,這種方式隨着我們的實體Domain越來越多的時候我們不得不一個一個的添加到DbContext中,這不禁讓我很苦惱,有沒有更好的辦法呢?
為了說明的方便,我建立了一個控制台的程序,畢竟EF和具體的項目類型無關。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TestContext testContext = new TestContext(); 6 ///獲取數據庫表Person中的所有數據 在查詢的時候最好加上AsNoTracking 禁止EF跟蹤 7 var personList = testContext.Persons.AsNoTracking().ToList(); 8 } 9 } 10 11 public class TestContext : DbContext 12 { 13 private static TestContext _instance; 14 15 public static TestContext Instance 16 { 17 get 18 { 19 if (_instance == null) 20 { 21 _instance = new TestContext(); 22 } 23 return _instance; 24 } 25 } 26 27 private string _connectionString; 28 29 public string ConnectionString 30 { 31 get 32 { 33 if (string.IsNullOrWhiteSpace(_connectionString)) 34 { 35 _connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionString; 36 } 37 return _connectionString; 38 } 39 set 40 { 41 _connectionString = value; 42 } 43 } 44 45 public TestContext() 46 : base("name=testConn") 47 { 48 _connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionString; 49 } 50 public TestContext(string connectionString) 51 : base(connectionString) 52 { 53 54 } 55 56 /// <summary> 57 /// 定義的實體 58 /// </summary> 59 public DbSet<Person> Persons { get; set; } 60 } 61 [Table("Person")] 62 public class Person 63 { 64 public string Name { get; set; } 65 66 public string Age { get; set; } 67 }
每次添加實體Domain 都要在DbContext 中添加一個對應的屬性,很令人苦惱,並且如果屬性為string 類型,在數據庫中創建的表的長度為max,當然我們可以修改EF的默認約定來讓string 類型在數據表中具有一定的長度。我們有沒有更好的方式來控制EF創建的數據表呢,並且要易於維護,讓所有同事都可以很輕松的掌握。當然,有一個新大陸被發現了。
我們通過反射來找到所有繼承自EntityTypeConfiguration的類,這些類就是EF的映射類,我們把這些映射類添加到EF configuration中就可以實現我們的功能,說太多,上代碼。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TestContext testContext = new TestContext(); 6 ///獲取數據庫表Person中的所有數據 在查詢的時候最好加上AsNoTracking 禁止EF跟蹤 7 // var personList = testContext.Persons.AsNoTracking().ToList(); 8 } 9 } 10 11 public class TestContext : DbContext 12 { 13 private static TestContext _instance; 14 15 public static TestContext Instance 16 { 17 get 18 { 19 if (_instance == null) 20 { 21 _instance = new TestContext(); 22 } 23 return _instance; 24 } 25 } 26 27 private string _connectionString; 28 29 public string ConnectionString 30 { 31 get 32 { 33 if (string.IsNullOrWhiteSpace(_connectionString)) 34 { 35 _connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionString; 36 } 37 return _connectionString; 38 } 39 set 40 { 41 _connectionString = value; 42 } 43 } 44 45 public TestContext() 46 : base("name=testConn") 47 { 48 _connectionString = ConfigurationManager.ConnectionStrings["testConn"].ConnectionString; 49 } 50 public TestContext(string connectionString) 51 : base(connectionString) 52 { 53 54 } 55 protected override void OnModelCreating(DbModelBuilder modelBuilder) 56 { 57 ///DomainMapping 所在的程序集一定要寫對,因為目前在當前項目所以是采用的當前正在運行的程序集 如果你的mapping在單獨的項目中 記得要加載對應的assembly 58 ///這是重點 59 var typesToRegister = Assembly.GetExecutingAssembly().GetTypes() 60 .Where(type => !String.IsNullOrEmpty(type.Namespace)) 61 .Where(type => type.BaseType != null && type.BaseType.BaseType != null && type.BaseType.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); 62 foreach (var type in typesToRegister) 63 { 64 dynamic configurationInstance = Activator.CreateInstance(type); 65 modelBuilder.Configurations.Add(configurationInstance); 66 } 67 68 base.OnModelCreating(modelBuilder); 69 } 70 71 72 } 73 74 public class BaseDomain 75 { 76 77 } 78 [Table("Person")] 79 public class Person 80 { 81 public string ID { get; set; } 82 public string Name { get; set; } 83 84 public string Age { get; set; } 85 } 86 87 public class PersonMapping : BaseDomainMapping<Person> 88 { 89 public override void Init() 90 { 91 this.ToTable("Person"); 92 this.HasKey(l => l.ID); 93 this.Property(l => l.Name).HasMaxLength(200).IsRequired();//設置Name屬性長度為200 並且是必填 94 this.Property(l => l.Age).HasMaxLength(200).IsOptional(); //設置Age長度為200 並且可為空 95 } 96 } 97 98 99 public abstract class BaseDomainMapping<T> : EntityTypeConfiguration<T> 100 where T : BaseDomain, new() 101 { 102 103 public BaseDomainMapping() 104 { 105 Init(); 106 } 107 /// <summary> 108 /// 初始化代碼 109 /// </summary> 110 public virtual void Init() 111 { 112 Console.WriteLine("Init"); 113 } 114 }
這個其實用到了主要兩個知識點,一個就是抽象類中定義的virtual方法,在抽象類中進行調用,在繼承自抽象類的類中override這個方法,此文為Init,那么子類中的這個方法也會被調用,避免在每個子類中都要寫調用Init的方法。
第二個就是,注意OnModelCreating 方法,他找到所有繼承自
EntityTypeConfiguration的類,然后添加到Configuration中,也就是我們實現了多個配置,統一添加到EF的配置中,EF在執行的時候會執行所有的配置類,當然包括我們自己定義的映射Mapping。
大家可能要說了,這樣是可以只寫一個映射類就可以,但是我們怎么訪問呢?沒有了原來的通過屬性
TestContext testContext = new TestContext();
///獲取數據庫表Person中的所有數據 在查詢的時候最好加上AsNoTracking 禁止EF跟蹤
var personList = testContext.Persons.AsNoTracking().ToList(); 通過屬性訪問很簡單方便
,我們需要想辦法獲取到DbSet 類通過EF訪問數據庫,現在我們的思路就是通過我們定義的TestContext ,找到我們通過反射注冊的所有配置類。
1 /// <summary> 2 /// Entity Framework repository 3 /// </summary> 4 public partial class EfRepository<T> : IRepository<T> where T : BaseDomain 5 { 6 private readonly IDbContext _context; ///可以認為就是我們定義的TestContext 7 private IDbSet<T> _entities; 8 9 /// <summary> 10 /// Ctor 11 /// </summary> 12 /// <param name="context">Object context</param> 13 public EfRepository(IDbContext context) 14 { 15 this._context = context; 16 } 17 18 /// <summary> 19 /// Get entity by identifier 20 /// </summary> 21 /// <param name="id">Identifier</param> 22 /// <returns>Entity</returns> 23 public virtual T GetById(object id) 24 { 25 //see some suggested performance optimization (not tested) 26 //http://stackoverflow.com/questions/11686225/dbset-find-method-ridiculously-slow-compared-to-singleordefault-on-id/11688189#comment34876113_11688189 27 return this.Entities.Find(id); 28 } 29 30 /// <summary> 31 /// Insert entity 32 /// </summary> 33 /// <param name="entity">Entity</param> 34 public virtual void Insert(T entity) 35 { 36 try 37 { 38 if (entity == null) 39 throw new ArgumentNullException("entity"); 40 41 this.Entities.Add(entity); 42 43 this._context.SaveChanges(); 44 } 45 catch (DbEntityValidationException dbEx) 46 { 47 var msg = string.Empty; 48 49 foreach (var validationErrors in dbEx.EntityValidationErrors) 50 foreach (var validationError in validationErrors.ValidationErrors) 51 msg += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine; 52 53 var fail = new Exception(msg, dbEx); 54 //Debug.WriteLine(fail.Message, fail); 55 throw fail; 56 } 57 } 58 59 /// <summary> 60 /// Update entity 61 /// </summary> 62 /// <param name="entity">Entity</param> 63 public virtual void Update(T entity) 64 { 65 try 66 { 67 if (entity == null) 68 throw new ArgumentNullException("entity"); 69 70 this._context.SaveChanges(); 71 } 72 catch (DbEntityValidationException dbEx) 73 { 74 var msg = string.Empty; 75 76 foreach (var validationErrors in dbEx.EntityValidationErrors) 77 foreach (var validationError in validationErrors.ValidationErrors) 78 msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); 79 80 var fail = new Exception(msg, dbEx); 81 //Debug.WriteLine(fail.Message, fail); 82 throw fail; 83 } 84 } 85 86 /// <summary> 87 /// Delete entity 88 /// </summary> 89 /// <param name="entity">Entity</param> 90 public virtual void Delete(T entity) 91 { 92 try 93 { 94 if (entity == null) 95 throw new ArgumentNullException("entity"); 96 97 this.Entities.Remove(entity); 98 99 this._context.SaveChanges(); 100 } 101 catch (DbEntityValidationException dbEx) 102 { 103 var msg = string.Empty; 104 105 foreach (var validationErrors in dbEx.EntityValidationErrors) 106 foreach (var validationError in validationErrors.ValidationErrors) 107 msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); 108 109 var fail = new Exception(msg, dbEx); 110 //Debug.WriteLine(fail.Message, fail); 111 throw fail; 112 } 113 } 114 115 /// <summary> 116 /// Gets a table 117 /// </summary> 118 public virtual IQueryable<T> Table 119 { 120 get 121 { 122 return this.Entities; 123 } 124 } 125 126 127 /// <summary> 128 /// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations 129 /// </summary> 130 public virtual IQueryable<T> TableNoTracking 131 { 132 get 133 { 134 return this.Entities.AsNoTracking(); 135 } 136 } 137 138 139 /// <summary> 140 /// Entities 141 /// </summary> 142 protected virtual IDbSet<T> Entities 143 { 144 get 145 { 146 if (_entities == null) 147 _entities = _context.Set<T>(); 148 return _entities; 149 } 150 } 151 }
接口類:
1 /// <summary> 2 /// Repository 3 /// </summary> 4 public partial interface IRepository<T> where T : BaseEntity 5 { 6 /// <summary> 7 /// Get entity by identifier 8 /// </summary> 9 /// <param name="id">Identifier</param> 10 /// <returns>Entity</returns> 11 T GetById(object id); 12 13 /// <summary> 14 /// Insert entity 15 /// </summary> 16 /// <param name="entity">Entity</param> 17 void Insert(T entity); 18 19 /// <summary> 20 /// Update entity 21 /// </summary> 22 /// <param name="entity">Entity</param> 23 void Update(T entity); 24 25 /// <summary> 26 /// Delete entity 27 /// </summary> 28 /// <param name="entity">Entity</param> 29 void Delete(T entity); 30 31 /// <summary> 32 /// Gets a table 33 /// </summary> 34 IQueryable<T> Table { get; } 35 36 /// <summary> 37 /// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations 38 /// </summary> 39 IQueryable<T> TableNoTracking { get; } 40 }
可以看到這個實現類很簡單,就是通過傳入我們定義的TestContext,然后通過
_context.Set<T>(); 可以獲取到我們定義的DbSet,剩下的就是正常的屬性定義一樣了。
說了那么多,該歇歇了,總結一下思路,就是通過反射注冊所有的實現EntityTypeConfiguration的類,(注意實現類 所在的assembly),然后通過context.set<T>()方法獲取到我們定義的Domain,就可以進行增刪改查等操作。另外還有一點就是,如果只是查詢數據,我建議使用AsNoTracking ,禁止EF跟蹤,提高一下性能,另外也可以禁止EF緩存查詢的數據。
有圖有真相。
總結一下:這只是一個簡單的實例,在實際的項目中肯定Domain和mapping是分開的,所以注冊的時候我們一定要設定好注冊的assembly。再次強調。
源代碼下載:
源碼