某電商平臺開發記要

本文是博主在開發某電商平臺項目的一些雜項記錄,方便本身和團隊同事查閱,偏向於具體技術或應用的細節和我的理解,但也未必很是具體。文中未提的更多內容可能會另起篇章。html


導航屬性——EF實體關係fluent配置linux

實體關係——一對一[或零],一對多,多對多——對應到數據庫就是外鍵約束。爲了性能及數據遷移考慮,在事務性要求不高的情形中,咱們通常都禁用外鍵,可是EF中仍可保留實體關係以方便編程。webpack

本文基於EF6.1.3版本。git

EF中有兩類關係:Independent association 和 Foreign Key association。在實體定義時能夠看出它們的不一樣。程序員

 1 //這是Independent association
 2 public class Order
 3 {
 4     public int ID { get; set; }
 5     public Customer Customer { get; set; } // <-- Customer object
 6     ...
 7 }
 8 
 9 //這是Foreign key association
10 public class Order
11 {
12     public int ID { get; set; }
13     public int CustomerId { get; set; }  // <-- Customer ID
14     public Customer Customer { get; set; } // <-- Customer object
15     ...
16 }

很明顯看到二者的差異就在因而否存在外鍵屬性,EF會按照默認規則構建或尋找到正確的外鍵字段。咱們也能夠顯式配置外鍵,兩種方法:github

1 Map(m=>m.MapKey("CustomerId"));
2 HasForeignKey(m=>m.CustomerId);

Map適用於Independent association,而HasForeignKey用於Foreign Key association。若是在Foreign Key association時使用Map,將會拋出:「CustomerId:Name:類型中的每一個屬性名必須惟一,已定義屬性名CustomerId」的錯誤。web

須要注意的是,一對一的實體關係,EF並未提供HasForeignKey指定外鍵。why?EF團隊認爲,既然兩個實體是一一對應的關係,那麼能夠由一個主鍵標識兩個實體,so,will use its primary key as the foreign key。。。也是醉了。若是硬要指定一個外鍵的話,對於Independent association還好,咱們能夠用Map,可是Foreign Key association就悲劇了。可使用WithMany()這個hack,但比較彆扭,我的是不推薦這種方法。詳情可參看One to zero-or-one with HasForeignKey。嘗試使用[ForeignKey]特性,也會報錯[好比]:系「CategoryCashDepositInfo_CategoriesInfo」中 Role「CategoryCashDepositInfo_CategoriesInfo_Source」的多重性無效。由於 Dependent Role 屬性不是鍵屬性,Dependent Role 多重性的上限必須爲「*」。so,一對一實體的外鍵也必須是它的主鍵,尼瑪。不幸遇到這種問題,在項目初期(通常來講踩坑都是比較早的),最好的方式仍是改變數據結構以適應EF要求,畢竟它這麼要求確實有道理。redis

另:若一實體沒有導航屬性,可是另外一實體包含該實體集合的屬性,那麼在生成數據庫時,EF也會自動爲它們生成外鍵約束。docker

在增刪改實體時,如有上下文跟蹤,則連帶着實體的導航屬性對應的數據也一併會受影響,好比在更新父子表時,不須要本身寫單獨更細兩張表的代碼,EF都幫咱們處理好了。舉個典型例子:數據庫

public class Journal
{
    public int ID { get; set; }
    public decimal Amount { get; set; }
    public int OrderID { get; set; }
    public BillOrder Order { get; set; }

}

public class BillOrder
{
    public int ID { get; set; }
    public string Title { get; set; }
}

using (var context = new Entities())
{
    var order = new BillOrder { Title="test order" };
    //OrderID =order.ID 有無都同樣,最後數據表裏字段會賦予實際值
    var j = new Journal { Amount=10, Order= order,OrderID =order.ID };
    context.Journals.Add(j);//只要add主類便可
    context.SaveChanges();
}
View Code

更多可參看 MVC3+EF4.1學習系列(六)-----導航屬性數據更新的處理

待驗證:一對一時,導航屬性有沒有延遲加載一說?另導航屬性鏈查詢細節,好比Comment.OrderItem.OrderInfo.PayDate,其中OrderItem是Comment的導航熟悉,OrderInfo是OrderItem的導航屬性,這個時候SQL查詢步驟是怎樣的呢?——一對一時,不會自動加載,即獲取父對象後,導航屬性對應的子對象一直爲null(無論後續有沒有用到,用到的話會拋出NullReferenceException),可是在獲取父對象時使用Include顯式加載子對象,是能夠的。同其它導航屬性同樣,以前測試出現沒法加載是由於忘記給導航屬性前面添加virtual關鍵字了。。。

