某電商平臺項目開發要點記錄

来源:http://www.cnblogs.com/newton/archive/2017/05/10/6544563.html
-Advertisement-
Play Games

本文是博主在開發某電商平臺項目的一些雜項記錄,方便自己和團隊同事查閱,偏向於具體技術或應用的細節和個人理解,但也未必非常具體。文中未提的更多內容可能會另起篇章。 導航屬性——EF實體關係fluent配置 AutoMapper Autofac Repository模式 Model & DTO 開源&商 ...


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


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

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

本文基於EF6.1.3版本。

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會按照預設規則構建或尋找到正確的外鍵欄位。我們也可以顯式配置外鍵,兩種方法:

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”的錯誤。

需要註意的是,一對一的實體關係,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要求,畢竟它這麼要求確實有道理。

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

在增刪改實體時,若有上下文跟蹤,則連帶著實體的導航屬性對應的數據也一併會受影響,比如在更新父子表時,不需要自己寫單獨更細兩張表的代碼,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關鍵字了。。。


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


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:這玩意兒目前很火,日後研究。鏡像層


自動化部署-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
1、二者都是非同步模塊定義(Asynchronuous Module Definition)的一個實現;
2、CMD和AMD都是CommonJS的一種規範的實現定義,RequireJS和SeaJS是對應的實踐;
3、CMD和AMD的區別:CMD相當於按需載入,定義一個模塊的時候不需要立即制定依賴模塊,在需要的時候require就可以了,比較方便;而AMD則相反,定義模塊的時候需要制定依賴模塊,並以形參的方式引入factory中。

.gitignore只適用於尚未添加到git庫的文件。如果已經添加了,則需用git rm移除後再重新commit。

 

參考資料:

MapKey vs HasForeignKey Difference - Fluent Api

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

EF 延遲載入和預先載入

 

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • LeetCode : two sum 第一次寫博客,算是熟悉這些編輯環境吧,本來是打算在csdn上用markdown寫的,結果改了博客介紹就被關閉了,暈死。。。好了,話不多說,今天打算拿LeetCode上的第一題:Two Sum來分享試驗一下。 題目描述:Given an array of inte ...
  • 1.JSP頁面中設置輸入選項和驗證碼 <form action=login.do" method="post" > <div class="line_1" > <span class="line_1_span">會員登錄</span> <input type="text" class="form-c ...
  • 題目描述 n 個小伙伴(編號從 0 到 n-1)圍坐一圈玩游戲。按照順時針方向給 n 個位置編號,從0 到 n-1。最初,第 0 號小伙伴在第 0 號位置,第 1 號小伙伴在第 1 號位置,……,依此類推。游戲規則如下:每一輪第 0 號位置上的小伙伴順時針走到第 m 號位置,第 1 號位置小伙伴走到 ...
  • step1. 導包(導入要使用的標簽的jar文件)。 step2. 使用taglib指令引入要使用的標簽。 taglib指令: uri:標簽的命名空間。 prefix:命名空間的別名。 註: 命名空間:是為了區分同名的元素而添加的首碼。自定義標簽: step1. 寫一個java類,繼承SimpleT ...
  • 題目描述 春春幼兒園舉辦了一年一度的“積木大賽”。今年比賽的內容是搭建一座寬度為n的大廈,大廈可以看成由n塊寬度為1的積木組成,第i塊積木的最終高度需要是hi。 在搭建開始之前,沒有任何積木(可以看成n塊高度為 0 的積木)。接下來每次操作,小朋友們可以選擇一段連續區間[l, r],然後將第第 L ...
  • 在程式遇到問題調試時,有時候需要列印一些中間變數,觀察完調試完又需要把這些列印註釋掉,感覺很麻煩。所以寫了一個小程式,可以設置列印日誌等級。 在主程式設置要列印的日誌等級debug,就把相應的等級的日誌全部打出來觀察。 程式用到了va_list(在C語言中解決變參問題的一組巨集),相關知識可參考htt ...
  • 原文獻上, 點擊滴滴滴 迭代器模式(Iterator)定義: 提供一種方法順序訪問聚合對象的各個元素嗎而又不暴露該對象的內部展示 不用Iterator的壞處 原文中編寫了三個簡單的集合 ArraryList HashSet LinkedList 原先是沒有實現Iterable 內部沒有具體實現ite ...
  • 前言:Zookeeper的監聽機制很多人都踩過坑,感覺實現了watcher 介面,後面節點的變化都會一一推送過來,然而並非如此。 Watch機制官方聲明:一個Watch事件是一個一次性的觸發器,當被設置了Watch的數據發生了改變的時候,則伺服器將這個改變發送給設置了Watch的客戶端,以便通知它們 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...