欢迎访问 生活随笔!

ag凯发k8国际

当前位置: ag凯发k8国际 > 编程语言 > asp.net >内容正文

asp.net

从头编写 asp.net core 2.0 web api 基础框架 (4) ef配置 -ag凯发k8国际

发布时间:2024/10/12 asp.net 23 豆豆
ag凯发k8国际 收集整理的这篇文章主要介绍了 从头编写 asp.net core 2.0 web api 基础框架 (4) ef配置 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

第一部分: https://www.cnblogs.com/frank0812/p/11165940.html

第二部分:https://www.cnblogs.com/frank0812/p/11166163.html

第三部分:https://www.cnblogs.com/frank0812/p/11167963.html

github源码地址:https://github.com/solenovex/building-asp.net-core-2-web-api-starter-template-from-scratch

 

前三部分弄完,我们已经可以对内存数据进行crud的基本操作,并且可以在asp.net core 2中集成nlog了。

下面继续:

entity framework 是orm(object-relational-mapping)。orm是一种让你可以使用面向对象的范式对数据库进行查询和操作。

简单的情况下,orm可以把数据库中的表和model对象一一映射起来;也有比较复杂的情况,orm允许使用oo(面向对象)功能来做映射,例如:person作为基类,employee作为person的派生类,他们俩可以在数据库中映射成一个表;或者在没有继承的情况下,数据库中的一个表可能和多个类有映射关系。

ef core 不是 ef6的升级版,这个大家应该知道,ef core是轻量级、具有很好的扩展性的,并且是跨平台的ef版本。

ef core 目前有很多providers,所以支持很多种数据库,包括:mssql,sqlite,sql compact,postgres,mysql,db2等等。而且还有一个内存的provider,用于测试和开发。开发uwp应用的时候也可以使用ef core(用sqlite provider)。

ef core支持两种模式:

code first:简单理解为 先写c#(model),然后生成数据库。

database first:现在数据库中建立表,然后生成c#的model。

由于用asp.net core 2.0开发的项目基本都是新项目,所以建议使用code first。

entity就是普通的c#类,就像dto一样。dto是与外界打交道的model,entity则不一样,有一些dto的计算属性我们并不像保存在数据库中,所以entity中没有这些属性;而数据从entity传递到dto后某些属性也会和数据库里面的形式不一样。

首先把我们原来的product和material这两个dto的名字重构一下,改成productdto和materialdto。

建立一个entities文件夹,在里面建立product.cs:

namespace corebackend.api.entities {public class product{public int id { get; set; }public string name { get; set; }public float price { get; set; }} }

dbcontext

efcore使用一个dbcontext和数据库打交道,它代表着和数据库之间的一个session,可以用来查询和保存我们的entities。

dbcontext需要一个provider,以便能访问数据库(这里我们就用localdb吧)。

我们就建立一个dbcontext吧(大一点的项目会使用多个dbcontext)。建立mycontext并集成dbcontext:

namespace corebackend.api.entities {public class mycontext : dbcontext{public dbset products { get; set; }} }

这里我们为product建立了一个类型为dbset的属性,它可以用来查询和保存实例(针对dbset的linq查询语句将会被解释成针对数据库的查询语句)。

因为我们需要使用这个mycontext,所以就需要先在container中注册它,然后就可以在依赖注入中使用了。

打开startup.cs,修改configureservices,添加这一句话:

services.adddbcontext();

使用adddbcontext这个extension method为mycontext在container中进行注册,它默认的生命周期使scoped。

但是它如何连接数据库?这就需要连接字符串,我们需要为dbcontext提供连接字符串,这里有两种方式。

第一种是在mycontext中override onconfiguring这个方法:

namespace corebackend.api.entities {public class mycontext : dbcontext{public dbset products { get; set; }protected override void onconfiguring(dbcontextoptionsbuilder optionsbuilder){optionsbuilder.usesqlserver("xxxx connection string");base.onconfiguring(optionsbuilder);}} }

其中的参数optionsbuilder提供了一个usesqlserver()这个方法,它告诉dbcontext将会被用来连接sql server数据库,在这里就可以提供连接字符串,这就是第一种方法。

