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下。

浙公网安备 33010602011771号