本文是博主在开发某电商平台项目的一些杂项记录,方便本身和团队同事查阅,偏向于具体技术或应用的细节和我的理解,但也未必很是具体。文中未提的更多内容可能会另起篇章。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)-一对一关系