導航屬性的刪除更新操做須要特別注意,若是直接將導航屬性賦值爲新對象,保存後,數據表中將新增記錄,而原記錄仍然存在,緣由顯而易見,這裏不說了。

1 var order = context.BillOrders.First();
2 context.Set<BillOrderSub>().RemoveRange(order.Items);//這步不能少
3 var items = new int[] { 1, 1, 1 }.Select(i => new BillOrderSub()).ToArray();
4 order.Items = items;
5 context.SaveChanges();

注意要使用DbSet定義的RemoveRange之類的方法,不然會報下面的錯誤

另:給父對象設置EntityState,並不會自動給導骯實體賦予相同EntityState。刪除父對象時,一對一的導航實體不會自動跟着刪除;如果一對多的狀況,那麼只要刪除父對象,導航實體會自動被刪除;多對多的狀況未驗證,由於涉及到映射表,推測會自動刪除映射關係,即刪除映射表裏的相關記錄。經測試,多對多狀況,也沒法自動刪除。


AutoMapper

AutoMapper提供的自定義映射——Custom value resolvers 和 Projection,乍看之下彷佛差很少,側重解決點是不同的,可是它們彷佛又通用。。。在使用上,後者MapFrom方法的其中一個重載接收表達式樹(Expression)類型的參數,所以涉及到稍微複雜的語句,可能出現以下圖所示的狀況:

這個時候只能採用前者的ResolveUsing方法了,以下:

還有個IValueResolver接口,與IMemberValueResolver的區別在於IValueResolver不指定source類的哪一個屬性須要轉換,這就致使了轉換時自定義邏輯可能要引用source類,若是其它類也有相似轉換,那麼就不能複用了。

6.0.1.0版本,以下寫法,則只有最後一個Resolver起做用。

改爲下面寫法,則無問題。

 

注意到上面opt => opt.ResolveUsing<ShopGradeResolver>(),每次Mapper.Map的時候都會new一個ShopGradeResolver對象,實際上是不必的,由於只執行邏輯而狀態無關。因此可改成單例模式:opt => opt.ResolveUsing(Singleton<ShopGradeResolver>.Instance)。

另,當source類有導航屬性時,會在Mapper.Map時去數據庫裏查,所以若用不到該導航屬性則應該設置映射規則時ignore之。

Mapper.Initialize調用屢次,最後一次會覆蓋前面的,所以若是映射是由各個項目本身處理,那麼應該考慮使用Profile,而後在主項目中 Mapper.Initialize(cfg => cfg.AddProfiles(typeFinder.GetAssemblies())); AutoMapper will scan the designated assemblies for public classes inheriting from Profile and add them to the configuration. 更多請看 Configuration


Autofac

Lifetime Scope Instance Scope,咱們獲取實例,都要先BeginLifetimeScope(),然後根據組件註冊時的InstanceScope策略,獲取組件實例。InstanceScope中,InstancePerRequest在Asp.net MVC等站點開發時比較經常使用,即對每一請求返回同一實例,though, it’s still just instance per matching lifetime scope——MatchingScopeLifetimeTags.RequestLifetimeScopeTag,MVC中爲「AutofacWebRequest」,在。注意,ASP.NET Core uses Instance Per Lifetime Scope rather than Instance Per Request. 如何在MVC中使用,請參看文檔:http://docs.autofac.org/en/latest/faq/per-request-scope.html?highlight=RequestLifetimeScope#implementing-custom-per-request-semantics

It is important to always resolve services from a lifetime scope and not the root container. Due to the disposal tracking nature of lifetime scopes, if you resolve a lot of disposable components from the container (the 「root lifetime scope」), you may inadvertently cause yourself a memory leak. The root container will hold references to those disposable components for as long as it lives (usually the lifetime of the application)。

Autofac主張LifetimeScope不要線程共享,不然,You can get into a bad situation where components can’t be resolved if you spawn the thread and then dispose the parent scope.即共享scope被其它線程釋放致使組件沒法正常獲取。鑑於此,Autofac並未爲多線程共享LifetimeScope提供便捷方法,若定要如此,那麼只能人爲處理(好比將LifetimeScope做爲參數傳入線程或設爲全局靜態變量)。

