EF虽然是一个晚生畸形的ORM框架,但功能强大又具有灵活性的,给了开发人员一定的发挥空间。因为微软出发点总是好的,让开发变得简单,但实际上不是所有的事情都这么理想。这里顺便推荐马丁大叔的书《企业应架构模式》。

本节主要深入分析EF的分层问题,下面是本节的已列出的要探讨内容。

  • 领域模型的概念
  • DbContext与Unit of Work 的概念
  • DbContext 创建实例及线程安全问题
  • 不要随便using或Dispose DbContext
  • DbContext的SaveChanges事务
  • Repository与UnitOfWork引入
  • DbContext T4模板的应用
  • EDM文件是放在DAL层还是Model层中?
  • EF MVC项目分层

一、领域模型的概念

领域模型:是描述业务用例实现的对象模型。它是对业务角色和业务实体之间应该如何联系和协作以执行业务的一种抽象。 业务对象模型从业务角色内部的观点定义了业务用例。定义很商业,很抽象,也很理解。一个商业的概念被引入进来之后,引发很多争议和思考。而DomainObject  在我们实际的项目又演化成大致下面几种

1.纯事务脚本对象(只有字段的set,get),没有任何业务(包括没有导航属性),可以以理解为贫血的领域模型。

2.带有自身业务的对象,例如验证业务,关联导航等。

3.对象包含量了大量的业务,而这些业务中并不是所有业务都和它相关。

尤其是第2种,界限很难划分,怎么判断这个业务是自身的,还是其它的? 或者是否重用度高呢? 第一种和第三种在之前的项目都使用过,目前个人觉得EF现在走的是第2种路线,EF在生成Model模型后,依然可以对模型进行业务修改。我们也不必在这样上面纠结太多,项目怎么做方便就怎么去实现。比如纯净的POCO我可以当DTO或VO使用;而第3种情况,我们在微软的DataSet时,也是大量使用的。想详细了解这段的可以参照这篇讨论

二、DbContext与Unit of Work 的概念

在马丁大叔中书看我们可以准看到Unit of Work 的定义:维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决。即管理对象的CRUD操作,以及相应的事务与并发问题等。Unit of Work的是用来解决领域模型存储和变更工作,而这些数据层业务并不属于领域模型本身具有的。而DbContext其实就是一个Unit of work ,只是如果直接使用这个DbContext 的话,那DbContext所有的业务都是直接暴露的,当然这是看是否项目需要了。可以看出微软的EF DbContext借用了Unit of work的思想。

三、DbContext 创建实例及线程安全问题

1. DbContext不适合创建成单例模式,例如A对象正在编辑,B对象编辑完了提交,导致正在编辑的A对象也被提交了,但是A的改可能要取消的,但是最终都被提交到数据库中了。

2. 如果DbContext创建过多的实例,就要控制好并发的问题,因为不同实例的DbContext可能会对同一条记录进行修改。

3. DbContext线程安全问题,同一实例的DbContext被不同线程调用会引发第一条场景的情况。不同线程使用不同实例的DbContext时又会引发第二种场景的情况。

第一种情况很难控制,而第二种情况可以采用乐观并发锁来解决,其次就是尽量避免对一记录的写操作。

四、不要随便using或Dispose DbContext

我们先来看一段代码

  1. BlogCategory cate = null;
    using (DemoDBEntities context = new DemoDBEntities())
    {
    //context.Configuration.LazyLoadingEnabled = false;
    DbSet<BlogCategory> set = context.Set<BlogCategory>();
    cate = set.Find(2);
    }
  2.  
  3. //肯定会出错 因为DbContext被释放了 无法延迟加载对象
    BlogArticle blog = cate.BlogArticle.First();

当我们在使用延迟加载的时候,如果使用using或dispose 释放掉DbContext后,就无法延迟加载导航属性。为什么?我们来看一下DbContext是如何加载对象以及导航属性的。

将上面的代码修改一下

  1. static void Main(string[] args)
    {
    BlogCategory cate = null;
    using (DemoDBEntities context = new DemoDBEntities())
    {
    //context.Configuration.LazyLoadingEnabled = false;
    DbSet<BlogCategory> set = context.Set<BlogCategory>();
    cate = set.Find(2);
  2.  
  3. //肯定会出错 因为DbContext被释放了 无法延迟加载对象
    BlogArticle blog = cate.BlogArticle.First();
    }
  4.  
  5. Console.ReadLine();
    }