第二种方法:

先大概看一下dbcontext的源码的定义:

namespace microsoft.entityframeworkcore {public class dbcontext : idisposable, iinfrastructure, idbcontextdependencies, idbsetcache, idbcontextpoolable{public dbcontext([notnullattribute] dbcontextoptions options);

有一个constructor带有一个dbcontextoptions参数,那我们就在mycontext种建立一个constructor,并overload这个带有参数的constructor。

namespace corebackend.api.entities {public class mycontext : dbcontext{public mycontext(dbcontextoptions options):base(options){}public dbset products { get; set; }} }

这种方法相对第一种的优点是:它可以在我们注册mycontext的时候就提供options,显然这样做比第一种override onconfiguring更合理。

然后返回startup:

public void configureservices(iservicecollection services){services.addmvc(); #if debugservices.addtransient(); #elseservices.addtransient(); #endifvar connectionstring = @"server=(localdb)\mssqllocaldb;database=productdb;trusted_connection=true";services.adddbcontext(o => o.usesqlserver(connectionstring));}

使用adddbcontext的另一个overload的方法,它可以带一个参数,在里面调用usesqlserver。

关于连接字符串,我是用的是localdb,实例名是mssqllocaldb。可以在命令行查询本机localdb的实例,使用sqllocaldb info:

也可以通过vs的sql server object explorer查看:

连接字符串中的productdb是数据库名;连接字符串的最后一部分表示这是一个受信任的连接,也就是说使用了集成验证,在windows系统就是指windows凭证。

生成数据库

因为我们使用的是code first,所以如果还没有数据库的话,它应该会自动建立一个数据库。

打开mycontext:

public mycontext(dbcontextoptions options):base(options){database.ensurecreated();}

这个constructor在被依赖注入的时候会被调用,在里面写database.ensurecreated()。其中database是dbcontext的一个属性对象。

ensurecreated()的作用是,如果有数据库存在,那么什么也不会发生。但是如果没有,那么就会创建一个数据库。

但是现在就运行的话,并不会创建数据库,因为没有创建mycontext的实例,也就不会调用constructor里面的内容。

那我们就建立一个临时的controller,然后注入mycontext,此时就调用了mycontext的constructor:

namespace corebackend.api.controllers {[route("api/[controller]")]public class testcontroller: controller{private mycontext _context;public testcontroller(mycontext context){_context = context;}[httpget]public iactionresult get(){return ok();}} }

使用postman访问get这个action后,我们可以从debug窗口看见一些创建数据库和表的sql语句:

然后我们查看一下sql server object explorer:

我们可以看到数据库建立好了,里面还有dbo.products这个表。

database.ensurecreated()确实可以保证创建数据库,但是随着代码不断被编写,我们的model不断再改变,数据库应该也随之改变,而ensurecreated()就不够了,这就需要迁移(migration)

不过迁移之前,我们先看看product这个表的具体字段属性:

product的id作为了主键,而name这个字符串的长度是max,而price没有精度限制,这样不行。我们需要对model生成的表的字段进行限制!

解释一下:product这个entity中的id,根据约定(id或者productid)会被视为映射表的主键,并且该主键是自增的。

如果不使用id或者productid这两个名字作为主键的话,我们可以通过两种方式把该属性设置成为主键:data annotation注解和fluet api。我只在早期使用data annotation,后来一直使用fluent api,所以我这里只介绍fluent api吧。

针对product这个entity,我们要把它映射成一个数据库的表,所以针对每个属性,可能需要设定一些限制,例如最大长度,是否必填等等。

针对product,我们可以在mycontext里面override onmodelcreating这个方法,然后这样写:

protected override void onmodelcreating(modelbuilder modelbuilder){modelbuilder.entity().haskey(x => x.id);modelbuilder.entity().property(x => x.name).isrequired().hasmaxlength(50);modelbuilder.entity().property(x => x.price).hascolumntype("decimal(8,2)");}

第一行表示设置id为主键(其实我们并不需要这么做)。然后name属性是必填的,而且最大长度是50。最后price的精度是8,2,数据库里的类型为decimal。

fluent api有很多方法,具体请查看文档:https://docs.microsoft.com/en-us/ef/core/modeling/

然后,我们就会发现一个严重的问题。如果项目里面有很多entity,那么所有的fluent api配置都需要写在onmodelcreating这个方法里,那太多了。

所以我们改进一下,使用ientitytypeconfiguration。建立一个叫productconfiguration的类:

public class productconfiguration : ientitytypeconfiguration{public void configure(entitytypebuilder builder){builder.haskey(x => x.id);builder.property(x => x.name).isrequired().hasmaxlength(50);builder.property(x => x.price).hascolumntype("decimal(8,2)");}}

把刚才在mycontext里写的配置都移动到这里,然后修改一些mycontext的onmodelcreating方法:

protected override void onmodelcreating(modelbuilder modelbuilder){modelbuilder.applyconfiguration(new productconfiguration());}

就是把productconfiguration里面写的配置加载进来,和之前的效果是一样的。

但是项目中如果有很多entities的话也需要写很多行代码,更好的做法是写一个方法,可以加载所有实现了ientitytypeconfiguration的实现类。在老版的asp.net web api 2.2里面有一个方法可以从某个assembly加载所有继承于entitytypeconfiguration的类,但是entity framework core并没有提供类似的方法,以后我们自己写一个吧,现在先这样。

然后把数据库删掉,重新生成一下数据库:

很好!

随着代码的更改,数据库也会跟着变,所有ensurecreated()不满足要求。migration就允许我们把数据库从一个版本升级到另一个版本。那我们就研究一下,首先把数据库删了,然后创建第一个迁移版本。

打开package manager console,做个迁移 add-migration xxx:

add-migration 然后接着是一个你起的名字。

然后看一下vs的solution explorer 会发现生成了一个migrations目录:

里面有两个文件,一个是snapshot,它是目前entity的状态:

namespace corebackend.api.migrations {[dbcontext(typeof(mycontext))]partial class mycontextmodelsnapshot : modelsnapshot{protected override void buildmodel(modelbuilder modelbuilder){ #pragma warning disable 612, 618modelbuilder.hasannotation("productversion", "2.0.0-rtm-26452").hasannotation("sqlserver:valuegenerationstrategy", sqlservervaluegenerationstrategy.identitycolumn);modelbuilder.entity("corebackend.api.entities.product", b =>{b.property("id").valuegeneratedonadd();b.property("name").isrequired().hasmaxlength(50);b.property("price").hascolumntype("decimal(8,2)");b.haskey("id");b.totable("products");}); #pragma warning restore 612, 618}} }

这就是当前product这个model的状态细节,包括我们通过fluent api为其添加的映射限制等。

另一个文件是xxxx_productinfodbinitialmigration,下划线后边的部分就是刚才add-migration命令后边跟着的名字参数。

namespace corebackend.api.migrations {public partial class productinfodbinitialmigration : migration{protected override void up(migrationbuilder migrationbuilder){migrationbuilder.createtable(name: "products",columns: table => new{id = table.column(type: "int", nullable: false).annotation("sqlserver:valuegenerationstrategy", sqlservervaluegenerationstrategy.identitycolumn),name = table.column(type: "nvarchar(50)", maxlength: 50, nullable: false),price = table.column(type: "decimal(8,2)", nullable: false)},constraints: table =>{table.primarykey("pk_products", x => x.id);});}protected override void down(migrationbuilder migrationbuilder){migrationbuilder.droptable(name: "products");}} }

这里面包含着migration builder需要的代码,用来迁移这个版本的数据库。里面有up方法,就是从当前版本升级到下一个版本;还有down方法,就是从下一个版本再退回到当前版本。

我们也可以不使用 add-migration命令,手写上面这些代码也行,我感觉还是算了吧。

另外还有一件事,那就是要保证迁移migration都有效的应用于数据库了,那就是另一个命令 update-database

先等一下,我们也可以使用代码来达到同样的目的,打开mycontext:

public mycontext(dbcontextoptions options): base(options){database.migrate();}

把之前的ensurecreated改成database.migrate(); 如果数据库还没删除,那就最后删除一次。

运行,并除法testcontroller:

然后会看见product表,除此之外还有一个__efmigrationhistory表,看看有啥:

这个表里面保存了哪些迁移已经被应用于这个数据库了。这也保证了database.migrate()或者update-database命令不会执行重复的迁移migration。

我们再弄个迁移,为product添加一个属性:

namespace corebackend.api.entities {public class product{public int id { get; set; }public string name { get; set; }public float price { get; set; }public string description { get; set; }}public class productconfiguration : ientitytypeconfiguration{public void configure(entitytypebuilder builder){builder.haskey(x => x.id);builder.property(x => x.name).isrequired().hasmaxlength(50);builder.property(x => x.price).hascolumntype("decimal(8,2)");builder.property(x => x.description).hasmaxlength(200);}} }

执行add-migration后,会在migrations目录生成了一个新的文件:

namespace corebackend.api.migrations {public partial class adddescriptiontoproduct : migration{protected override void up(migrationbuilder migrationbuilder){migrationbuilder.addcolumn(name: "description",table: "products",type: "nvarchar(200)",maxlength: 200,nullable: true);}protected override void down(migrationbuilder migrationbuilder){migrationbuilder.dropcolumn(name: "description",table: "products");}} }

然后这次执行update-database命令:

加上verbose参数就是显示执行过程的明细而已。

不用运行,看看数据库:

description被添加上了,然后看看迁移表:

目前差不太多了,但还有一个安全隐患。它是:

保存连接字符串,你可能会想到appsettings.json,但这不是一个好的想法。在本地开发的时候还没有什么问题(使用的是集成验证),但是你要部署到服务器的时候,数据库连接字符串可能包括用户名和密码(sql server的另一种验证方式)。加入你不小心把appsettings.json或写到c#里面的连接字符串代码提交到了git或tfs,那么这个用户名和密码包括服务器的名称可能就被暴露了,这样做很不安全。

我们可以这样做,首先针对开发环境(development environment)把c#代码中的连接字符串拿掉,把它放到appsettings.json里面。然后针对正式生产环境(production environment),我们使用环境变量来保存这些敏感数据。

开发环境:

appsettings.json:

{"mailsettings": {"mailtoaddress": "admin__json@qq.com","mailfromaddress": "noreply__json@qq.com"},"connectionstrings": {"productioninfodbconnectionstring": "server=(localdb)\\mssqllocaldb;database=productdb;trusted_connection=true"} }

startup.cs:

public void configureservices(iservicecollection services){services.addmvc(); #if debugservices.addtransient(); #elseservices.addtransient(); #endifvar connectionstring = configuration["connectionstrings:productioninfodbconnectionstring"];services.adddbcontext(o => o.usesqlserver(connectionstring));}

然后你可以设断点看看connectionstring的值。目前项目的环境变量是production,先改成development:

然后断点调试:

可以看到这两个jsonconfigurationprovider就是appsettings的两个文件的配置。

这个就是appsettings.json,里面包含着我们刚才添加的连接字符串。

由于当前是development环境,所以如果你查看另外一个jsonconfigurationprovider的话,会发现它里面的值是空的(data数是0).

所以没有问题。

生产环境:

在项目的属性--debug里面,我们看到了环境变量:

而这个环境变量,我们可以在程序中读取出来,所以可以在这里添加连接字符串:

注意它的key,要和appsettings.json里面的整体结构一致;value呢应该是给一个服务器的数据库的字符串,这里就随便弄个假的吧。别忘了把development改成production。

然后调试一下:

没错。如果你仔细调试一下看看的话:就会从environmentvariablesconfigurationprovider的第64个找到我们刚才写到连接字符串:

但是还没完。

打开项目的launchsettings.json:

你会发现:

{"iissettings": {"windowsauthentication": false,"anonymousauthentication": true,"iisexpress": {"applicationurl": "http://localhost:60835/","sslport": 0}},"profiles": {"iis express": {"commandname": "iisexpress","launchbrowser": true,"environmentvariables": {"connectionstrings:productioninfodbconnectionstring": "server=.;database=productdb;userid=sa;password=pass;","aspnetcore_environment": "production"}},"corebackend.api": {"commandname": "project","launchbrowser": true,"environmentvariables": {"aspnetcore_environment": "development"},"applicationurl": "http://localhost:60836/"}} }

连接字符串在这里。这个文件一般都会源码控制给忽略,也不会在发布的时候发布到服务器。那么服务器怎么读取到这个连接字符串呢???

看上面调试environmentvariablesconfigurationprovider的值,会发现里面有几十个变量,这些基本都不是来自launchsettings.json,它们是从系统层面上定义的!!

这回我们这样操作:

把launchsettings里面的连接字符串去掉:

{"iissettings": {"windowsauthentication": false,"anonymousauthentication": true,"iisexpress": {"applicationurl": "http://localhost:60835/","sslport": 0}},"profiles": {"iis express": {"commandname": "iisexpress","launchbrowser": true,"environmentvariables": {"aspnetcore_environment": "production"}},"corebackend.api": {"commandname": "project","launchbrowser": true,"environmentvariables": {"aspnetcore_environment": "development"},"applicationurl": "http://localhost:60836/"}} }

然后这里自然也就没有了:

现在任何json文件都没有敏感信息了。

现在我们要把连接字符串添加到系统变量中。

在win10搜索框输入 envi:

然后点击上面的结果:

点击环境变量:

这里面上边是用户的变量,下面是系统的变量,这就是刚才environmentvariableconfigurationprovider里面调试出来的那一堆环境变量。

而这个地方就是在你应该服务器上添加连接字符串的地方。再看一下调试:

environment的provider在第4个位置,appsettings.production.json的在第3个位置。也就是说如果appsettings.product.json和系统环境变量都有一样key的连接字符串,那么程序会选择系统环境变量的值,因为它是后边的配置会覆盖前边的配置。

在系统环境变量中添加:

然后调试运行(需要重启vs,以便新添加的系统环境变量生效):

嗯,没问题!

目前ef core还没有内置的方法来做种子数据。那么自己写:

建立一个mycontextextensions.cs:

namespace corebackend.api.entities {public static class mycontextextensions{public static void ensureseeddataforcontext(this mycontext context){if (context.products.any()){return;}var products = new list{new product{name = "牛奶",price = 2.5f,description = "这是牛奶啊"},new product{name = "面包",price = 4.5f,description = "这是面包啊"},new product{name = "啤酒",price = 7.5f,description = "这是啤酒啊"}};context.products.addrange(products);context.savechanges();}} }

这是个extension method,如果数据库没有数据,那就弄点种子数据,addrange可以添加批量数据到context(被context追踪),但是到这还没有插入到数据库。使用savechanges会把数据保存到数据库。

然后再startup的configure方法中调用这个method:

public void configure(iapplicationbuilder app, ihostingenvironment env, iloggerfactory loggerfactory,mycontext mycontext){// loggerfactory.addprovider(new nlogloggerprovider());loggerfactory.addnlog();if (env.isdevelopment()){app.usedeveloperexceptionpage();}else{app.useexceptionhandler();}mycontext.ensureseeddataforcontext();app.usestatuscodepages();app.usemvc();}

首先注入mycontext,然后调用这个extension method。

然后把系统环境变量中的连接字符串删了把,并且把项目属性debug中改成development,这时候需要重启vs,因为一般环境变量是在软件启动的时候附加到其内存的,软件没关的情况下如果把系统环境变量给删了,在软件的内存中应该还是能找到该环境变量,所以软件得重启才能获取最新的环境变量们。重启vs,并运行:

种子数据进去了

 

https://www.cnblogs.com/cgzl/p/7661805.html

转载于:https://www.cnblogs.com/frank0812/p/11168796.html

总结

以上是ag凯发k8国际为你收集整理的从头编写 asp.net core 2.0 web api 基础框架 (4) ef配置的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得ag凯发k8国际网站内容还不错,欢迎将ag凯发k8国际推荐给好友。

  • 上一篇:
  • 下一篇:
网站地图