本文是博主在開發某電商平臺項目的一些雜項記錄,方便自己和團隊同事查閱,偏向於具體技術或應用的細節和個人理解,但也未必非常具體。文中未提的更多內容可能會另起篇章。 導航屬性——EF實體關係fluent配置 AutoMapper Autofac Repository模式 Model & DTO 開源&商 ...
本文是博主在開發某電商平臺項目的一些雜項記錄,方便自己和團隊同事查閱,偏向於具體技術或應用的細節和個人理解,但也未必非常具體。文中未提的更多內容可能會另起篇章。
- 導航屬性——EF實體關係fluent配置
- AutoMapper
- Autofac
- Repository模式
- Model & DTO
- 開源&商用.NET電商平臺——NopCommerce(3.9版) & Himall(2.4版)
- 伺服器搭建-VMware vSphere Hypervisor(esxi)
- 自動化部署-Jenkins
實體關係——一對一[或零],一對多,多對多——對應到資料庫就是外鍵約束。為了性能及數據遷移考慮,在事務性要求不高的情形中,我們一般都禁用外鍵,但是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提供的自定義映射——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之。
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 }View Code
通過IOC(比如上文介紹的Autofac),動態註入業務層,業務層只引用介面(基礎的實體和集合類),不需引用特定ORM程式集。
然而,有多少項目有切換ORM的風險呢,如果真的到了需要切換ORM的地步了,未必沒有更好的方法可以嘗試。有人說便於模擬數據mock,用於開發和測試,這倒是有點道理——連接到開發/測試資料庫,顯得有點“重”,也不靈活,領域模型和資料庫更改需要同步。
後來發現有RhinoMocks這個東東,它可以針對任意介面創建出mock實例。有了IRepository,我們就可以MockRepository.GenerateMock<IRepository<XXX>>();就可以出來一個TestRepository。從面向介面編程的角度來說,由於各種ORM並沒有統一介面,所以我們自定義了IRepository,其實可以看作是代理/適配介面,並非真正意義上的Repository模式,just提取了個介面而已。。。
說回來,大部分程式員要麼不懂設計,要麼過度設計,要麼只會套用模式,從來不想想這是否解決了[或帶來了]什麼問題,而他們是有存在的必要的——去填補那80%。拿以前引用過的一句話與各位共勉:設計,是一種美。就像蓋大樓,如果每座房屋都是千篇一律,那麼也就不存在架構師了。
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:這玩意兒目前很火,日後研究。鏡像層
傳統的更新站點(測試環境)步驟:
- 從代碼伺服器上獲取最新代碼,如git
- 本地編譯
- 登錄到遠程伺服器,將編譯生成的程式集、靜態資源等(不包括web.config)覆蓋到站點文件夾
- 可能還要修改伺服器上的web.config[和其它配置文件]
- 通知相關人等站點已更新
若代碼提交頻繁,想要所有人第一時間看到效果,必須同樣頻繁的做這些操作,有沒有神器能幫我們自動做這些工作呢?當然是有的,本人用的是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,其對於的框架是RequireJS1、二者都是非同步模塊定義(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)-一對一關係
轉載請註明本文出處:http://www.cnblogs.com/newton/p/6544563.html