我们打开SQL Server Profiler 来监视一上面的代码执行情况

可以看如果DbContext如果在第一次读取BlogCategory被释放后,那在加载导航属性的时候肯定不会执行成功。

另外一点:为什么很多人一定要using 或dispose掉DbContext ?

是担心数据库连接没有释放?还是担心DbContext占用过多资源呢?

首先担心数据库连接没有释放肯定是多余的,因为DbContext在SaveChanges完成后会释放掉打开的数据库连接,我们来反编译一下SaveChages的源码看看

  1. public virtual int SaveChanges(SaveOptions options)
    {
    this.OnSavingChanges();
    if ((SaveOptions.DetectChangesBeforeSave & options) != SaveOptions.None)
    {
    this.ObjectStateManager.DetectChanges();
    }
    if (this.ObjectStateManager.SomeEntryWithConceptualNullExists())
    {
    throw new InvalidOperationException(Strings.ObjectContext_CommitWithConceptualNull);
    }
    bool flag = false;
    int objectStateEntriesCount = this.ObjectStateManager.GetObjectStateEntriesCount(EntityState.Modified | EntityState.Deleted | EntityState.Added);
    using (new EntityBid.ScopeAuto("<dobj.ObjectContext.SaveChanges|API> %d#, affectingEntries=%d", this.ObjectID, objectStateEntriesCount))
    {
    EntityConnection connection = (EntityConnection) this.Connection;
    if (0 >= objectStateEntriesCount)
    {
    return objectStateEntriesCount;
    }
    if (this._adapter == null)
    {
    IServiceProvider providerFactory = connection.ProviderFactory as IServiceProvider;
    if (providerFactory != null)
    {
    this._adapter = providerFactory.GetService(typeof(IEntityAdapter)) as IEntityAdapter;
    }
    if (this._adapter == null)
    {
    throw EntityUtil.InvalidDataAdapter();
    }
    }
    this._adapter.AcceptChangesDuringUpdate = false;
    this._adapter.Connection = connection;
    this._adapter.CommandTimeout = this.CommandTimeout;
    try
    {
    this.EnsureConnection();
    flag = true;
    Transaction current = Transaction.Current;
    bool flag2 = false;
    if (connection.CurrentTransaction == null)
    {
    flag2 = null == this._lastTransaction;
    }
    using (DbTransaction transaction = null)
    {
    if (flag2)
    {
    transaction = connection.BeginTransaction();
    }
    objectStateEntriesCount = this._adapter.Update(this.ObjectStateManager);
    if (transaction != null)
    {
    transaction.Commit();
    }
    }
    }
    finally
    {
    if (flag)
    {
    this.ReleaseConnection();
    }
    }
    if ((SaveOptions.AcceptAllChangesAfterSave & options) == SaveOptions.None)
    {
    return objectStateEntriesCount;
    }
    try
    {
    this.AcceptAllChanges();
    }
    catch (Exception exception)
    {
    if (EntityUtil.IsCatchableExceptionType(exception))
    {
    throw EntityUtil.AcceptAllChangesFailure(exception);
    }
    throw;
    }
    }
    return objectStateEntriesCount;
    }

可以看到DbContext 每次打开  EntityConnection   最后都会 finally 时 通过this.ReleaseConnection() 释放掉连接,所以这个担心是多余的。

其次DbContext 是否占用过多的资源呢?DbContext确实占用了资源,主要体现在DbContext的Local属性上,每一次的增删改查,Loacl都会从数据库中加载数据,而这些数据在SaveChanges之后并没有释放掉。因此释放DbContext 是需要的,但是这样又会影响到延迟加载。这样的话,我们可以通过重载SaveChanges,在SaveChanges之后清除掉Local中的数据。但是这样做为什么有问题,我也不知道,有待考证。上一节中有介绍重载SaveChanges 清除Local 数据阻止查询数据更新。

五、DbContext的SaveChanges自带事务与分布式事务