以上爲4.x版本參照。


Repository模式

先來看一篇博文——博客園的大牛們,被大家害慘了,Entity Framework歷來都不須要去寫Repository設計模式。對於這位博友的觀點,在其應用場景下我表示贊同。大部分架構和模式,都是爲了達到解耦的目的,EF自己就是Repository模式實現,它讓業務層與具體數據庫解耦,便可較方便地切換不一樣數據庫。那麼假如說業務層須要同ORM解耦,去應對可能的ORM切換,那麼咱們也能夠在業務層和ORM層再套一層Repository。如下爲簡單的實現代碼:

    public partial interface IRepository<T> where T : BaseEntity
    {
        T GetById(object id);
        void Insert(T entity);
        void Insert(IEnumerable<T> entities);
        void Update(T entity);
        void Update(IEnumerable<T> entities);
        void Delete(T entity);
        void Delete(IEnumerable<T> entities);
        IQueryable<T> Table { get; }
        IQueryable<T> TableNoTracking { get; }
    }

各路ORM只要實現該接口便可,好比EF:

    public partial class EfRepository<T> : IRepository<T> where T : BaseEntity
    {
        #region Fields

        private readonly IDbContext _context;
        private IDbSet<T> _entities;

        #endregion

        #region Ctor

        public EfRepository(IDbContext context)
        {
            this._context = context;
        }

        #endregion

        #region Utilities

        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

        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);
        }

        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);
            }
        }

        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);
            }
        }

        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);
            }
        }

        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);
            }
        }

        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);
            }
        }

        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);
            }
        }
        
        #endregion

        #region Properties

        public virtual IQueryable<T> Table
        {
            get
            {
                return this.Entities;
            }
        }

        public virtual IQueryable<T> TableNoTracking
        {
            get
            {
                return this.Entities.AsNoTracking();
            }
        }

        protected virtual IDbSet<T> Entities
        {
            get
            {
                if (_entities == null)
                    _entities = _context.Set<T>();
                return _entities;
            }
        }

        #endregion
    }
View Code

經過IOC(好比上文介紹的Autofac),動態注入業務層,業務層只引用接口(基礎的實體和集合類),不需引用特定ORM程序集。

然而,有多少項目有切換ORM的風險呢,若是真的到了須要切換ORM的地步了,未必沒有更好的方法能夠嘗試。有人說便於模擬數據mock,用於開發和測試,這卻是有點道理——鏈接到開發/測試數據庫,顯得有點「重」,也不靈活,領域模型和數據庫更改須要同步。

後來發現有RhinoMocks這個東東,它能夠針對任意接口建立出mock實例。有了IRepository,咱們就能夠MockRepository.GenerateMock<IRepository<XXX>>();就能夠出來一個TestRepository。從面向接口編程的角度來講,因爲各類ORM並無統一接口,因此咱們自定義了IRepository,其實能夠看做是代理/適配接口,並不是真正意義上的Repository模式,just提取了個接口而已。。。

說回來,大部分程序員要麼不懂設計,要麼過分設計,要麼只會套用模式,歷來不想一想這是否解決了[或帶來了]什麼問題,而他們是有存在的必要的——去填補那80%。拿之前引用過的一句話與各位共勉:設計,是一種美。就像蓋大樓,若是每座房屋都是千篇一概,那麼也就不存在架構師了。


Model & DTO

POCO:Plain Old Class Object,也就是最基本的CLR Class,在原先的EF中,實體類一般是從一個基類繼承下來的,並且帶有大量的屬性描述。而POCO則是指最原始的Class,換句話說這個實體的Class僅僅須要從Object繼承便可,不須要從某一個特定的基類繼承。在EF中,主要是配合Code First使用。Cost Frist則是指咱們先定義POCO這樣的實體class,而後生成數據庫。實際上如今也可使用Entity Framework Power tools將已經存在的數據庫反向生成POCO的class(不經過edmx文件)。——該段來自某博問回答。

Model:領域模型。能夠包含行爲(方法/邏輯)

DTO:數據傳輸對象。The canonical definition of a DTO is the data shape of an object without any behavior( 不包含行爲)。

ViewModel:是在MVVM模式中,在展現層頻繁使用的Model

