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

来源: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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...