通过反编译可以看到单实例DbContext的SaveChanges方式默认开启了事务,当同时更新多条记录时,有一条失败就会RollBack。模拟测试代码

  1. using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using EF.Model;
    using EF.DAL;
    using System.Data.Entity;
    using System.Collections;
    using System.Transactions;
    namespace EF.Demo
    {
    class Program
    {
    static void Main(string[] args)
    {
    BlogCategory cate = null;
    DemoDBEntities context = new DemoDBEntities();
  2.  
  3. //DemoDBEntities context2 = new DemoDBEntities();
    try
    {
    //using (TransactionScope scope = new TransactionScope())
    //{
    //context.Configuration.LazyLoadingEnabled = false;
    DbSet<BlogCategory> set = context.Set<BlogCategory>();
  4.  
  5. cate = new BlogCategory();
    cate.CateName = "2010-7";
    cate.CreateTime = DateTime.Now;
  6.  
  7. cate.BlogArticle.Add(new BlogArticle() { Title = "2011-7-15" });
    set.Add(cate);
  8.  
  9. //由于没设置Title字段,并且CreateTime字段不能为空,故会引发异常
    context.Set<BlogArticle>().Add(new BlogArticle { BlogCategory_CateID = 2 });
    int a = context.SaveChanges();
  10.  
  11. // context2.Set<BlogArticle>().Add(new BlogArticle { BlogCategory_CateID = 2 });
    // int b = context2.SaveChanges();
  12.  
  13. // scope.Complete();
    //}
    }
    catch(Exception ex)
    {
    Console.WriteLine(ex.Message);
    }
  14.  
  15. Console.ReadLine();
    }
    }
    }

通过SQL SERVER Profile 监视到没有一句SQL语句被执行,SaveChanges事务是预执新所有操作成功后才会更新到数据库中。

我们再来测试一下分布式事务,创建的Context2用于模拟代表其它数据库

  1. using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using EF.Model;
    using EF.DAL;
    using System.Data.Entity;
    using System.Collections;
    using System.Transactions;
    namespace EF.Demo
    {
    class Program
    {
    static void Main(string[] args)
    {
    BlogCategory cate = null;
    DemoDBEntities context = new DemoDBEntities();
  2.  
  3. DemoDBEntities context2 = new DemoDBEntities();
    try
    {
    using (TransactionScope scope = new TransactionScope())
    {
    //context.Configuration.LazyLoadingEnabled = false;
    DbSet<BlogCategory> set = context.Set<BlogCategory>();
  4.  
  5. cate = new BlogCategory();
    cate.CateName = "2010-7";
    cate.CreateTime = DateTime.Now;
  6.  
  7. cate.BlogArticle.Add(new BlogArticle() { Title = "2011-7-15" });
    set.Add(cate);
    //实例1 对数据库执行提交
    int a = context.SaveChanges();
  8.  
  9. //实例2 模拟其它数据库提交 时间字段为空,无法更新成功
    context2.Set<BlogArticle>().Add(new BlogArticle { Title="2011-7-16", BlogCategory_CateID = 2 });
    int b = context2.SaveChanges();
  10.  
  11. scope.Complete();
    }
    }
    catch(Exception ex)
    {
    Console.WriteLine(ex.Message);
    }
  12.  
  13. Console.ReadLine();
    }
    }
    }

通过SQL SERVER Profile 监视,虽然context实际执行了两条SQL记录,但是context2的SQL没有执行成功,导致事务回滚,所有操作都被没有执行成功。

六、Repository与UnitOfWork引入

Repository是什么? 马丁大叔的书上同样也有解释:它是衔接数据映射层和域之间的一个纽带,作用相当于一个在内存中的域对象映射集合,它分离了领域对象和数据库访问代码的细节。Repository受DomainObject驱动,Repository用于实现不属于DomainObject的自身相关的,但又受DomainObject约束的业务。如CRUD操作就不是领域模型要关注的业务,但是领域模型最终要映射为数据关系保存到数据库中。一个领域模型要有对应的Repository来处理与数据层衔接过程。但不是所有的DomainObject对Repository约束是相同的,可能这个领域对象没有对应Repository删除操作,而别外一个却有,所以我们经常使用的泛型Repository<T> 是不合适的。但是为了代码简洁重用,大家根据实际情况还是使用了简洁的IRepository<T>接口,就像我们有时为了简单直接把POCO当DTO或VO使用了。如果不引入Repository,我觉得没有必要实现DAL层,因为DbContext本身就是DAL层,然后只要为DbContext定义好接IDAL接口从而必免与BLL层的耦合。从这里就可以看出Repository与DAL的区别,一个受域业务驱动出现的,一个是出于解除耦合出现的。

