NOP源码分析五,文件位置等详细内容,感冒真难受,嗓子痒又疼。。
前面我们知道,依赖注入是通过实现IDependencyRegistrar接口的Register方法实现的。而NOP的依赖类是在Nop.Web.Framework下的DependencyRegistrar类,里面注册了好多类,就不黏贴了。我们再回头看一下上节介绍的任务计划。里面有个 IScheduleTaskService类 查找其实现类,代码如下:
namespace Nop.Services.Tasks { /// <summary> /// Task service /// </summary> public partial class ScheduleTaskService : IScheduleTaskService { #region Fields private readonly IRepository<ScheduleTask> _taskRepository; #endregion #region Ctor public ScheduleTaskService(IRepository<ScheduleTask> taskRepository) { this._taskRepository = taskRepository; } #endregion #region Methods /// <summary> /// Deletes a task /// </summary> /// <param name="task">Task</param> public virtual void DeleteTask(ScheduleTask task) { if (task == null) throw new ArgumentNullException("task"); _taskRepository.Delete(task); } /// <summary> /// Gets a task /// </summary> /// <param name="taskId">Task identifier</param> /// <returns>Task</returns> public virtual ScheduleTask GetTaskById(int taskId) { if (taskId == 0) return null; return _taskRepository.GetById(taskId); } /// <summary> /// Gets a task by its type /// </summary> /// <param name="type">Task type</param> /// <returns>Task</returns> public virtual ScheduleTask GetTaskByType(string type) { if (String.IsNullOrWhiteSpace(type)) return null; var query = _taskRepository.Table; query = query.Where(st => st.Type == type); query = query.OrderByDescending(t => t.Id); var task = query.FirstOrDefault(); return task; } /// <summary> /// Gets all tasks /// </summary> /// <param name="showHidden">A value indicating whether to show hidden records</param> /// <returns>Tasks</returns> public virtual IList<ScheduleTask> GetAllTasks(bool showHidden = false) { var query = _taskRepository.Table; if (!showHidden) { query = query.Where(t => t.Enabled); } query = query.OrderByDescending(t => t.Seconds); var tasks = query.ToList(); return tasks; } /// <summary> /// Inserts a task /// </summary> /// <param name="task">Task</param> public virtual void InsertTask(ScheduleTask task) { if (task == null) throw new ArgumentNullException("task"); _taskRepository.Insert(task); } /// <summary> /// Updates the task /// </summary> /// <param name="task">Task</param> public virtual void UpdateTask(ScheduleTask task) { if (task == null) throw new ArgumentNullException("task"); _taskRepository.Update(task); } #endregion } }
我们看到所有的操作都是通过IRepository类(仓库,存储类),进行的 。然后再到DependencyRegistrar里查找实现类,接口就不贴代码了,实现类都实现了。如下:
namespace Nop.Data { /// <summary> /// Entity Framework repository /// </summary> public partial class EfRepository<T> : IRepository<T> where T : BaseEntity { #region Fields private readonly IDbContext _context; private IDbSet<T> _entities; #endregion #region Ctor /// <summary> /// Ctor /// </summary> /// <param name="context">Object context</param> public EfRepository(IDbContext context) { this._context = context; } #endregion #region Methods /// <summary> /// Get entity by identifier /// </summary> /// <param name="id">Identifier</param> /// <returns>Entity</returns> public virtual T GetById(object id) { //see some suggested performance optimization (not tested) //http://stackoverflow.com/questions/11686225/dbset-find-method-ridiculously-slow-compared-to-singleordefault-on-id/11688189#comment34876113_11688189 return this.Entities.Find(id); } /// <summary> /// Insert entity /// </summary> /// <param name="entity">Entity</param> public virtual void Insert(T entity) { try { if (entity == null) throw new ArgumentNullException("entity"); this.Entities.Add(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { var msg = string.Empty; foreach (var validationErrors in dbEx.EntityValidationErrors) foreach (var validationError in validationErrors.ValidationErrors) msg += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine; var fail = new Exception(msg, dbEx); //Debug.WriteLine(fail.Message, fail); throw fail; } } /// <summary> /// Insert entities /// </summary> /// <param name="entities">Entities</param> public virtual void Insert(IEnumerable<T> entities) { try { if (entities == null) throw new ArgumentNullException("entities"); foreach (var entity in entities) this.Entities.Add(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { var msg = string.Empty; foreach (var validationErrors in dbEx.EntityValidationErrors) foreach (var validationError in validationErrors.ValidationErrors) msg += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine; var fail = new Exception(msg, dbEx); //Debug.WriteLine(fail.Message, fail); throw fail; } } /// <summary> /// Update entity /// </summary> /// <param name="entity">Entity</param> public virtual void Update(T entity) { try { if (entity == null) throw new ArgumentNullException("entity"); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { var msg = string.Empty; foreach (var validationErrors in dbEx.EntityValidationErrors) foreach (var validationError in validationErrors.ValidationErrors) msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); var fail = new Exception(msg, dbEx); //Debug.WriteLine(fail.Message, fail); throw fail; } } /// <summary> /// Delete entity /// </summary> /// <param name="entity">Entity</param> public virtual void Delete(T entity) { try { if (entity == null) throw new ArgumentNullException("entity"); this.Entities.Remove(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { var msg = string.Empty; foreach (var validationErrors in dbEx.EntityValidationErrors) foreach (var validationError in validationErrors.ValidationErrors) msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); var fail = new Exception(msg, dbEx); //Debug.WriteLine(fail.Message, fail); throw fail; } } /// <summary> /// Delete entities /// </summary> /// <param name="entities">Entities</param> public virtual void Delete(IEnumerable<T> entities) { try { if (entities == null) throw new ArgumentNullException("entities"); foreach (var entity in entities) this.Entities.Remove(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { var msg = string.Empty; foreach (var validationErrors in dbEx.EntityValidationErrors) foreach (var validationError in validationErrors.ValidationErrors) msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); var fail = new Exception(msg, dbEx); //Debug.WriteLine(fail.Message, fail); throw fail; } } #endregion #region Properties /// <summary> /// Gets a table /// </summary> public virtual IQueryable<T> Table { get { return this.Entities; } } /// <summary> /// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations /// </summary> public virtual IQueryable<T> TableNoTracking { get { return this.Entities.AsNoTracking(); } } /// <summary> /// Entities /// </summary> protected virtual IDbSet<T> Entities { get { if (_entities == null) _entities = _context.Set<T>(); return _entities; } } #endregion } }
我们就从下面这个方法开始研究:
public virtual IList<ScheduleTask> GetAllTasks(bool showHidden = false) { var query = _taskRepository.Table; if (!showHidden) { query = query.Where(t => t.Enabled); } query = query.OrderByDescending(t => t.Seconds); var tasks = query.ToList(); return tasks; }
其中这是虚方法:简单一点说就是子类中override的方法能够覆盖基类中的virtual方法,当你把一个子类的实例转换为基类时,调用该方法时还是调用的子类的override的方法。
我们看下_taskRepository.Table的代码如下:
public virtual IQueryable<T> Table { get { return this.Entities; } }
调用的是Entities。代码如下:
protected virtual IDbSet<T> Entities { get { if (_entities == null) _entities = _context.Set<T>(); return _entities; } }
_entities的声明时: private IDbSet<T> _entities; 是通过_context获得的。是通过构造传入的如下:
ublic EfRepository(IDbContext context) { this._context = context; }
IDbContext 从依赖注入查到代码如下:
if (dataProviderSettings != null && dataProviderSettings.IsValid()) { var efDataProviderManager = new EfDataProviderManager(dataSettingsManager.LoadSettings()); var dataProvider = efDataProviderManager.LoadDataProvider(); dataProvider.InitConnectionFactory(); builder.Register<IDbContext>(c => new NopObjectContext(dataProviderSettings.DataConnectionString)).InstancePerLifetimeScope(); } else { builder.Register<IDbContext>(c => new NopObjectContext(dataSettingsManager.LoadSettings().DataConnectionString)).InstancePerLifetimeScope(); }
如果数据库正常配置成功(我们一般用SQL),则LoadDataProvider方法返回如下代码
case "sqlserver": return new SqlServerDataProvider();
下面是SqlServerDataProvider实现类:
namespace Nop.Data { public class SqlServerDataProvider : IDataProvider { #region Utilities protected virtual string[] ParseCommands(string filePath, bool throwExceptionIfNonExists) { if (!File.Exists(filePath)) { if (throwExceptionIfNonExists) throw new ArgumentException(string.Format("Specified file doesn't exist - {0}", filePath)); return new string[0]; } var statements = new List<string>(); using (var stream = File.OpenRead(filePath)) using (var reader = new StreamReader(stream)) { string statement; while ((statement = ReadNextStatementFromStream(reader)) != null) { statements.Add(statement); } } return statements.ToArray(); } protected virtual string ReadNextStatementFromStream(StreamReader reader) { var sb = new StringBuilder(); while (true) { var lineOfText = reader.ReadLine(); if (lineOfText == null) { if (sb.Length > 0) return sb.ToString(); return null; } if (lineOfText.TrimEnd().ToUpper() == "GO") break; sb.Append(lineOfText + Environment.NewLine); } return sb.ToString(); } #endregion #region Methods /// <summary> /// Initialize connection factory /// </summary> public virtual void InitConnectionFactory() { var connectionFactory = new SqlConnectionFactory(); //TODO fix compilation warning (below) #pragma warning disable 0618 Database.DefaultConnectionFactory = connectionFactory; } /// <summary> /// Initialize database /// </summary> public virtual void InitDatabase() { InitConnectionFactory(); SetDatabaseInitializer(); } /// <summary> /// Set database initializer /// </summary> public virtual void SetDatabaseInitializer() { //pass some table names to ensure that we have nopCommerce 2.X installed var tablesToValidate = new[] { "Customer", "Discount", "Order", "Product", "ShoppingCartItem" }; //custom commands (stored proedures, indexes) var customCommands = new List<string>(); //use webHelper.MapPath instead of HostingEnvironment.MapPath which is not available in unit tests customCommands.AddRange(ParseCommands(HostingEnvironment.MapPath("~/App_Data/Install/SqlServer.Indexes.sql"), false)); //use webHelper.MapPath instead of HostingEnvironment.MapPath which is not available in unit tests customCommands.AddRange(ParseCommands(HostingEnvironment.MapPath("~/App_Data/Install/SqlServer.StoredProcedures.sql"), false)); var initializer = new CreateTablesIfNotExist<NopObjectContext>(tablesToValidate, customCommands.ToArray()); Database.SetInitializer(initializer); } /// <summary> /// A value indicating whether this data provider supports stored procedures /// </summary> public virtual bool StoredProceduredSupported { get { return true; } } /// <summary> /// Gets a support database parameter object (used by stored procedures) /// </summary> /// <returns>Parameter</returns> public virtual DbParameter GetParameter() { return new SqlParameter(); } #endregion } }
查看调用dataProvider.InitConnectionFactory(); 方法的代码如下:
/// <summary> /// Initialize connection factory /// </summary> public virtual void InitConnectionFactory() { var connectionFactory = new SqlConnectionFactory(); //TODO fix compilation warning (below) #pragma warning disable 0618 Database.DefaultConnectionFactory = connectionFactory; }
就是设置了默认连接工厂类, 是EF6里自带的一个类,下面是无参构造函数的说明:
// 摘要: // Creates a new connection factory with a default BaseConnectionString property // of 'Data Source=.\SQLEXPRESS; Integrated Security=True; MultipleActiveResultSets=True;'. public SqlConnectionFactory();
最终 注册的是NopObjectContext 代码如下:
public class NopObjectContext : DbContext, IDbContext { #region Ctor public NopObjectContext(string nameOrConnectionString) : base(nameOrConnectionString) { //((IObjectContextAdapter) this).ObjectContext.ContextOptions.LazyLoadingEnabled = true; }
然后调用他的Set方法,就是调用父类,EF自带类功能的方法:因为继承自DbContext,所以基本都可以调用它的功能进行操作,事实也是这么做的,比如插入实体insert.
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity { return base.Set<TEntity>(); }
内部有一个很重要的方法:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { //dynamically load all configuration //System.Type configType = typeof(LanguageMap); //any of your configuration classes here //var typesToRegister = Assembly.GetAssembly(configType).GetTypes() var typesToRegister = Assembly.GetExecutingAssembly().GetTypes() .Where(type => !String.IsNullOrEmpty(type.Namespace)) .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(NopEntityTypeConfiguration<>)); foreach (var type in typesToRegister) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.Configurations.Add(configurationInstance); } //...or do it manually below. For example, //modelBuilder.Configurations.Add(new LanguageMap()); base.OnModelCreating(modelBuilder); }
获取当前执行代码的程序集的类型并筛选,筛选出有命名空间、有父类并且父类是NopEntityTypeConfiguration类型的 类 ,循环得到类型的示例,并加入的配置。
NopEntityTypeConfiguration 继承自 EntityTypeConfiguration,作用是可以修改是实体类的对应关系, 这里的映射类都继承自NopEntityTypeConfiguration ,实现了关系映射。
所有的关系映射类几乎都在Nop.Data.Mapping下。