NopCommerce-EntityFramework开发:主要是Controller-Service-Repository的开发方式
操作数据库,主要对象是BaseEntity,IDbContext和NopObjectContext,IRepository和EfRepository,NopEntityTypeConfiguration(可以使用EntityFramework提供的EntityTypeConfiguration不使用NopCommerce拓展的NopEntityTypeConfiguration) 以下代码GitBub地址:https://github.com/heshengli/nop BaseEntity,实体基类////// Base class for entities/// public abstract partial class BaseEntity{ ////// Gets or sets the entity identifier /// public int Id { get; set; } public override bool Equals(object obj) { return Equals(obj as BaseEntity); } private static bool IsTransient(BaseEntity obj) { return obj != null && Equals(obj.Id, default(int)); } private Type GetUnproxiedType() { return GetType(); } public virtual bool Equals(BaseEntity other) { if (other == null) return false; if (ReferenceEquals(this, other)) return true; if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id)) { var otherType = other.GetUnproxiedType(); var thisType = GetUnproxiedType(); return thisType.IsAssignableFrom(otherType) || otherType.IsAssignableFrom(thisType); } return false; } public override int GetHashCode() { if (Equals(Id, default(int))) return base.GetHashCode(); return Id.GetHashCode(); } public static bool operator ==(BaseEntity x, BaseEntity y) { return Equals(x, y); } public static bool operator !=(BaseEntity x, BaseEntity y) { return !(x == y); }}
IDbContext,数据上下文接口
public interface IDbContext{ ////// Get DbSet /// ///Entity type ///DbSet IDbSetSet () where TEntity : BaseEntity; /// /// Save changes /// ///int SaveChanges(); /// /// Execute stores procedure and load a list of entities at the end /// ///Entity type /// Command text /// Parameters ///Entities IListExecuteStoredProcedureList (string commandText, params object[] parameters) where TEntity : BaseEntity, new(); /// /// Creates a raw SQL query that will return elements of the given generic type. The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type. /// ///The type of object returned by the query. /// The SQL query string. /// The parameters to apply to the SQL query string. ///Result IEnumerableSqlQuery (string sql, params object[] parameters); /// /// Executes the given DDL/DML command against the database. /// /// The command string /// false - the transaction creation is not ensured; true - the transaction creation is ensured. /// Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used /// The parameters to apply to the command string. ///The result returned by the database after executing the command. int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters); ////// Detach an entity /// /// Entity void Detach(object entity); ////// Gets or sets a value indicating whether proxy creation setting is enabled (used in EF) /// bool ProxyCreationEnabled { get; set; } ////// Gets or sets a value indicating whether auto detect changes setting is enabled (used in EF) /// bool AutoDetectChangesEnabled { get; set; }}
IRepository,基础操作接口
////// Repository /// public partial interface IRepositorywhere T : BaseEntity{ /// /// Get entity by identifier /// /// Identifier ///Entity T GetById(object id); ////// Insert entity /// /// Entity void Insert(T entity); ////// Insert entities /// /// Entities void Insert(IEnumerableentities); /// /// Update entity /// /// Entity void Update(T entity); ////// Update entities /// /// Entities void Update(IEnumerableentities); /// /// Delete entity /// /// Entity void Delete(T entity); ////// Delete entities /// /// Entities void Delete(IEnumerableentities); /// /// Delete entity by id /// /// void Delete(int id); ////// Gets a table /// IQueryableTable { get; } /// /// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations /// IQueryableTableNoTracking { get; }}
NopObjectContext,继承DbContext, IDbContext,具体实现
////// Object context/// public class NopObjectContext : DbContext, IDbContext{ #region Ctor public NopObjectContext(string nameOrConnectionString) : base(nameOrConnectionString) { //是否开启懒加载 //((IObjectContextAdapter) this).ObjectContext.ContextOptions.LazyLoadingEnabled = true; } #endregion #region Utilities 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<>)); //ef自带对象 //var typesToRegister2 = Assembly.GetExecutingAssembly().GetTypes() //.Where(type => !String.IsNullOrEmpty(type.Namespace)) //.Where(type => type.BaseType != null && type.BaseType.IsGenericType && //type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); 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()); //移除复数表名的约定 //modelBuilder.Conventions.Remove(); //检测数据库是否存在 //Database.SetInitializer (null); base.OnModelCreating(modelBuilder); } /// /// Attach an entity to the context or return an already attached entity (if it was already attached) /// ///TEntity /// Entity ///Attached entity protected virtual TEntity AttachEntityToContext(TEntity entity) where TEntity : BaseEntity, new() { //little hack here until Entity Framework really supports stored procedures //otherwise, navigation properties of loaded entities are not loaded until an entity is attached to the context var alreadyAttached = Set ().Local.FirstOrDefault(x => x.Id == entity.Id); if (alreadyAttached == null) { //attach new entity Set ().Attach(entity); return entity; } //entity is already loaded return alreadyAttached; } #endregion #region Methods /// /// Create database script /// ///SQL to generate database public string CreateDatabaseScript() { return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript(); } ////// Get DbSet /// ///Entity type ///DbSet public new IDbSetSet () where TEntity : BaseEntity { return base.Set (); } /// /// Execute stores procedure and load a list of entities at the end /// ///Entity type /// Command text /// Parameters ///Entities public IListExecuteStoredProcedureList (string commandText, params object[] parameters) where TEntity : BaseEntity, new() { //add parameters to command if (parameters != null && parameters.Length > 0) { for (int i = 0; i <= parameters.Length - 1; i++) { var p = parameters[i] as DbParameter; if (p == null) throw new Exception("Not support parameter type"); commandText += i == 0 ? " " : ", "; commandText += "@" + p.ParameterName; if (p.Direction == ParameterDirection.InputOutput || p.Direction == ParameterDirection.Output) { //output parameter commandText += " output"; } } } var result = this.Database.SqlQuery (commandText, parameters).ToList(); //performance hack applied as described here - http://www.nopcommerce.com/boards/t/25483/fix-very-important-speed-improvement.aspx bool acd = this.Configuration.AutoDetectChangesEnabled; try { this.Configuration.AutoDetectChangesEnabled = false; for (int i = 0; i < result.Count; i++) result[i] = AttachEntityToContext(result[i]); } finally { this.Configuration.AutoDetectChangesEnabled = acd; } return result; } /// /// Creates a raw SQL query that will return elements of the given generic type. The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type. /// ///The type of object returned by the query. /// The SQL query string. /// The parameters to apply to the SQL query string. ///Result public IEnumerableSqlQuery (string sql, params object[] parameters) { return this.Database.SqlQuery (sql, parameters); } /// /// Executes the given DDL/DML command against the database. /// /// The command string /// false - the transaction creation is not ensured; true - the transaction creation is ensured. /// Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used /// The parameters to apply to the command string. ///The result returned by the database after executing the command. public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters) { int? previousTimeout = null; if (timeout.HasValue) { //store previous timeout previousTimeout = ((IObjectContextAdapter)this).ObjectContext.CommandTimeout; ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = timeout; } var transactionalBehavior = doNotEnsureTransaction ? TransactionalBehavior.DoNotEnsureTransaction : TransactionalBehavior.EnsureTransaction; var result = this.Database.ExecuteSqlCommand(transactionalBehavior, sql, parameters); if (timeout.HasValue) { //Set previous timeout back ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = previousTimeout; } //return result return result; } ////// Detach an entity /// /// Entity public void Detach(object entity) { if (entity == null) throw new ArgumentNullException("entity"); ((IObjectContextAdapter)this).ObjectContext.Detach(entity); } #endregion #region Properties ////// Gets or sets a value indicating whether proxy creation setting is enabled (used in EF) /// public virtual bool ProxyCreationEnabled { get { return this.Configuration.ProxyCreationEnabled; } set { this.Configuration.ProxyCreationEnabled = value; } } ////// Gets or sets a value indicating whether auto detect changes setting is enabled (used in EF) /// public virtual bool AutoDetectChangesEnabled { get { return this.Configuration.AutoDetectChangesEnabled; } set { this.Configuration.AutoDetectChangesEnabled = value; } } #endregion}
EfRepository,IRepository接口的具体实现
////// Entity Framework repository/// public partial class EfRepository: IRepository where T : BaseEntity { #region Fields private readonly IDbContext _context; private IDbSet _entities; #endregion #region Ctor /// /// Ctor /// /// Object context public EfRepository(IDbContext context) { this._context = context; } #endregion #region Utilities ////// Get full error /// /// Exception ///Error protected string GetFullErrorText(DbEntityValidationException exc) { var msg = string.Empty; foreach (var validationErrors in exc.EntityValidationErrors) foreach (var error in validationErrors.ValidationErrors) msg += string.Format("Property: {0} Error: {1}", error.PropertyName, error.ErrorMessage) + Environment.NewLine; return msg; } #endregion #region Methods ////// Get entity by identifier /// /// Identifier ///Entity 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); } ////// Insert entity /// /// Entity public virtual void Insert(T entity) { try { if (entity == null) throw new ArgumentNullException("entity"); this.Entities.Add(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { throw new Exception(GetFullErrorText(dbEx), dbEx); } } ////// Insert entities /// /// Entities public virtual void Insert(IEnumerableentities) { try { if (entities == null) throw new ArgumentNullException("entities"); foreach (var entity in entities) this.Entities.Add(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { throw new Exception(GetFullErrorText(dbEx), dbEx); } } /// /// Update entity /// /// Entity public virtual void Update(T entity) { try { if (entity == null) throw new ArgumentNullException("entity"); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { throw new Exception(GetFullErrorText(dbEx), dbEx); } } ////// Update entities /// /// Entities public virtual void Update(IEnumerableentities) { try { if (entities == null) throw new ArgumentNullException("entities"); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { throw new Exception(GetFullErrorText(dbEx), dbEx); } } /// /// Delete entity /// /// Entity public virtual void Delete(T entity) { try { if (entity == null) throw new ArgumentNullException("entity"); this.Entities.Remove(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { throw new Exception(GetFullErrorText(dbEx), dbEx); } } ////// Delete entities /// /// Entities public virtual void Delete(IEnumerableentities) { try { if (entities == null) throw new ArgumentNullException("entities"); foreach (var entity in entities) this.Entities.Remove(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { throw new Exception(GetFullErrorText(dbEx), dbEx); } } public void Delete(int id) { try { T entity = this.Entities.Find(id); if (entity == null) throw new ArgumentNullException("entities"); this.Entities.Remove(entity); this._context.SaveChanges(); } catch (DbEntityValidationException dbEx) { throw new Exception(GetFullErrorText(dbEx), dbEx); } } #endregion #region Properties /// /// Gets a table /// public virtual IQueryableTable { get { return this.Entities; } } /// /// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations /// public virtual IQueryableTableNoTracking { get { return this.Entities.AsNoTracking(); } } /// /// Entities /// protected virtual IDbSetEntities { get { if (_entities == null) _entities = _context.Set (); return _entities; } } #endregion}NopEntityTypeConfiguration,可拓展的抽象类public abstract class NopEntityTypeConfiguration : EntityTypeConfiguration where T : class{ protected NopEntityTypeConfiguration() { PostInitialize(); } /// /// Developers can override this method in custom partial classes /// in order to add some custom initialization code to constructors /// protected virtual void PostInitialize() { }}
简单demo:
实体基础代码public class Student : BaseEntity{ public string Name { get; set; } public string CustomProperty { get; set; }}////// 实体映射表nop拓展/// public partial class StudentMap : NopEntityTypeConfiguration{ public StudentMap() { this.ToTable("Student"); this.HasKey(pa => pa.Id); this.Property(pa => pa.Name).IsRequired(); }}/// /// 原生ef,需要调整NopObjectContext的代码/// //public partial class StudentMap2 : EntityTypeConfiguration//{// public StudentMap2()// {// this.ToTable("Student");// this.HasKey(pa => pa.Id);// this.Property(pa => pa.Name).IsRequired();// }//}public interface IStudentService{ void Insert(Student stu); void Update(Student stu); void Delete(int id); IEnumerable GetList();}public class StudentService : IStudentService{ private readonly IRepository _iStudentRepository; private readonly IDbContext _iDbContext; public StudentService() { _iDbContext = new NopObjectContext("App"); _iStudentRepository = new EfRepository (_iDbContext); } //Ioc Autofac 调整 //public StudentService(IRepository iStudentRepositpory,IDbContext iDbContext) //{ // _iDbContext = iDbContext; // _iStudentRepository = iStudentRepositpory; //} public void Delete(int id) { _iStudentRepository.Delete(id); } public IEnumerable GetList() { return _iStudentRepository.Table.AsEnumerable(); } public void Insert(Student stu) { _iStudentRepository.Insert(stu); } public void Update(Student stu) { _iStudentRepository.Update(stu); }}
调用代码(使用了简单的consoleApp)
class Program { private static readonly IStudentService _iStudentService = new StudentService(); static void Main(string[] args) { Student stu = new Student() { Name="李四", CustomProperty="自定义属性2" }; _iStudentService.Insert(stu); Console.WriteLine(stu.Name+""); Console.ReadKey(); }}