• EF 基本配置和操作数据库


    原文地址为:EF 的 霸气配置,秒杀一切

        通过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。再次强调。
    源代码下载:
    源码
    
    
    转载请注明本文地址:EF 的 霸气配置,秒杀一切
  • 相关阅读:
    权限设计=功能权限+数据权限
    C# 自定义配置文件
    asp.net core 系列 2 启动类 Startup.CS
    asp.net core 系列 1 概述
    IOC : Unity 配置和使用
    Unity Ioc 依赖倒置及Untity AOP被动拦截/自动拦截
    webuploader-异步切片上传(暂不支持断点续传)及 下载方法!C#/.NET
    C# 构造基础返回值类型-BaseResponse
    神奇的选择器 :focus-within
    妙用 scale 与 transfrom-origin,精准控制动画方向
  • 原文地址:https://www.cnblogs.com/sunny3158/p/11773113.html
Copyright © 2020-2023  润新知