不少人糾結Model和DTO的關係,怎麼用,哪一個在下哪一個在上,搭建項目時就照貓畫虎用上了,而後再想要分析出個這麼用的緣由來。網上也不乏誤人子弟的觀點,彷佛只要是個項目,都要「DTO」一把。其實從它們出現的目的去理解就很清楚了,DTO能夠看做一種模式,避免了屢次調用數據的問題,好比本來取當前用戶的姓名和性別,要分兩次,現下咱們只要定義一個包含這兩個屬性的User類,客戶端獲取當前用戶,服務端一次取出兩個屬性值並構造出User對象返回,只要請求一次就能夠了。咱們如今面向對象編程,基本上很天然地就使用了這種方式。因此領域模型和DTO並不是先後/平級關係,或者說並不是相同概念,POCO/Model都是DTO的一種實現方式,咱們能夠繼續封裝,多個類再組合成爲更大的類,目的就是減小服務請求次數,這即是DTO。


開源&商用.NET電商平臺——NopCommerce(3.9版) & Himall(2.4版)

筆者大體看了下二者的代碼,總的來講,各有優缺點。優勢就不說了,畢竟這麼長時間的優化(前者是代碼層面,後者更多的是功能業務上)。下面說說初步看到的缺點。

二者的代碼架構都有問題。如NopCommerce的Core項目,引用了Web相關的dll,不過Nop能夠認爲就是專爲Web搭建的,因此這麼作也無可厚非。可是實際開發時仍是得將底層項目純粹化,畢竟其它類型的項目(如windows服務)也要構建其上。Himall甚至有循環引用的問題,爲了不編譯出錯,使用了運行時動態加載的方式,然而我沒找到非得相互引用的緣由。

Himall中,所謂的快遞插件是快遞模板(用於打印),插件的配置數據保存在插件目錄下的config.xml,NopCommerce中,插件能夠在安裝時初始化配置[和其保存地方好比數據庫]。和NopCommerce不一樣,Himall的插件並不能自呈現(不能自定義view)。另插件尋找方式二者也不一樣,himall是先到目錄下找dll(根據名稱規則),再找相關配置,而nopcommerce是先找配置(Description.txt),再找相關dll,兩種方式並沒有優劣,但從代碼實現上來說後者比前者好。

Himall可能經手了太多人,許多邏輯或思考有重複的嫌疑,其實徹底能夠合爲一處,不少影響性能的地方也未做處理,如AutoMapper在每次實例轉換時都要去創建一遍映射規則,將其置於應用程序啓動時執行一次便可,舉手之勞不知爲什麼不作。

NopCommerce彷佛沒有用事務。。。

NopCommerce都是經過構造函數注入實例,以下

        private readonly IRepository<ShoppingCartItemInfo> _shoppingcartItemRep;
        private readonly IRepository<ProductInfo> _productRep;
        private readonly IRepository<UserMemberInfo> _userRep;

        public CartService(IRepository<ShoppingCartItemInfo> shoppingcartItemRep,IRepository<ProductInfo> productRep,IRepository<UserMemberInfo> userRep)
        {
            this._shoppingcartItemRep = shoppingcartItemRep;
            this._productRep = productRep;
            this._userRep = userRep;
        }

可是並不是每次都會用到這些實例,因此我以爲仍是應該按需獲取,好比以屬性的方式

        private IRepository<ShoppingCartItemInfo> ShoppingcartItemRep
        {
            get { return EngineContext.Resolve<IRepository<ShoppingCartItemInfo>>(); }
        }

另外,這兩套框架有不少值得借鑑的地方,有興趣的同窗可自行研究,本人對它們接觸時間不長,就不展開講了。。。


服務器搭建-VMware vSphere Hypervisor(esxi)