UnitOfWork 工作单元,前面已经介绍过。为了减少业务层频繁调用DbContext的SaveChanges同步数据库操作(将多个对象的更新一次提交,减少与数据库交互过程),又要保证DbContext对业务层封闭,所以我们要增加一个对业务层开放的接口。想一想如果把SaveChanges的方法下放到每个Repository中或者DAL中,那业务层在协调多个Repository事务操作时,就会频繁的写数据库。而分离了Repository中的所有SaveChanges (或者撤销以及完成单元工作后销毁等操作)后,并通过接口在业务层统一调用,这样既大大提高了效率,也体现了一个完整的单元工作业务。

七、DbContext T4模板的应用

在Model First中,我们借助于EDMX 和T4模板完成了DbContext和Model的初步设计。但是微软提供的这些模板不能满足用户的所有需求,这个时候我就要修改T4 来生成我们想要的代码。

T4模板应用非常广泛,很多ORM工具的模板也在使用的T4模板,T4也可以生成HTML,JS等多种语言。T4模板支持多种语言书写,可读性很强,也容易上手。

DbContext模板 一共分为两个 DemoDB.DbContext.tt (unit of work)和DemoDb.tt (model) 。前一节我们介绍了如何修改DemoDb.tt 模板 使我们POCO模型继承POCOEntity,这一节再修改一下DemoDb.DbContext.tt模板 使其继承IUnitOfWork接口。

首先我们在Model层中增加IUnitOfWork接口如下

  1. using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
  2.  
  3. namespace EF.Model
    {
    public interface IUnitOfWork
    {
    //事务提交
    int Save();
    }
    }

我们再修改DemoDb.DbContext.tt模板

  1. <#@ template language="C#" debug="false" hostspecific="true"#>
    <#@ include file="EF.Utility.CS.ttinclude"#><#@
    output extension=".cs"#><#
  2.  
  3. var loader = new MetadataLoader(this);
    var region = new CodeRegion(this);
    //---------------------------------------------------这里导入了DemoDB.edmx映射文件---------------add by mecity
    var inputFile = @"DemoDB.edmx";
    //---------------------------------------------------映射文件转为集合方便模板篇历生成代码--------add by mecity
    var ItemCollection = loader.CreateEdmItemCollection(inputFile);
  4.  
  5. Code = new CodeGenerationTools(this);
    EFTools = new MetadataTools(this);
    ObjectNamespace = Code.VsNamespaceSuggestion();
    ModelNamespace = loader.GetModelNamespace(inputFile);
  6.  
  7. EntityContainer container = ItemCollection.GetItems<EntityContainer>().FirstOrDefault();
    if (container == null)
    {
    return string.Empty;
    }
    #>
    //------------------------------------------------------------------------------
    // <auto-generated>
    // <#=GetResourceString("Template_GeneratedCodeCommentLine1")#>
    //
    // <#=GetResourceString("Template_GeneratedCodeCommentLine2")#>
    // <#=GetResourceString("Template_GeneratedCodeCommentLine3")#>
    // </auto-generated>
    //------------------------------------------------------------------------------
  8.  
  9. <#
  10.  
  11. if (!String.IsNullOrEmpty(ObjectNamespace))
    {
    #>
    namespace <#=Code.EscapeNamespace(ObjectNamespace)#>
    {
    <#
    PushIndent(CodeRegion.GetIndent(1));
    }
  12.  
  13. #>
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    //---------------------------------------------------这加加入对EF.Model命名空间的引用---------------add by mecity
    using EF.Model;
    <#
    if (container.FunctionImports.Any())
    {
    #>
    using System.Data.Objects;
    <#
    }
    #>
  14.  
  15. //---------------------------------------------------这里加入对IUnitOfWork接口继承---------------add by mecity
    <#=Accessibility.ForType(container)#> partial class <#=Code.Escape(container)#> : DbContext,IUnitOfWork
    {
    public <#=Code.Escape(container)#>()
    : base("name=<#=container.Name#>")
    {
    <#
    WriteLazyLoadingEnabled(container);
    #>
    }
  16.  
  17. protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    throw new UnintentionalCodeFirstException();
    }
  18.  
  19. //---------------------------------------------------这里加入对IUnitOfWork接口方法的实现---------------add by mecity
    public int Save()
    {
    return base.SaveChanges();
    }

注意T4模板中加了注释的地方,保存模板后,就会重新创建DemoDBEntities,看一下模板修改后生成后的代码

  1. //------------------------------------------------------------------------------
    // <auto-generated>
    // 此代码是根据模板生成的。
    //
    // 手动更改此文件可能会导致应用程序中发生异常行为。
    // 如果重新生成代码,则将覆盖对此文件的手动更改。
    // </auto-generated>
    //------------------------------------------------------------------------------
  2.  
  3. namespace EF.DAL
    {
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    //---------------------------------------------------这加加入对EF.Model命名空间的引用---------------add by mecity
    using EF.Model;
  4.  
  5. //---------------------------------------------------这里加入对IUnitOfWork接口继承---------------add by mecity
    public partial class DemoDBEntities : DbContext,IUnitOfWork
    {
    public DemoDBEntities()
    : base("name=DemoDBEntities")
    {
    }
  6.  
  7. protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    throw new UnintentionalCodeFirstException();
    }
  8.  
  9. //---------------------------------------------------这里加入对IUnitOfWork接口方法的实现---------------add by mecity
    public int Save()
    {
    return base.SaveChanges();
    }
  10.  
  11. public DbSet<BlogArticle> BlogArticle { get; set; }
    public DbSet<BlogCategory> BlogCategory { get; set; }
    public DbSet<BlogComment> BlogComment { get; set; }
    public DbSet<BlogDigg> BlogDigg { get; set; }
    public DbSet<BlogTag> BlogTag { get; set; }
    public DbSet<BlogMaster> BlogMaster { get; set; }
    }
    }

