如何使用ASP.NET Core、EF Core、ABP(ASP.NET Boilerplate)创建分层的Web应用程序(第一部分)
本文是为了学习ABP的使用,是翻译ABP官方文档的一篇实战教程,我暂时是优先翻译自己感兴趣或者比较想学习的部分,后续有时间希望能将ABP系列翻译出来,除了自己能学习外,有可能的话希望帮助一些英文阅读能力稍微差一点的同学(当然我自己也不一定翻译的多好,大家共同学习)。
其实这篇文章也花了我一些时间,突然感叹其实写文章挺不容易的,这次虽然是翻译,基本内容都是尊重原文的意思翻译,但是里面的每一句代码我都自己写了也运行测试了,截图都是自己运行的结果。
这个ABP框架真的挺不错的,已经有很多人也已经翻译了,但是好像都是以前的,但是官网有些更新可能没同步,而且自己翻译觉得记忆更深刻一些。
接受来自任何小伙伴任何方面的好评与差评!!!!!!!!!!!!!
官网原文链接:https://aspnetboilerplate.com/Pages/Documents/Articles/Introduction-With-AspNet-Core-And-Entity-Framework-Core-Part-1/index.html
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
简介
在本文中,我将展示如何使用以下工具创建一个简单的跨平台分层web应用程序:
- .Net Core作为基本的跨平台应用程序的开发框架
- ABP(ASP.NET Boilerplate)作为启动模板和应用框架
- ASP.NET Core作为Web 框架
- Entity FrameWork作为ORM框架
- BootStrap作为HTML/Css框架
- jQuery作为客户端Ajax/Dom库
- xUnit 和 Shouldly用来对服务器端的单元/集成测试
我还将Log4Net和AutoMapper,这些已经默认包含在ABP模板中。
将要用到的技术(这些技术我们暂时都不做延伸的解释,后续有时间会有专门的文章进行说明):
- 分层体系结构
- 领域驱动设计(DDD)
- 依赖注入(DI)
- 集成测试
我们将要开发一个任务管理的应用程序,任务可以进行分配给某些人。在这里,我们不用自己一层一层的去开发应用程序,而是在应用程序增长时切换到垂直层。随着应用程序的发展,我将根据需要介绍ABP和其他框架的一些特性。
前期准备
要运行和开发此示例,请提前在机器上安装下列工具:
- Visual Studio 2017
- SQL Server(可以将连接字符串更改为localdb)
- Visual Studio扩展:
- Bundler & Minifier
- Web Compile
创建应用程序
使用ABP的启动模板(http://www.aspnetboilerplate.com/Templates)来创建一个名为“acme simpletaskapp”的新web应用程序。公司名称(这里的“Acme”)是可选的。我们选择多页Web应用程序(Multi Page Web Application),在这里为了保证最基本的启动模板功能,我们也不选择SPA,并且禁用了身份验证。
它创建了一个分层的解决方案,如下所示:
它包含6个以我们创建模板时输入的项目名称开头的项目。
- .Core项目用于领域/业务层(实体、领域服务…)
- .Appilcation项目为应用层(dtos,应用服务…)
- .EntityFramework项目用于EF Core集成(从其他层抽象EF Core)
- .Web项目就是ASP.Net MVC
- .Tests项目用于单元测试和集成测试(直到应用层,不包括web层)
- .Web.Tests用来对ASP.NET Core的集成测试(包括web层的完整的集成测试)
运行一下应用程序,可以看到如下界面:
它包含一个顶部菜单,空的主页和About页面和一个切换语言下拉选项。
开发应用程序
创建一个Task实体
我想从一个简单的Task实体开始。由于实体是域层的一部分,所以我将它添加到.Core项目中:
using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text; namespace Acme.SimpleTaskSystem
{
[Table("AppTasks")]
public class Task : Entity, IHasCreationTime
{
public const int MaxTitleLength = ;
public const int MaxDescriptionLength = * ; //64KB [Required]
[MaxLength(MaxTitleLength)]
public string Title { get; set; } [MaxLength(MaxDescriptionLength)]
public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } public Task()
{
CreationTime = Clock.Now;
State = TaskState.Open;
} public Task(string title, string description = null)
: this()
{
Title = title;
Description = description;
}
} public enum TaskState : byte
{
Open = ,
Completed =
}
}
- Task继承ABP的Entity类,它默认包含int类型的Id属性。我们可以使用通用版本Entity<TPrimaryKey>来选择不同的PK类型。
- IHasCreationTime是一个简单的接口,它只定义了CreationTime属性(为CreationTime使用一个标准名称非常好)。
- Task实体定义一个必填的Title和一个可选的Description。
- TaskState是一个简单的定义任务状态的枚举。
- Clock.Now默认情况下返回DateTime.Now,但是它提供了一个抽象,所以有需要的话很容易的切换到DateTime.UtcNow。如果使用ABP框架,请用Clock.Now,而不是DateTime.Now
- 将Task实体存储到数据库中的AppTasks表中。
添加任务到DbContext
.EntityFrameworkCore项目预定义了一个DbContext,我们应该在DbContext中添加一个Task实体的DbSet:
using Abp.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; namespace Acme.SimpleTaskSystem.EntityFrameworkCore
{
public class SimpleTaskSystemDbContext : AbpDbContext
{
//Add DbSet properties for your entities...
public DbSet<Task> Tasks { get; set; }
public SimpleTaskSystemDbContext(DbContextOptions<SimpleTaskSystemDbContext> options)
: base(options)
{ }
}
}
现在EF Core知道我们已经有了一个Task实体。
创建第一个数据库迁移
我们将创建一个初始的数据库迁移来创建数据库和AppTasks表,从Visual Studio打开包管理器控制台并运行Add-Migration命令(默认项目必须是.EntityFrameworkCore项目):
此命令在.EntityFrameworkCore项目中创建一个Migrations文件夹,该文件夹包含迁移类和数据库模型的快照:
自动生成的“Initial”迁移类如下所示:
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations; namespace Acme.SimpleTaskSystem.Migrations
{
public partial class Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AppTasks",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Title = table.Column<string>(maxLength: , nullable: false),
Description = table.Column<string>(maxLength: , nullable: true),
CreationTime = table.Column<DateTime>(nullable: false),
State = table.Column<byte>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AppTasks", x => x.Id);
});
} protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AppTasks");
}
}
}
创建数据库
从包管理器控制台运行Update-Database命令创建数据库:
这条命令将在本地sql server中创建一个名为SimpleTaskSystemDb的数据库,并执行迁移:
现在,我有一个Task实体和并在数据库中有相应的表,我们添加几条示例数据:
注意,数据库连接字符串定义在.Web项目中的appsettings.json文件中。
任务应用程序服务
应用程序服务用于向表示层公开域逻辑,应用程序被表示层通过数据传输对象(DTO)作为参数(如果有需要)调用,使用域对象执行某些特定的业务逻辑,并返回一个DTO到表示层(如果需要)。
我们在.Application项目中创建一个应用程序服务TaskAppService,以执行与任务相关的应用程序逻辑,首先定义一个应用程序服务的接口。
public interface ITaskAppService : IApplicationService
{
Task<ListResultDto<TaskListDto>> GetAll(GetAllTasksInput input);
}
定义接口不是必须的,但是建议用接口。作为约定,在ABP中所有App服务都必须实现IApplicationService接口(它只是一个空的标记接口)。我创建了一个用于查询任务的GetAll方法。为此,我还定义了以下dto:
public class GetAllTasksInput
{
public TaskState? State { get; set; }
}
[AutoMapFrom(typeof(Task))]
public class TaskListDto : EntityDto, IHasCreationTime
{
public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; }
}
- GetAllTasksInput DTO定义了GetAll方法的输入参数,我没有直接将状态作为方法参数,而是将它添加到DTO对象中。这样的话,之后我们可以在DTO中添加其他参数,而不需要影响现有的客户端逻辑。
- TaskListDto用于返回任务数据。它继承自定义了一个Id属性的EntityDto(我们可以将Id添加到Dto中,而不是从EntityDto派生出来),我们定义[AutoMapFrom]属性来创建从任务实体到TaskListDto的自动映射。这个属性在Abp.AutoMapper nuget包中定义。
- 最后,ListResultDto是一个包含项目列表的简单类(我们可以直接返回一个列表)。
现在我们可以去实现ITaskAppService
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Domain.Repositories;
using Abp.Linq.Extensions;
using Microsoft.EntityFrameworkCore;
namespace Acme.SimpleTaskSystem
{
public class TaskAppService : SimpleTaskSystemAppServiceBase, ITaskAppService
{
private readonly IRepository<Task> _taskRepository; public TaskAppService(IRepository<Task> taskRepository)
{
_taskRepository = taskRepository;
} public async Task<ListResultDto<TaskListDto>> GetAll(GetAllTasksInput input)
{
var tasks = await _taskRepository
.GetAll()
.WhereIf(input.State.HasValue, t => t.State == input.State.Value)
.OrderByDescending(t => t.CreationTime)
.ToListAsync(); return new ListResultDto<TaskListDto>(
ObjectMapper.Map<List<TaskListDto>>(tasks)
);
}
}
}
- TaskAppService继承自包含在ABP启动模板中的SimpleTaskSystemAppServiceBase(它继承于ABP的ApplicationService类),这不是必需的,应用程序服务可以是普通类。但是ApplicationService基类有一些预先注入的服务(如此处使用的ObjectMapper)。
- 我用依赖注入去获得一个 repository。
- Repositories用来抽象对实体的数据库操作,ABP为每个执行公共任务的实体创建一个预定义的存储库(如这里的 IRepository<Task>), IRepository.GetAll()返回查询实体的IQueryable。
- WhereIf 是ABP中的扩展方法,用来简化IQueryable.Where
- ObjectMapper(来自ApplicationServiceBase类,默认情况下通过AutoMapper实现)用于将任务对象列表映射到TaskListDtos对象列表中。
测试TaskAppService
在进一步创建用户界面之前,我想测试TaskAppService。如果您对自动化测试不感兴趣,可以跳过这一部分。
启动模板包含一个.Tests项目来测试我们的代码。它使用EF Core提供的内存数据库来代替SQL SERVER.因此我们的单元测试可以在没有真正的数据库下工作,它为每个测试创建一个单独的数据库。因此,测试是相互隔离的。我们可以使用TestDataBuilder类在运行测试之前向内存数据库添加一些初始测试数据。我更改TestDataBuilder代码如下所示:
using Acme.SimpleTaskSystem.EntityFrameworkCore; namespace Acme.SimpleTaskSystem.Tests.TestDatas
{
public class TestDataBuilder
{
private readonly SimpleTaskSystemDbContext _context; public TestDataBuilder(SimpleTaskSystemDbContext context)
{
_context = context;
} public void Build()
{
_context.Tasks.AddRange(new Task("Follow the white rabbit", "Follow the white rabbit in order to know the reality."),
new Task("Clean your room") { State = TaskState.Completed });
}
}
}
可以看下示例项目的源代码,以了解TestDataBuilder在何处以及如何使用。我向dbcontext添加了两个任务(其中一个已经完成)。我可以编写测试,假设数据库中有两个任务。我的第一个集成测试测试上面创建的TaskAppService.GetAll()方法:
using Shouldly;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit; namespace Acme.SimpleTaskSystem.Tests
{
public class TaskAppService_Tests : SimpleTaskSystemTestBase
{
private readonly ITaskAppService _taskAppService;
public TaskAppService_Tests()
{
_taskAppService = Resolve<ITaskAppService>();
}
[Fact]
public async System.Threading.Tasks.Task Should_Get_All_Tasks()
{
// act
var output = await _taskAppService.GetAll(new GetAllTasksInput());
//Assert
output.Items.Count.ShouldBe();
}
[Fact]
public async System.Threading.Tasks.Task Should_Get_Filtered_Tasks()
{
//Act
var output = await _taskAppService.GetAll(new GetAllTasksInput { State = TaskState.Open }); //Assert
output.Items.ShouldAllBe(t => t.State == TaskState.Open);
}
}
}
我创建了两个不同的tests来测试GetAll()方法,现在我们从VS打开测试资源管理器(Test\Windows\Test Explorer)来运行单元测试
两个都成功了。注意ABP启动模板默认安装了xUnit 和 Shouldly ,所以我们才可以直接使用。
创建任务列表视图
现在,我知道TaskAppService可以正常工作,我可以开始创建一个页面来列出所有的任务。
添加一个新的菜单项
首先在顶部菜单中添加一个新的菜单
using Abp.Application.Navigation;
using Abp.Localization; namespace Acme.SimpleTaskSystem.Web.Startup
{
/// <summary>
/// This class defines menus for the application.
/// </summary>
public class SimpleTaskSystemNavigationProvider : NavigationProvider
{
public override void SetNavigation(INavigationProviderContext context)
{
context.Manager.MainMenu
.AddItem(
new MenuItemDefinition(
PageNames.Home,
L("HomePage"),
url: "",
icon: "fa fa-home"
)
).AddItem(
new MenuItemDefinition(
PageNames.About,
L("About"),
url: "Home/About",
icon: "fa fa-info"
)
).AddItem(new MenuItemDefinition(
"TaskList",
L("TaskList"),
url:"Tasks",
icon:"fa fa-tasks"));
} private static ILocalizableString L(string name)
{
return new LocalizableString(name, SimpleTaskSystemConsts.LocalizationSourceName);
}
}
}
如上所示,Startup模板附带两个页面:Home和About,我们可以修改他们,也可以自己创建新的页面,在这里我选择新创建页面。
创建TaskController 和 ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; namespace Acme.SimpleTaskSystem.Web.Controllers
{
public class TasksController : SimpleTaskSystemControllerBase
{
private readonly ITaskAppService _taskAppService;
public TasksController(ITaskAppService taskAppService)
{
_taskAppService = taskAppService;
}
public async Task<ActionResult> Index(GetAllTasksInput input)
{
var output = await _taskAppService.GetAll(input);
var model = new IndexViewModel(output.Items);
return View(model);
}
}
}
- TasksController继承SimpleTaskSystemControllerBase(继承AbpController),SimpleTaskSystemControllerBase包含此应用程序中控制器的通用基本代码。
- 为获得任务列表我注入了ITaskAppService
- 我没有直接将GetAll方法的结果传递给视图,而是在.Web项目中创建了一个IndexViewModel类,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace Acme.SimpleTaskSystem.Web
{
public class IndexViewModel
{
public IReadOnlyList<TaskListDto> Tasks { get; }
public IndexViewModel(IReadOnlyList<TaskListDto> tasks)
{
Tasks = tasks;
}
public string GetTaskLabel(TaskListDto task)
{
switch(task.State)
{
case TaskState.Open:
return "label-success";
default:
return "label-default";
}
}
}
}
这个简单的视图模型在其构造函数中获取任务列表(由ITaskAppService提供)。它还具有GetTaskLabel方法,该方法将在视图中用于为给定任务选择Bootstrap标签类。
创建任务列表页
最后Index视图页如下所示:
@using Acme.SimpleTaskSystem.Web.Startup
@model Acme.SimpleTaskSystem.Web.IndexViewModel
@{
ViewBag.Title = L("TaskList");
ViewBag.ActiveMenu = PageNames.TaskList; //和SimpleTaskSystemNavigationProvider定义的菜单名字相匹配,以高亮显示菜单项
} <h2>@L("TaskList")</h2>
<div class="row">
<div>
<ul class="list-group" id="TaskList">
@foreach(var task in Model.Tasks)
{
<li class="list-group-item">
<span class="pull-right label @Model.GetTaskLabel(task)">@L($"TaskState_{task.State}")</span>
<h4 class="list-group-item-heading">@task.Title</h4>
<div class="list-group-item-text">
@task.CreationTime.ToString("yyyy-MM-dd HH:mm:ss")
</div>
</li>
}
</ul>
</div>
</div>
我们只是简单的使用给定的模型以及Bootstrap的 list group组件去呈现视图。在这里,我们使用了IndexViewModel.GetTaskLabel()方法来获取任务的标签类型。渲染的页面是这样的:
本地化
我们在视图中使用ABP框架的L方法,用于定义本地化字符串,我们已经在.Core项目中的Localization/SourceFiles文件夹下将其定义在.json文件中。en本地化如下:
{
"culture": "en",
"texts": {
"HelloWorld": "Hello World!",
"ChangeLanguage": "Change language",
"HomePage": "HomePage",
"About": "About",
"Home_Description": "Welcome to SimpleTaskSystem...",
"About_Description": "This is a simple startup template to use ASP.NET Core with ABP framework.",
"TaskList": "TaskList",
"Open": "open",
"TaskState_Open": "Open",
"TaskState_Completed": "Completed"
}
}
除了最后三行是新加的,其他全是启动模板自带的,我们可以根据情况进行删除。
过滤任务
正如上面所示,TasksController实际上获得一个GetAllTasksInput,可以用来过滤任务。我们可以在任务列表视图中添加下拉菜单来过滤任务。这里我们将下拉菜单添加到标题标签中:
<h2>@L("TaskList")
<span class="pull-right">
@Html.DropDownListFor(
model => model.SelectedTaskState,
Model.GetTasksStateSelectListItems(LocalizationManager),
new
{
@class = "form-control",
id = "TaskStateCombobox"
})
</span>
</h2>
然后我在 IndexViewModel中增加SelectedTaskState属性和GetTasksStateSelectListItems方法:
public TaskState? SelectedTaskState { get; set; } public List<SelectListItem> GetTasksStateSelectListItems(ILocalizationManager localizationManager)
{
var list = new List<SelectListItem>
{
new SelectListItem
{
Text = localizationManager.GetString(SimpleTaskSystemConsts.LocalizationSourceName, "AllTasks"),
Value = "",
Selected = SelectedTaskState == null
}
}; list.AddRange(Enum.GetValues(typeof(TaskState))
.Cast<TaskState>()
.Select(state =>
new SelectListItem
{
Text = localizationManager.GetString(SimpleTaskSystemConsts.LocalizationSourceName, $"TaskState_{state}"),
Value = state.ToString(),
Selected = state == SelectedTaskState
})
); return list;
}
在控制器中设置SelectedTaskState:
public async Task<ActionResult> Index(GetAllTasksInput input)
{
var output = await _taskAppService.GetAll(input);
var model = new IndexViewModel(output.Items)
{
SelectedTaskState = input.State
};
return View(model);
}
现在,我们可以运行应用程序查看视图右上角的combobox:
现在这个combobox 只是显示出来了,还不能用,我们现在写一个javascript代码当combobox值改变时重新请求和刷新任务列表。
我们在.Web项目中创建wwwroot\js\views\tasks\index.js文件:
(function ($) {
$(function () {
var _$taskStateCombobox = $("#TaskStateCombobox");
_$taskStateCombobox.change(function () {
location.href = '/Tasks?state' + _$taskStateCombobox.val();
});
});
})(jQuery)
在视图中引用index.js之前,我使用了VS扩展Bundler & Minifier(这是在ASP.Net Core项目中缩小文件的默认方式,在vs->工具->扩展和更新->下载)来缩小脚本:
这将在.Web项目的bundleconfig.json的文件中自动添加如下代码:
{
"outputFileName": "wwwroot/js/views/tasks/index.min.js",
"inputFiles": [
"wwwroot/js/views/tasks/index.js"
]
}
并创建一个缩小的index.min.js文件
每当index.js改变时,index.min.js也会自动改变,现在我们将js文件加到对应的视图中:
@section scripts
{
<environment names="Development">
<script src="~/js/views/tasks/index.js"></script>
</environment> <environment names="Staging,Production">
<script src="~/js/views/tasks/index.min.js"></script>
</environment>
}
有了上面的代码,我们可以在开发环境中使用index.js文件,在生产环境使用index.min.js文件,这是ASP.NET Core MVC项目中常用的方法。
自动化测试任务列表页面
我们可以创建继承测试,而且这已经被集成到 ASP.NET Core MVC 基础框架中。如果对自动化测试不感兴趣的小伙伴可以跳过这部分哦。
ABP框架中的 .Web.Tests项目是用来做测试的,我创建一个简单的测试去请求TaskController.Index,然后看其如何响应:
public class TasksController_Tests: SimpleTaskSystemWebTestBase
{
[Fact]
public async System.Threading.Tasks.Task Should_Get_Tasks_By_State()
{
//ACT
var response = await GetResponseAsStringAsync(
GetUrl<TasksController>(nameof(TasksController.Index), new
{
state = TaskState.Open
}
)
);
//assert
response.ShouldNotBeNullOrWhiteSpace();
}
}
GetResponseAsStringAsync和GetUrl方法是ABP框架中AbpAspNetCoreIntegratedTestBase类提供的辅助方法。我们可以直接使用Client (HttpClient的一个实例)属性来发出请求,但是使用这些辅助类会更容易一些。
调试测试,可以看到响应HTML:
这说明index页面响应无异常,但是我们可能还想知道返回的HTML是不是我们所想要的,有一些库可以用来解析HTML。AngleSharp就是其中之一,它预装在ABP启动模板中的.Web.Tests项目中。所以我用它来检查创建的HTML代码:
//Get tasks from database
var tasksInDatabase = await UsingDbContextAsync(async dbContext =>
{
return await dbContext.Tasks
.Where(t => t.State == TaskState.Open)
.ToListAsync();
}); //Parse HTML response to check if tasks in the database are returned
var document = new HtmlParser().Parse(response);
var listItems = document.QuerySelectorAll("#TaskList li"); //Check task count
listItems.Length.ShouldBe(tasksInDatabase.Count); //Check if returned list items are same those in the database
foreach (var listItem in listItems)
{
var header = listItem.QuerySelector(".list-group-item-heading");
var taskTitle = header.InnerHtml.Trim();
tasksInDatabase.Any(t => t.Title == taskTitle).ShouldBeTrue();
}
我们可以更深入和更详细地检查HTML,但是在大多数情况下,检查基本标签就足够了。
后面我会更新翻译第二部分。。。。。。
如何使用ASP.NET Core、EF Core、ABP(ASP.NET Boilerplate)创建分层的Web应用程序(第一部分)的更多相关文章
- asp.net core+ef core
asp.net core+ef core 官方的文档https://docs.asp.net/en/latest/tutorials/first-mvc-app/start-mvc.html 先来看一 ...
- Asp.net Core + EF Core + Bootstrap搭建的MVC后台通用管理系统模板(跨平台版本)
Asp.net Core + EF Core + Bootstrap搭建的MVC后台通用管理系统模板(跨平台版本) 原创 2016年07月22日 10:33:51 23125 6月随着.NET COR ...
- 国产化之路-统信UOS + Nginx + Asp.Net MVC + EF Core 3.1 + 达梦DM8实现简单增删改查操作
专题目录 国产化之路-统信UOS操作系统安装 国产化之路-国产操作系统安装.net core 3.1 sdk 国产化之路-安装WEB服务器 国产化之路-安装达梦DM8数据库 国产化之路-统信UOS + ...
- .net core EF Core 视图的应用
由之前的一篇文章<.net core Entity Framework 与 EF Core>我们都已经知道 EF Core 增加了许多特性,并且性能上也有了很大的提升. 但是EF Core ...
- .net core EF Core 调用存储过程
在这里,我们将尝试去学习一下 .net core EF Core 中调用存储过程. 我们知道,EF Core 是不支持直接调用存储过程的,那它又提供了什么样的方式去执行存储过程呢?有如下方法: 1.F ...
- 在vs2015上使用asp.net core+ef core
官方的文档https://docs.asp.net/en/latest/tutorials/first-mvc-app/start-mvc.html 先来看一下实现的效果
- .Net Core EF Core之Sqlite使用及部署
1.添加引用Nuget包 Microsoft.EntityFrameworkCore.Sqlite Microsoft.EntityFrameworkCore.Design Microsoft.Ent ...
- asp.net Core EF core ( Entity Framework 7 ) 数据库更新维护
CreateDatabaseIfNotExists等之前的API已经废弃,现在采用的是微软封装好,简化.高效的API,migrations 因为,旧API,要付出高昂的代价,以及局限性 打开VS20 ...
- asp.net core-16.EF Core Migration
左边的是基于visual studio code 右边的是基于visual studio 如果想要在数据库的AspNetUsers表里添加一列 然后可以发现 在Data下的Migrations文件夹下 ...
随机推荐
- IM开发者的零基础通信技术入门(二):通信交换技术的百年发展史(下)
1.系列文章引言 1.1 适合谁来阅读? 本系列文章尽量使用最浅显易懂的文字.图片来组织内容,力求通信技术零基础的人群也能看懂.但个人建议,至少稍微了解过网络通信方面的知识后再看,会更有收获.如果您大 ...
- Pytorch系列教程-使用Seq2Seq网络和注意力机制进行机器翻译
前言 本系列教程为pytorch官网文档翻译.本文对应官网地址:https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutor ...
- PHP全栈学习笔记10
php常量,常量是不能被改变的,由英文字母,下划线,和数字组成,但是数字不能作为首字母出现. bool define ( string $name , mixed $value [, bool $ca ...
- 学python走过的坑 三 不能实现的浏览器缩放功能
公司一个项目,在启动web页面时,默认应该是打开项目页面,然后浏览器启动时总是打开一个广告页面,经理让写一个脚本,让电脑每次开机自启浏览器,且加载项目页面.浏览器自启和打开项目页面轻松搞定,这时问题来 ...
- 区块链技术现状&前景
炒作周期 Gartner 在 2017 年发布的新兴技术炒作曲线,这张图是去年 8 月发布的,当时估计它们也没料到随后能有那么火,当时区块链在这个位置,其实是已经过了炒作的巅峰期,正在往低谷走的这个阶 ...
- 强化学习(十)Double DQN (DDQN)
在强化学习(九)Deep Q-Learning进阶之Nature DQN中,我们讨论了Nature DQN的算法流程,它通过使用两个相同的神经网络,以解决数据样本和网络训练之前的相关性.但是还是有其他 ...
- SQL优化 MySQL版 - 单表优化及细节详讲
单表优化及细节详讲 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 注:本文章需要MySQL数据库优化基础或观看前几篇文章,传送门: B树索引详讲(初识SQL优化,认识索引):htt ...
- Python使用Ctypes与C/C++ DLL文件通信过程介绍及实例分析
项目中可能会经常用到第三方库,主要是出于程序效率考虑和节约开发时间避免重复造轮子.无论第三方库开源与否,编程语言是否与当前项目一致,我们最终的目的是在当前编程环境中调用库中的方法并得到结果或者借助库中 ...
- js转换时间戳-转换成 yyyy-MM-dd HH:mm:ss
比如:转换成 yyyy-MM-dd HH:mm:ss //时间戳转换方法 date:时间戳数字 function formatDate(date) { var date = new Date(date ...
- PhotoshopCS5中将单位修改成百分比
PhotoshopCS5中单位默认是厘米或px,当用同一动作修改两张照片时,会因为片子大小不同,修改收到影响.若将单位修改成百分比,则动作会根据照片大小,自动进行调整. 1)选择菜单栏中的“编辑”选项 ...