NopCommerce学习(2) EntityFramework
NopCommerce-EntityFramework开发:主要是Controller-Service-Repository的开发方式
操作数据库,主要对象是BaseEntity,IDbContext和NopObjectContext,IRepository和EfRepository,NopEntityTypeConfiguration(可以使用EntityFramework提供的EntityTypeConfiguration不使用NopCommerce拓展的NopEntityTypeConfiguration)
以下代码GitBub地址:https://github.com/heshengli/nop
BaseEntity,实体基类
/// <summary>
/// Base class for entities
/// </summary>
public abstract partial class BaseEntity
{
/// <summary>
/// Gets or sets the entity identifier
/// </summary>
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
{
/// <summary>
/// Get DbSet
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <returns>DbSet</returns>
IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;
/// <summary>
/// Save changes
/// </summary>
/// <returns></returns>
int SaveChanges();
/// <summary>
/// Execute stores procedure and load a list of entities at the end
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="commandText">Command text</param>
/// <param name="parameters">Parameters</param>
/// <returns>Entities</returns>
IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters)
where TEntity : BaseEntity, new();
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TElement">The type of object returned by the query.</typeparam>
/// <param name="sql">The SQL query string.</param>
/// <param name="parameters">The parameters to apply to the SQL query string.</param>
/// <returns>Result</returns>
IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters);
/// <summary>
/// Executes the given DDL/DML command against the database.
/// </summary>
/// <param name="sql">The command string</param>
/// <param name="doNotEnsureTransaction">false - the transaction creation is not ensured; true - the transaction creation is ensured.</param>
/// <param name="timeout">Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used</param>
/// <param name="parameters">The parameters to apply to the command string.</param>
/// <returns>The result returned by the database after executing the command.</returns>
int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters);
/// <summary>
/// Detach an entity
/// </summary>
/// <param name="entity">Entity</param>
void Detach(object entity);
/// <summary>
/// Gets or sets a value indicating whether proxy creation setting is enabled (used in EF)
/// </summary>
bool ProxyCreationEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether auto detect changes setting is enabled (used in EF)
/// </summary>
bool AutoDetectChangesEnabled { get; set; }
}
IRepository,基础操作接口
/// <summary>
/// Repository
/// </summary>
public partial interface IRepository<T> where T : BaseEntity
{
/// <summary>
/// Get entity by identifier
/// </summary>
/// <param name="id">Identifier</param>
/// <returns>Entity</returns>
T GetById(object id);
/// <summary>
/// Insert entity
/// </summary>
/// <param name="entity">Entity</param>
void Insert(T entity);
/// <summary>
/// Insert entities
/// </summary>
/// <param name="entities">Entities</param>
void Insert(IEnumerable<T> entities);
/// <summary>
/// Update entity
/// </summary>
/// <param name="entity">Entity</param>
void Update(T entity);
/// <summary>
/// Update entities
/// </summary>
/// <param name="entities">Entities</param>
void Update(IEnumerable<T> entities);
/// <summary>
/// Delete entity
/// </summary>
/// <param name="entity">Entity</param>
void Delete(T entity);
/// <summary>
/// Delete entities
/// </summary>
/// <param name="entities">Entities</param>
void Delete(IEnumerable<T> entities);
/// <summary>
/// Delete entity by id
/// </summary>
/// <param name="id"></param>
void Delete(int id);
/// <summary>
/// Gets a table
/// </summary>
IQueryable<T> Table { get; }
/// <summary>
/// Gets a table with "no tracking" enabled (EF feature) Use it only when you load record(s) only for read-only operations
/// </summary>
IQueryable<T> TableNoTracking { get; }
}
NopObjectContext,继承DbContext, IDbContext,具体实现
/// <summary>
/// Object context
/// </summary>
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<PluralizingEntitySetNameConvention>();
//检测数据库是否存在
//Database.SetInitializer<ObjectContext>(null);
base.OnModelCreating(modelBuilder);
}
/// <summary>
/// Attach an entity to the context or return an already attached entity (if it was already attached)
/// </summary>
/// <typeparam name="TEntity">TEntity</typeparam>
/// <param name="entity">Entity</param>
/// <returns>Attached entity</returns>
protected virtual TEntity AttachEntityToContext<TEntity>(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<TEntity>().Local.FirstOrDefault(x => x.Id == entity.Id);
if (alreadyAttached == null)
{
//attach new entity
Set<TEntity>().Attach(entity);
return entity;
}
//entity is already loaded
return alreadyAttached;
}
#endregion
#region Methods
/// <summary>
/// Create database script
/// </summary>
/// <returns>SQL to generate database</returns>
public string CreateDatabaseScript()
{
return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript();
}
/// <summary>
/// Get DbSet
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <returns>DbSet</returns>
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
/// <summary>
/// Execute stores procedure and load a list of entities at the end
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="commandText">Command text</param>
/// <param name="parameters">Parameters</param>
/// <returns>Entities</returns>
public IList<TEntity> ExecuteStoredProcedureList<TEntity>(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<TEntity>(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;
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TElement">The type of object returned by the query.</typeparam>
/// <param name="sql">The SQL query string.</param>
/// <param name="parameters">The parameters to apply to the SQL query string.</param>
/// <returns>Result</returns>
public IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters)
{
return this.Database.SqlQuery<TElement>(sql, parameters);
}
/// <summary>
/// Executes the given DDL/DML command against the database.
/// </summary>
/// <param name="sql">The command string</param>
/// <param name="doNotEnsureTransaction">false - the transaction creation is not ensured; true - the transaction creation is ensured.</param>
/// <param name="timeout">Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used</param>
/// <param name="parameters">The parameters to apply to the command string.</param>
/// <returns>The result returned by the database after executing the command.</returns>
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;
}
/// <summary>
/// Detach an entity
/// </summary>
/// <param name="entity">Entity</param>
public void Detach(object entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
((IObjectContextAdapter)this).ObjectContext.Detach(entity);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether proxy creation setting is enabled (used in EF)
/// </summary>
public virtual bool ProxyCreationEnabled
{
get
{
return this.Configuration.ProxyCreationEnabled;
}
set
{
this.Configuration.ProxyCreationEnabled = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether auto detect changes setting is enabled (used in EF)
/// </summary>
public virtual bool AutoDetectChangesEnabled
{
get
{
return this.Configuration.AutoDetectChangesEnabled;
}
set
{
this.Configuration.AutoDetectChangesEnabled = value;
}
}
#endregion
}
EfRepository,IRepository接口的具体实现
/// <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 Utilities
/// <summary>
/// Get full error
/// </summary>
/// <param name="exc">Exception</param>
/// <returns>Error</returns>
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
/// <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)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <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)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <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)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <summary>
/// Update entities
/// </summary>
/// <param name="entities">Entities</param>
public virtual void Update(IEnumerable<T> entities)
{
try
{
if (entities == null)
throw new ArgumentNullException("entities");
this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <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)
{
throw new Exception(GetFullErrorText(dbEx), dbEx);
}
}
/// <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)
{
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
/// <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
}
NopEntityTypeConfiguration,可拓展的抽象类
public abstract class NopEntityTypeConfiguration<T> : EntityTypeConfiguration<T> where T : class
{
protected NopEntityTypeConfiguration()
{
PostInitialize();
}
/// <summary>
/// Developers can override this method in custom partial classes
/// in order to add some custom initialization code to constructors
/// </summary>
protected virtual void PostInitialize()
{
}
}
简单demo:
实体基础代码
public class Student : BaseEntity
{
public string Name { get; set; }
public string CustomProperty { get; set; }
}
/// <summary>
/// 实体映射表nop拓展
/// </summary>
public partial class StudentMap : NopEntityTypeConfiguration<Student>
{
public StudentMap()
{
this.ToTable("Student");
this.HasKey(pa => pa.Id);
this.Property(pa => pa.Name).IsRequired();
}
}
/// <summary>
/// 原生ef,需要调整NopObjectContext的代码
/// </summary>
//public partial class StudentMap2 : EntityTypeConfiguration<Student>
//{
// 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<Student> GetList();
}
public class StudentService : IStudentService
{
private readonly IRepository<Student> _iStudentRepository;
private readonly IDbContext _iDbContext;
public StudentService()
{
_iDbContext = new NopObjectContext("App");
_iStudentRepository = new EfRepository<Student>(_iDbContext);
}
//Ioc Autofac 调整
//public StudentService(IRepository<Student> iStudentRepositpory,IDbContext iDbContext)
//{
// _iDbContext = iDbContext;
// _iStudentRepository = iStudentRepositpory;
//}
public void Delete(int id)
{
_iStudentRepository.Delete(id);
}
public IEnumerable<Student> 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();
}
}
NopCommerce学习(2) EntityFramework的更多相关文章
- NopCommerce学习(1) Caching
redis教程 http://www.runoob.com/redis/redis-tutorial.html 下载地址:https://github.com/MSOpenTech/redis/rel ...
- entityframework学习笔记--001
最近想重新好好学习一下entityframework,于是在院子里找到了一篇不错的博客.下面把学习的过程记录下来,方便以后复习. 学习过程参考大神的博客:http://www.cnblogs.com/ ...
- Web APi之EntityFramework【CRUD】(三)
前言 之前我们系统学习了EntityFramework,个人觉得有些东西不能学了就算完了,必须要学以致用,在Web API上也少不了增(C).删(D).改(U).查(R).鉴于此,我们通过EF来实现W ...
- 基于nopcommerce b2c开源项目的精简版开发框架Nop.Framework
http://www.17ky.net/soft/70612.html?v=1#0-sqq-1-39009-9737f6f9e09dfaf5d3fd14d775bfee85 项目详细介绍 该开源项目是 ...
- 基于nopCommerce的开发框架(附源码)
.NET的开发人员应该都知道这个大名鼎鼎的高质量b2c开源项目-nopCommerce,基于EntityFramework和MVC开发,拥有透明且结构良好的解决方案,同时结合了开源和商业软件的最佳特性 ...
- ABP 初探 之基于EasyUI的CURD
结束了天天加班的项目,项目虽然结束,但还是有点小问题,只能在后期优化当中完成了,本次做项目采用了,MVC.Webapi.Entityframework,在园了里看到了有关ABP的介绍,同样ABP也是最 ...
- entity framework 新手入门篇(3)-entity framework实现orderby,count,groupby,like,in,分页等
前面我们已经学习了entityframework的基本的增删改查,今天,我们将在EF中实现一些更加贴近于实际功能的SQL方法. 承接上面的部分,我们有一个叫做House的数据库,其中包含house表和 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(转)
http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html 好久没写 DDD 领域驱动设计相关的文章 ...
- .NET领域最为流行的IOC框架之一Autofac
一.前言 Autofac是.NET领域最为流行的IOC框架之一,微软的Orchad开源程序使用的就是Autofac,Nopcommerce开源程序也是用的Autofac. Orchad和Nopcomm ...
随机推荐
- Linux Loop设备 使用
有时候需要一个独立的块设备,loop设备是个方便的选择,可通过如下方式创建 dd if=/dev/zero of=./loopback_file bs=1M count=1000 losetup /d ...
- 怎样关闭占用80端口的pid为4的进程
我也被这个问题给纠结了好几天.重装系统都三次了.终于找到原因了:我用的是sqlserver 2008;解决方法:window-sqlserver 2008-配置工具-sqlserver 配置管理器 找 ...
- JS之setTimeOut与clearTimeOut
小练习1:针对HTML,分别使用 setTimeout 和 setInterval 实现以下功能: 点击按钮时,开始改变 fade-obj 的透明度,开始一个淡出(逐渐消失)动画,直到透明度为0 在动 ...
- Python代码 变量None的使用
代码中经常会有变量是否为None的判断,有三种主要的写法: 第一种是'if x is None': 第二种是 'if not x:': 第三种是'if not x is None'(这句这样理解更清晰 ...
- div,css&table布局有哪些区别
DIV+CSS布局与TABLE布局相比,有哪些优点? 1.代码少,页面文件小,下载快 Div+css的布局现在属于国际W3C标准,table不是. 都知道用div的布局代码肯定少,所有的样式都在CSS ...
- Django—Model
Django 模型是与数据库相关的,与数据库相关的代码一般写在 models.py 中,Django 支持 Sqlite3.MySQL.PostgreSQL 等数据库,只需要在 settings.py ...
- Github上600多个iOS开源项目地址
将Github上600多个iOS开源项目进行分类并且有相应介绍,小伙伴们快来看呀 地址:http://github.ibireme.com/github/list/ios/
- webpack2-webpack.config.js配置
写在前面: 了解更多:https://github.com/miaowwwww/webpack-learn 贴一个webpack.ocnfig.js 的配置属性表 一.代码分割: 1.插件 Comm ...
- canvas中的非零环绕原则
非零环绕原则: 如果需要判断某一个区域是否需要填充颜色. 就从该区域中随机的选取一个点. 从这个点拉一条直线出来, 一定要拉到图形的外面. 此时以该点为圆心.看穿过拉出的直线的线段. 如果是顺时针方向 ...
- PHP 如何实现网址伪静态
Apache的 mod_rewrite是比较强大的,在进行网站建设时,可以通过这个模块来实现伪静态. 主要步骤如下: 1.检测Apache是否开启mod_rewrite功能 可以通过php提供 ...