經過EF 做爲操做數據庫的工具備一段時間了,也作了幾個相對不大的項目,慢慢的也對EF的使用摸索出來了一些規則,雖說不是技術難點,可是,我說的是可是,可以提升咱們開發效率的棉花糖有時咱們仍是必需要吃的,由於他確實很甜很甜。如今Ef已經更新到6.1.1了,從原來的5.0 到如今也不過是短短的一年多,因此說Ef的生命力仍是很強的。什麼 你要我對比一下EF和NHibernate的優缺點,這不是本文的重點,我只說一句,EF側重代碼配置,NHibernate 側重配置文件配置,可是說哪一種好,蘿蔔白菜 各有所愛吧。數據庫
剛開始使用EF操做數據表咱們是這樣使用的,經過在DBContext中添加Dbset<T> 這種形式的屬性來實現,可是,我說的是可是,這種方式隨着咱們的實體Domain愈來愈多的時候咱們不得不一個一個的添加到DbContext中,這不由讓我很苦惱,有沒有更好的辦法呢?緩存
爲了說明的方便,我創建了一個控制檯的程序,畢竟EF和具體的項目類型無關。app
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建立的數據表呢,而且要易於維護,讓全部同事均可以很輕鬆的掌握。固然,有一個新大陸被發現了。ide
咱們經過反射來找到全部繼承自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 方法,他找到全部繼承自ui
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。再次強調。
源代碼下載:
源碼