八、EDM文件是放在DAL层还是Model层中?

记得我第一篇EF介绍中将EDMX文件和Model放在一起,这样做有一定风险,按照领域模型的概念,Model中这些业务对象被修改的可能性非常高,并且每个业务对象的修改的业务都可能不同,因此修改DemoDB.tt模板满足所有对象是不实现的, 并且意外保存EDMX文件时,也会导致Model手动修改的内容丢失。因此EDMX不适合和Model放在一起,最好移至到DAL层或Repository层。DAL中的DemoDb.DbContext.tt模板生成代码是相对固定的(只有一个DemoDBEntities类),因此对DemoDb.DbContext.tt模板的修改基本可以满足要求。见上节T4应用。我们可以先在DAL中的EDMX完成POCO对象的初步生成与映射关系工作后,再移至到Model中处理。

九、EF MVC项目分层

就目前CodePlex上的微软项目NorthwindPoco/Oxite/Oxite2)以及其它开源的.net mvc EF项目分层来看,大致结构如下

View   视图

Controller 控制器

IService  Controller调用具体业务的接口

Service   IService的具体实现 ,利用IOC注入到Controller

Repository 是IRepository 的具体实现,利用IOC注入到Service

Model+IRepository  因为IRepository接口对应的是DoaminModel约束业务,并且都是直接开放给Service 调用的,所以放在一个类库下也容易理解,当然分开也无影响。

VO/DTO  ViewObject与DTO 传输对象类库

当然这只是参考,怎么合理分层还是依项目需求,项目进度,资源情况以及后期维护等情况而定。

