本文是博主在開發某電商平臺項目的一些雜項記錄,方便本身和團隊同事查閱,偏向於具體技術或應用的細節和我的理解,但也未必很是具體。文中未提的更多內容可能會另起篇章。html
實體關係——一對一[或零],一對多,多對多——對應到數據庫就是外鍵約束。爲了性能及數據遷移考慮,在事務性要求不高的情形中,咱們通常都禁用外鍵,可是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(); }
更多可參看 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提供的自定義映射——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。
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版本參照。
先來看一篇博文——博客園的大牛們,被大家害慘了,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 }
經過IOC(好比上文介紹的Autofac),動態注入業務層,業務層只引用接口(基礎的實體和集合類),不需引用特定ORM程序集。
然而,有多少項目有切換ORM的風險呢,若是真的到了須要切換ORM的地步了,未必沒有更好的方法能夠嘗試。有人說便於模擬數據mock,用於開發和測試,這卻是有點道理——鏈接到開發/測試數據庫,顯得有點「重」,也不靈活,領域模型和數據庫更改須要同步。
後來發現有RhinoMocks這個東東,它能夠針對任意接口建立出mock實例。有了IRepository,咱們就能夠MockRepository.GenerateMock<IRepository<XXX>>();就能夠出來一個TestRepository。從面向接口編程的角度來講,因爲各類ORM並無統一接口,因此咱們自定義了IRepository,其實能夠看做是代理/適配接口,並不是真正意義上的Repository模式,just提取了個接口而已。。。
說回來,大部分程序員要麼不懂設計,要麼過分設計,要麼只會套用模式,歷來不想一想這是否解決了[或帶來了]什麼問題,而他們是有存在的必要的——去填補那80%。拿之前引用過的一句話與各位共勉:設計,是一種美。就像蓋大樓,若是每座房屋都是千篇一概,那麼也就不存在架構師了。
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,目前最新穩定版是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下設置這兩個插件調用的執行文件地址:
這裏須要注意兩點:
而後在項目配置裏面,設置源碼管理:
因爲這裏是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的時代,有無必要打包(即減小請求次數)而喪失部分緩存優點(針對單個文件),本人持保留態度。
.gitignore只適用於還沒有添加到git庫的文件。若是已經添加了,則需用git rm移除後再從新commit。
參考資料:
MapKey vs HasForeignKey Difference - Fluent Api
Entity Framework Code First 學習日記(8)-一對一關係