開局一臺塔式服務器(Dell T430)一套鼠鍵,裝備全靠撿。。。windows server確定是必須的,考慮到後續要安裝如redis、git啥的,雖然大部分有windows版本,但網站最好仍是要部署到單獨系統,因此另外再安裝Linux比較好。服務器只有一臺,只能搞多個虛擬機,筆者知道的選擇有兩種:VMware Workstation 和 VMware vSphere Hypervisor(esxi),前者必定是裝在OS(Window或Linux)上的,基於OS作虛擬資源處理,然後者自己就可看做是個OS,直接操做硬件資源[分配到各個虛擬機],因此能夠認爲後者更有效率,性能更佳。vmware中文官網(https://www.vmware.com/cn.html)

從官網上下載vSphere Hypervisor,目前是6.5版,使用ultraiso作一個U盤安裝盤,可參看【親測】UltraISO 製做ESXi 的 USB 安裝盤,這裏有一個uefi的概念,能夠自行了解 UEFI是什麼?與BIOS的區別在哪裏?UEFI詳解!,直接感受就是在啓動的到時候少了自檢(內存、硬盤等硬件信息打印)這一步 。安裝和配置步驟可看 HOW TO: Install and Configure VMware vSphere Hypervisor 6.5 (ESXi 6.5)。官方中文文檔 VMware vSphere 6.5 文檔中心,感受這文檔也不完整,不少鏈接不能點,英文文檔的一下沒找到,不少東西仍是得靠搜索引擎和本身摸索。

遇到評估許可證已過時的提示,去下載個註冊機便可:)

6.5版,咱們能夠在瀏覽器(VM web client)裏管理ESXi,甚至能夠直接關閉物理機(在維護模式下)。在虛擬機裏安裝完操做系統,爲了方便管理,還能夠安裝VMare Tools,安裝了VMare Tools以後,能夠經過瀏覽器直接啓動(要退出維護模式)、重啓、關閉操做系統(不然要進入到操做系統界面去作這些操做)等(聽說還有系統間複製粘貼之類的功能)。

固然了,咱們安裝好系統之後,直接遠程登陸操做更方便。

從官網上下了windows server 2016標準版安裝後,顯示已激活,但水印提示180天到期,以管理員權限運行cmd,輸入  DISM /online /Get-CurrentEdition,發現是評估版。而後DISM /online /Set-Edition:ServerStandard /ProductKey:XXXXX-XXXXX-XXXXX-XXXXX-XXXXX /AcceptEula(產品密鑰是網上找的),執行完成後重啓,水印提示沒了(已非評估版),可是卻顯示未激活。。。提示以下:

並無說未激活就不能用的意思,先用着吧,等哪天網上能找到靠譜的密鑰。。。(用於測試環境,so,問題不會太大)

另外建立了一個虛擬機用於安裝centos,過程不贅述。以前經過windows系統去遠程登陸linux須要安裝ssh客戶端,因爲筆者的PC系統是win10,能夠安裝Ubuntu子系統,而後經過Ubuntu去鏈接遠程centos(Ubuntu默認安裝了ssh),以下:

KVM切換器:用於多臺主機一臺顯示器,切換顯示

iDRAC:Integrated Dell Remote Access Controller,也就是集成戴爾遠程控制卡,使用它,能夠遠程進行安裝系統,重啓等等本來須要進入機房才能進行的操做。

Server Core:windows server 2008開始,最小化的服務器核心,去掉了幾乎全部的應用界面,而且將支持的服務器角色降到最小,只能進行活動目錄、DHCP、DNS、文件/打印、媒體、Web等幾種服務器角色的安裝,還能夠安裝Sqlserver和PHP,可否和怎麼安裝其它東西筆者並未深刻了解。咱們能夠經過命令行安裝和配置IIS,而後經過IIS客戶端,遠程發佈站點。網上資料較少,很差玩。

Docker:Docker和虛擬機都是虛擬技術,咱們從它們產生的歷史背景能夠更好地理解它們之間的區別。虛擬機使得用戶能在單臺物理主機部署多個操做系統(與物理機安裝多系統不同,不一樣虛擬機能夠安裝不一樣內核的操做系統),便於用戶學習或者最大限度的使用物理機資源;物理機首先要安裝主操做系統,虛擬機再在此之上安裝和運行——或者說「虛擬」出——它們各自的系統;說白了,虛擬機展示給用戶的角色,是一個個相互隔離的操做系統。咱們知道,在操做系統裏安裝應用[以及該應用須要的運行環境],有時是一個挺折騰的過程,特別是涉及到同應用不一樣版本共存、潛在軟件衝突等狀況;而當咱們終於在測試機上把全部環境都配置好,並運行地妥妥貼貼,發佈到線上,相同的過程還得從新來一遍,還未必能保證不出現其它問題;因爲有這些痛點,Docker就出現了,它隔離的是操做系統中的各個應用,或者說應用環境(也能夠是一個操做系統,好比咱們能夠在centos系統裏運行一個ubuntu鏡像,這就搭建了一個基於ubuntu的應用環境)。可參看 docker容器與虛擬機有什麼區別?而在centos中啓動一個ubuntu的docker,都是兩個系統,爲啥效率會比虛擬機高的多?由於ubuntu共享centos的kernel。因爲docker的前提是kernel共用,因此咱們看不到在linux下啓動一個windows鏡像,反之亦然。可參看 一篇不同的docker原理解析。另基於一個鏡像啓動多個容器,多個容器之間共享鏡像,每一個容器在啓動的時候並不須要單獨複製一份鏡像文件,減小了鏡像對磁盤空間的佔用和容器啓動時間。參看 Docker鏡像進階:瞭解其背後的技術原理