Entity Framework之深入分析的更多相关文章

  1. [转]C#综合揭秘——Entity Framework 并发处理详解

    本文转自:http://www.cnblogs.com/leslies2/archive/2012/07/30/2608784.html 引言 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的 ...

  2. ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第二章:利用模型类创建视图、控制器和数据库

    在这一章中,我们将直接进入项目,并且为产品和分类添加一些基本的模型类.我们将在Entity Framework的代码优先模式下,利用这些模型类创建一个数据库.我们还将学习如何在代码中创建数据库上下文类 ...

  3. Entity Framework Core 1.1 升级通告

    原文地址:https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-entity-framework-core-1-1/ 翻译:杨晓东 ...

  4. Entity Framework Core 实现MySQL 的TimeStamp/RowVersion 并发控制

    将通用的序列号生成器库 从SQL Server迁移到Mysql 遇到的一个问题,就是TimeStamp/RowVersion并发控制类型在非Microsoft SQL Server数据库中的实现.SQ ...

  5. 采用MiniProfiler监控EF与.NET MVC项目(Entity Framework 延伸系列1)

    前言 Entity Framework 延伸系列目录 今天来说说EF与MVC项目的性能检测和监控 首先,先介绍一下今天我们使用的工具吧. MiniProfiler~ 这个东西的介绍如下: MVC Mi ...

  6. 来,给Entity Framework热热身

    先来看一下Entity Framework缓慢的初始化速度给我们更新程序带来的一种痛苦. 我们手动更新程序时通常的操作步骤如下: 1)把Web服务器从负载均衡中摘下来 2)更新程序 3)预热(发出一个 ...

  7. 采用EntityFramework.Extended 对EF进行扩展(Entity Framework 延伸系列2)

    前言 Entity Framework 延伸系列目录 今天我们来讲讲EntityFramework.Extended 首先科普一下这个EntityFramework.Extended是什么,如下: 这 ...

  8. Entity Framework教程(第二版)

    源起 很多年前刚毕业那阵写过一篇关于Entity Framework的文章,没发首页却得到100+的推荐.可能是当时Entity Framework刚刚发布介绍EF的文章比较少.一晃这么多年过去了,E ...

  9. Entity Framework 6 Recipes 2nd Edition 译 -> 目录 -持续更新

    因为看了<Entity Framework 6 Recipes 2nd Edition>这本书前面8章的翻译,感谢china_fucan. 从第九章开始,我是边看边译的,没有通读,加之英语 ...

随机推荐

  1. 命令行下从bak文件恢复sqlserver数据库方法

    命令行下从bak文件恢复sqlserver数据库方法 注:本文所示访问从SqlServer 2000 - 2014版都是通用的 参考:http://blog.sina.com.cn/s/blog_5c ...

  2. easyui tree loader用法

    easyui的tree每次都展开,在获取子节点,自定义参数解决方案,兄跌是不是找很久了! 直接上代码 //重写tree的loader $.extend($.fn.tree.defaults, { lo ...

  3. register_shutdown_function函数详解--脚本退出时执行回调函数

    register_shutdown_function — Register a function for execution on shutdown. ps:Registers a callback  ...

  4. C#中RSA加密解密和签名与验证的实现

    RSA加密算法是一种非对称加密算法.在公钥加密标准和电子商业中RSA被广泛使用.RSA是1977年由罗纳德•李维斯特(Ron Rivest).阿迪•萨莫尔(Adi Shamir)和伦纳德•阿德曼(Le ...

  5. 【转】exec xargs的区别 另附eval命令介绍

    -exec:  对符合条件的文件执行所给的Linux 命令,执行exec后面的shell脚本.脚本中,{}表示命令的参数即为所找到的文件,以:表示comman命令的结束.\是转义符,因为分号在命令中还 ...

  6. 升级Linux内核导致vmware无法使用(vmnet模块无法编译)解决方式

    近期将ubuntu升级到了14.04,出现了vmware无法启动的情况. 详细表现为:每次启动的时候都会弹出一个VMWare Kernel Module Updater的对话框,要求依据当前内核版本号 ...

  7. hibernate调用mysql存储过程

    在mysql中创建两个存储过程,如下: 1.根据id查找某条数据: )) begin select * from emp where empId=id; end; 2.根据id查找某个字段,并返回 ) ...

  8. sudo 和环境变量

    https://askubuntu.com/questions/57915/environment-variables-when-run-with-sudo https://www.phusionpa ...

  9. JavaWeb应用项目中文乱码的解决

     转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6383542.html 在把web项目成功部署到云主机后,通过浏览器成功地访问到了你的网站,却发现页面里本该由 ...

  10. PgSql备份pg_dump与还原手记pg_restore

    真没有想到,以前一直是PostgreSQL使用者,突然需要库移植又成了头一招了!原来它与mysql命令行操作区别还挺大.不用怕,但绝对要细心,因为数据库操作是网站的核心,一旦出现损坏或丢失,后果就非常 ...