自動化部署-Jenkins

傳統的更新站點(測試環境)步驟:

  1. 從代碼服務器上獲取最新代碼,如git
  2. 本地編譯
  3. 登陸到遠程服務器,將編譯生成的程序集、靜態資源等(不包括web.config)覆蓋到站點文件夾
  4. 可能還要修改服務器上的web.config[和其它配置文件]
  5. 通知相關人等站點已更新

若代碼提交頻繁,想要全部人第一時間看到效果,必須一樣頻繁的作這些操做,有沒有神器能幫咱們自動作這些工做呢?固然是有的,本人用的是Jenkins,目前最新穩定版是2.46.2。下面以發佈Asp.net mvc站點爲例,擇要點說明如何使用。

Jenkins的一些概念:https://jenkins.io/doc/book/glossary/

在windows系統上安裝好後,Jenkins以windows服務的形式運行,並以web方式供咱們管理。打開瀏覽器進入(默認http://localhost:8080/)後,須要安裝必要的插件,好比git和msbuild,而後在Global Tool Configuration下設置這兩個插件調用的執行文件地址:

這裏須要注意兩點:

  • 我是用Chocolatey安裝的git,注意安裝好git以後可能須要重啓服務器,不然在後面設置git遠程倉庫時會提示找不到git.exe的錯誤
  • 安裝了.Net Framework的機子,能夠在C:\Windows\Microsoft.NET\Framework64\v4.0.30319下面找到MSBuild.exe,可是它的版本是4.6.xxx,很早之前的,因此不能用。筆者用的是VS2017社區版開發,去微軟官網下載Visual Studio 2017 生成工具,安裝後的版本爲15.0;這裏咱們還要安裝14.0版本的MSBuild,爲何呢,後面會說到。

而後在項目配置裏面,設置源碼管理:

因爲這裏是https協議,因此咱們要提供用戶名密碼,Jenkins會據此從遠程倉庫取代碼。那麼何時取呢,這就要在Poll SCM(Source Code Manage,這裏即git)裏設置了。好比 H H 1,15 1-11 * 表示once a day on the 1st and 15th of every month except December,H能夠看做任務名稱的hash值對應的一個數,因此不指定肯定值的話,用這個便可。間隔表示法,H/15 * * * * 表示每15分鐘取一次。具體規則在設置時點文本框右邊問號可看到。

如今能夠執行一下,不出意外Jenkins會拉取代碼,並放入 安裝目錄\Jenkins\workspace\任務名\ 下。接下來設置編譯步驟:

若是項目中引用的dll有從nuget下載獲取,這些並不會包含在SCM裏,因此咱們要先執行nuget.exe restore下載相關dll。nuget.exe這個應用程序能夠到官網下載,目前版本是3.5。當咱們執行這步的時候(注意還沒有開始編譯),提示構建失敗:

剛開始我覺得是編譯時產生的問題,通過一番堅苦卓絕的查閱,就差把MSBuild從新研究一遍(MSBuild 保留屬性和已知屬性),終於發現原來是nuget致使的。能夠參看 nuget.exe does not work with msbuild 12 as of 3.5.0 & NuGet CLI does not work with MSBuild 15.0 on Windows。總之安裝了MSBuild14就哦了。

而後正式開始編譯,能夠直接編譯web項目,可是有些項目沒有直接被web項目引用,是生成到bin目錄下,因此這裏編譯整個解決方案。筆者先用MSBuild15試之,報錯:

C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(1111,5): error MSB3644: 未找到框架「.NETFramework,Version=v4.6.1」的引用程序集。若要解決此問題,請安裝此框架版本的 SDK 或 Targeting Pack,或將應用程序的目標從新指向已裝有 SDK 或 Targeting Pack 的框架版本。請注意,將從全局程序集緩存(GAC)解析程序集,並將使用這些程序集替換引用程序集。所以,程序集的目標可能未正確指向您所預期的框架。

改用MSBuild14沒這個錯誤,可是在編譯Web項目時報錯:

error MSB4019: 未找到導入的項目「C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\WebApplications\Microsoft.WebApplication.targets」。請確認 <Import> 聲明中的路徑正確,且磁盤上存在該文件。

網上說這是安裝Visual Studio生成的路徑,我不打算在服務器(我將Jenkins安裝在服務器上)安裝VS,從開發機上目錄C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\Microsoft\VisualStudio\v15.0\WebApplications找到這個文件,而後在服務器上新建報錯的那個路徑,將之copy後解決。

編譯單元測試項目時報錯:

error CS0246: The type or namespace name 'TestMethod' could not be found (are you missing a using directive or an assembly reference?)

看了下引用的dll在vs安裝目錄下,按照剛纔的作法,將該dll拷貝到服務器,並無用,不知爲什麼。想到單元測試自己就沒必要發佈,因此新建瞭解決方案配置Test,在該配置下,取消單元測試項目的生成,而後將MSBuild的編譯參數/p:Configuration=Test。可參看 How to Exclude A Project When Building A Solution? 這樣作還有個好處,請看使用Web.Config Transformation配置靈活的配置文件

繼續,報錯:error MSB6003: 指定的任務可執行文件「tsc.exe」未能運行。未能找到路徑「C:\Program Files (x86)\Microsoft SDKs\TypeScript」的一部分。從開發機拷貝,解決。

若Jenkins和web服務器不是同一個機子,咱們須要用到發佈配置文件,好比Web Deploy,而後增長几個MSBuild參數,這裏不贅述了。

Web Deploy:先去http://www.microsoft.com/web/downloads/platform.aspx下載Microsoft Web Platform Installer,給服務器裝上,而後裝上Web Deploy3.5,大體流程可參考Web Deploy 服務器安裝設置與使用,還有一個博文【初碼乾貨】在Window Server 2016中使用Web Deploy方式發佈.NET Web應用的從新梳理稍顯複雜,沒試過。

構建完了能夠設置通知,發送郵件,要即時的話,能夠用釘釘。看到也有個微博插件,不過幾年沒更新了,不知是否還能用。


其它

動態加載程序集:在MVC中,頁面是[在請求時]使用BuildManager動態編譯的,BuildManager will search refrence assembies in the ‘bin’ folder and in the GAC。因此若頁面使用了咱們要動態加載的程序集,而程序集文件不在上述兩處,則會報錯。具體可參看Developing a plugin framework in ASP.NET MVC with medium trust,另外文中說的file lock不知道做者是怎麼解決的。

運行時貌似都會將bin目錄下的dll加載到臨時文件夾下(好比c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\vs\),因此運行時bin下的dll能刪掉,而不會提示佔用

在構造函數中使用this會發生什麼?——並無神奇的事情發生。。。

咱們能夠用requirejs等組件模塊化js代碼,使用webpack打包多個js文件合成爲一個js文件,webpack會自動分析模塊之間的依賴關係。固然webpack不僅僅這個功能,可參看 入門Webpack,看這篇就夠了。 固然在Http1.1的時代,有無必要打包(即減小請求次數)而喪失部分緩存優點(針對單個文件),本人持保留態度。

如下轉自 知乎:CMD是國內玉伯大神在開發SeaJS的時候提出來的,屬於CommonJS的一種規範,此外還有AMD,其對於的框架是RequireJS
一、兩者都是異步模塊定義(Asynchronuous Module Definition)的一個實現;
二、CMD和AMD都是CommonJS的一種規範的實現定義,RequireJS和SeaJS是對應的實踐;
三、CMD和AMD的區別:CMD至關於按需加載,定義一個模塊的時候不須要當即制定依賴模塊,在須要的時候require就能夠了,比較方便;而AMD則相反,定義模塊的時候須要制定依賴模塊,並以形參的方式引入factory中。

.gitignore只適用於還沒有添加到git庫的文件。若是已經添加了,則需用git rm移除後再從新commit。

 

參考資料:

MapKey vs HasForeignKey Difference - Fluent Api

Entity Framework Code First 學習日記(8)-一對一關係

EF 延遲加載和預先加載

Docker 核心技術與實現原理

 

轉載請註明本文出處:http://www.cnblogs.com/newton/p/6544563.html

相關文章
相關標籤/搜索