.NET Core第10天_搭配EF Core串联资料库Db First_使用EntityFramework执行检视的MVC控制器

首先新建好专案後
https://ithelp.ithome.com.tw/upload/images/20210910/201074525bkaXiaZnH.png

https://ithelp.ithome.com.tw/upload/images/20210910/20107452OPAj2i6ZBC.png

一样记得要先弄好MVC服务注入跟MVC预设路由形式设定
(可参考.NET Core第8天_路由端点的切换_注入MVC服务_利用middleware来启用静态资源设置预设网址路由)

新增好空的.net core专案後
到nuget中安装entityframework core相关套件

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
https://ithelp.ithome.com.tw/upload/images/20210910/201074527i1zZNJH39.png

https://ithelp.ithome.com.tw/upload/images/20210910/20107452pA9Cxm73fi.png

在PMC (Package Manager Console) 下Scaffold-DbContext指令
将建立既有资料库的 EF Core 模型。
「Tools」 - 「NuGet Package Manager」 - 「Package Manager Console」,输入以下指令:

Scaffold-DbContext "Server=.\SQLExpress;Database=Northwind;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables Employees,Orders

指令完整的格式定义

Scaffold-DbContext "Server=服务器位置;Database=资料库;Trusted_Connection=True;MultipleActiveResultSets=true;User ID=帐号;Password=密码" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Force

北风资料库若本机SSMS还没有可以自己新建
https://github.com/microsoft/sql-server-samples/tree/master/samples/databases/northwind-pubs

"Server=.\SQLExpress;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=true"
将连接到本机SqlExpress服务器里头的Northwind资料库

Trusted_Connection=True
信任目前登入帐号登入资料库。

MultipleActiveResultSets=true
允许在单一次DB连线中可执行多个批次作业。

-OutputDir Models
代表要将自动产生的档案放入到 /Models之下
否则预设会统一放在专案跟目录就容易零乱

此时若一执行会报错
Scaffold-DbContext : 无法辨识 'Scaffold-DbContext' 词汇是否为 Cmdlet、函数、指令档或可执行程序的名称。

https://ithelp.ithome.com.tw/upload/images/20210910/20107452pnmv0iMYwL.png

这里要再补安装一个nuget套件
Microsoft.EntityFrameworkCore.Tools
https://ithelp.ithome.com.tw/upload/images/20210910/2010745264QwgILoic.png

再次执行又遇到一个新错误

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SNI_PN11, error: 26 - Error Locating Server/Instance Specified)

https://ithelp.ithome.com.tw/upload/images/20210910/20107452LDendhvazN.png

这里由於本机装的是SQL Server 2019 打开SQLServerManager15.msc跟services.msc
做确认後原因在於现在实体指定的是指向到本机预设MSSQLSERVER而非SQLEXPRESS
因此只要打一个.即可
修正过後的指令

Scaffold-DbContext "Server=.;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=true" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables Employees,Orders

https://ithelp.ithome.com.tw/upload/images/20210910/20107452N7twNfHgWR.png

有关於SQLExpress跟MSSQLSERVER的差异可以参考这一篇来调整指令中连线字串

预设在安装SQL Server时候若没有去特别设定额外的实体名称
则默认实体名称会是., (local)或者machine name跟ip address,当然在服务中默认舍麽都没有改的情况下会显示MSSQLSERVER

有特别去指定命名SQLEXPRESS的实体则写法会是
.\SQLEXPRESS
localhost\SQLEXPRESS

当指令成功执行完後就会自动产生EF Core帮我们准备好的Model class跟Context class

https://ithelp.ithome.com.tw/upload/images/20210910/20107452Y0HKeFmTtd.png

DbContext当中可以看到
https://ithelp.ithome.com.tw/upload/images/20210910/2010745279RXaKNRa5.png

DbSet部分的变数名对应资料库中table名称(不可更动)
因此也会建议在table命名上建议用复数形式表示这样子才能跟Model Class加以区隔
NorthwindContext 完整程序

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

#nullable disable

namespace prjNet5_3_DbFirst.Models
{
    public partial class NorthwindContext : DbContext
    {
        public NorthwindContext()
        {
        }

        public NorthwindContext(DbContextOptions<NorthwindContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Employee> Employees { get; set; }
        public virtual DbSet<Order> Orders { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
                optionsBuilder.UseSqlServer("Server=.;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=true");
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.HasAnnotation("Relational:Collation", "Chinese_Taiwan_Stroke_CI_AS");

            modelBuilder.Entity<Employee>(entity =>
            {
                entity.HasIndex(e => e.LastName, "LastName");

                entity.HasIndex(e => e.PostalCode, "PostalCode");

                entity.Property(e => e.EmployeeId).HasColumnName("EmployeeID");

                entity.Property(e => e.Address).HasMaxLength(60);

                entity.Property(e => e.BirthDate).HasColumnType("datetime");

                entity.Property(e => e.City).HasMaxLength(15);

                entity.Property(e => e.Country).HasMaxLength(15);

                entity.Property(e => e.Extension).HasMaxLength(4);

                entity.Property(e => e.FirstName)
                    .IsRequired()
                    .HasMaxLength(10);

                entity.Property(e => e.HireDate).HasColumnType("datetime");

                entity.Property(e => e.HomePhone).HasMaxLength(24);

                entity.Property(e => e.LastName)
                    .IsRequired()
                    .HasMaxLength(20);

                entity.Property(e => e.Notes).HasColumnType("ntext");

                entity.Property(e => e.Photo).HasColumnType("image");

                entity.Property(e => e.PhotoPath).HasMaxLength(255);

                entity.Property(e => e.PostalCode).HasMaxLength(10);

                entity.Property(e => e.Region).HasMaxLength(15);

                entity.Property(e => e.Title).HasMaxLength(30);

                entity.Property(e => e.TitleOfCourtesy).HasMaxLength(25);

                entity.HasOne(d => d.ReportsToNavigation)
                    .WithMany(p => p.InverseReportsToNavigation)
                    .HasForeignKey(d => d.ReportsTo)
                    .HasConstraintName("FK_Employees_Employees");
            });

            modelBuilder.Entity<Order>(entity =>
            {
                entity.HasIndex(e => e.CustomerId, "CustomerID");

                entity.HasIndex(e => e.CustomerId, "CustomersOrders");

                entity.HasIndex(e => e.EmployeeId, "EmployeeID");

                entity.HasIndex(e => e.EmployeeId, "EmployeesOrders");

                entity.HasIndex(e => e.OrderDate, "OrderDate");

                entity.HasIndex(e => e.ShipPostalCode, "ShipPostalCode");

                entity.HasIndex(e => e.ShippedDate, "ShippedDate");

                entity.HasIndex(e => e.ShipVia, "ShippersOrders");

                entity.Property(e => e.OrderId).HasColumnName("OrderID");

                entity.Property(e => e.CustomerId)
                    .HasMaxLength(5)
                    .HasColumnName("CustomerID")
                    .IsFixedLength(true);

                entity.Property(e => e.EmployeeId).HasColumnName("EmployeeID");

                entity.Property(e => e.Freight)
                    .HasColumnType("money")
                    .HasDefaultValueSql("((0))");

                entity.Property(e => e.OrderDate).HasColumnType("datetime");

                entity.Property(e => e.RequiredDate).HasColumnType("datetime");

                entity.Property(e => e.ShipAddress).HasMaxLength(60);

                entity.Property(e => e.ShipCity).HasMaxLength(15);

                entity.Property(e => e.ShipCountry).HasMaxLength(15);

                entity.Property(e => e.ShipName).HasMaxLength(40);

                entity.Property(e => e.ShipPostalCode).HasMaxLength(10);

                entity.Property(e => e.ShipRegion).HasMaxLength(15);

                entity.Property(e => e.ShippedDate).HasColumnType("datetime");

                entity.HasOne(d => d.Employee)
                    .WithMany(p => p.Orders)
                    .HasForeignKey(d => d.EmployeeId)
                    .HasConstraintName("FK_Orders_Employees");
            });

            OnModelCreatingPartial(modelBuilder);
        }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    }
}

在继承自DbContext的NorthwindContext.cs程序中预设在
OnConfiguring方法中有一串标记为警告的代表含意是说不建议把连线资讯放在程序中
(可能不安全或者不易更换建议写於设定档)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.

那可以先摆着

在此我们可看到
Employee类别对应资料库表当中型态产生的Model
https://ithelp.ithome.com.tw/upload/images/20210910/201074523BuWZqYXLS.png

Employee.cs

using System;
using System.Collections.Generic;

#nullable disable

namespace prjNet5_3_DbFirst.Models
{
    public partial class Employee
    {
        public Employee()
        {
            InverseReportsToNavigation = new HashSet<Employee>();
            Orders = new HashSet<Order>();
        }

        public int EmployeeId { get; set; }
        public string LastName { get; set; }
        public string FirstName { get; set; }
        public string Title { get; set; }
        public string TitleOfCourtesy { get; set; }
        public DateTime? BirthDate { get; set; }
        public DateTime? HireDate { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Region { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string HomePhone { get; set; }
        public string Extension { get; set; }
        public byte[] Photo { get; set; }
        public string Notes { get; set; }
        public int? ReportsTo { get; set; }
        public string PhotoPath { get; set; }

        public virtual Employee ReportsToNavigation { get; set; }
        public virtual ICollection<Employee> InverseReportsToNavigation { get; set; }
        public virtual ICollection<Order> Orders { get; set; }
    }
}

使用精灵产生检视画面与控制器

https://ithelp.ithome.com.tw/upload/images/20210910/20107452hb2ytDh1Yh.png

https://ithelp.ithome.com.tw/upload/images/20210910/20107452rDhyFf6mPJ.png

模型类别:先选Employee
内容类别:就是选NorthwindContext
暂时勾消版面配置

https://ithelp.ithome.com.tw/upload/images/20210910/20107452msqiIV1hbU.png

预设就会把EmployeeController跟View对应操作画面检视产生出来
https://ithelp.ithome.com.tw/upload/images/20210910/20107452glOE5ce5JX.png

EmployeesController.cs的程序码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using prjNet5_3_DbFirst.Models;

namespace prjNet5_3_DbFirst.Controllers
{
    public class EmployeesController : Controller
    {
        private readonly NorthwindContext _context;

        public EmployeesController(NorthwindContext context)
        {
            _context = context;
        }

        // GET: Employees
        public async Task<IActionResult> Index()
        {
            var northwindContext = _context.Employees.Include(e => e.ReportsToNavigation);
            return View(await northwindContext.ToListAsync());
        }

        // GET: Employees/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var employee = await _context.Employees
                .Include(e => e.ReportsToNavigation)
                .FirstOrDefaultAsync(m => m.EmployeeId == id);
            if (employee == null)
            {
                return NotFound();
            }

            return View(employee);
        }

        // GET: Employees/Create
        public IActionResult Create()
        {
            ViewData["ReportsTo"] = new SelectList(_context.Employees, "EmployeeId", "FirstName");
            return View();
        }

        // POST: Employees/Create
        // To protect from overposting attacks, enable the specific properties you want to bind to.
        // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("EmployeeId,LastName,FirstName,Title,TitleOfCourtesy,BirthDate,HireDate,Address,City,Region,PostalCode,Country,HomePhone,Extension,Photo,Notes,ReportsTo,PhotoPath")] Employee employee)
        {
            if (ModelState.IsValid)
            {
                _context.Add(employee);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            ViewData["ReportsTo"] = new SelectList(_context.Employees, "EmployeeId", "FirstName", employee.ReportsTo);
            return View(employee);
        }

        // GET: Employees/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var employee = await _context.Employees.FindAsync(id);
            if (employee == null)
            {
                return NotFound();
            }
            ViewData["ReportsTo"] = new SelectList(_context.Employees, "EmployeeId", "FirstName", employee.ReportsTo);
            return View(employee);
        }

        // POST: Employees/Edit/5
        // To protect from overposting attacks, enable the specific properties you want to bind to.
        // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int id, [Bind("EmployeeId,LastName,FirstName,Title,TitleOfCourtesy,BirthDate,HireDate,Address,City,Region,PostalCode,Country,HomePhone,Extension,Photo,Notes,ReportsTo,PhotoPath")] Employee employee)
        {
            if (id != employee.EmployeeId)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    _context.Update(employee);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!EmployeeExists(employee.EmployeeId))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction(nameof(Index));
            }
            ViewData["ReportsTo"] = new SelectList(_context.Employees, "EmployeeId", "FirstName", employee.ReportsTo);
            return View(employee);
        }

        // GET: Employees/Delete/5
        public async Task<IActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var employee = await _context.Employees
                .Include(e => e.ReportsToNavigation)
                .FirstOrDefaultAsync(m => m.EmployeeId == id);
            if (employee == null)
            {
                return NotFound();
            }

            return View(employee);
        }

        // POST: Employees/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var employee = await _context.Employees.FindAsync(id);
            _context.Employees.Remove(employee);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

        private bool EmployeeExists(int id)
        {
            return _context.Employees.Any(e => e.EmployeeId == id);
        }
    }
}

这里预设直接用精灵产生的仍然会执行报错!

https://ithelp.ithome.com.tw/upload/images/20210910/20107452cxGSUy8vPy.png

主要原因就是因为warning错误标记那段我们直接忽略没调整
但事实上那段资料库连线并没有起到作用所以直接访问会报错

这里我们把NorthwindContext.cs当中的
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)这方法里面的连线字串程序码整段注解或删除掉

将连线字串("Server=.;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=true")

若你是属於有实体名称的反斜线记得要两条
("Server=.\SQLEXPRESS;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=true")

设定移置appsettings.json
预设appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

调整後的appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DbConnectonString": "Server=服务器位置;Database=资料库;Trusted_Connection=True;MultipleActiveResultSets=true;User ID=帐号;Password=密码"
  }
}

所以在程序中KEY值取得就是透过DbConnectonString
在Startup.cs中加入资料库物件的DI (服务注入一律写在ConfigureServices)
加入如下程序调整
https://ithelp.ithome.com.tw/upload/images/20210910/20107452wt0lcSRDF5.png

调整过後的Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using prjNet5_3_DbFirst.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace prjNet5_3_DbFirst
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<NorthwindContext>(options =>options.UseSqlServer(Configuration.GetConnectionString("DbConnectonString")));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{Controller=Home}/{Action=Index}/{id?}"
                );//设置MVC默认路由
            });
        }
    }
}

再次执行就正常了
https://ithelp.ithome.com.tw/upload/images/20210910/20107452XPtHjzF9ae.png

本文同步发表至个人部落格
https://coolmandiary.blogspot.com/2021/07/net-core10ef-coredb-first.html

Ref:
教学课程:开始使用 ASP.NET MVC web 应用程序中的 EF Core
https://docs.microsoft.com/zh-tw/aspnet/core/data/ef-mvc/intro?view=aspnetcore-5.0

还原工程 (Scaffolding)
https://docs.microsoft.com/zh-tw/ef/core/managing-schemas/scaffolding?tabs=dotnet-core-cli

ASP.NET Core - (DB-First) Scaffold-DbContext的错误讯息
https://dotblogs.com.tw/mis2000lab/2020/02/05/ASPnet_Core_Scaffold-DbContext_Error

对 SQL Server 资料库引擎的连线进行疑难排解
https://docs.microsoft.com/zh-tw/sql/database-engine/configure-windows/troubleshoot-connecting-to-the-sql-server-database-engine?view=sql-server-ver15

[MSSQL] 无法连线到 MS-SQL error-a-network-related-or-instance-specific-error-occurred-while-establishing
https://marcus116.blogspot.com/2017/12/error-a-network-related-or-instance-specific-error-occurred-while-establishing.html

What is the difference between SQLEXPRESS and MSSQLSERVER?
https://dba.stackexchange.com/questions/22246/what-is-the-difference-between-sqlexpress-and-mssqlserver/22248


<<:  Day10 Lab说明

>>:  [Day 5] Ktor 微框架就如同一间毛胚屋,先来列出想要整合的框架及实作的功能清单

JS 宽松相等、严格相等以及隐含转型 DAY54

严格相等 型别与内容 "皆" 需相等 // 内容一样 型别不一样 false c...

【C++】Pointer to Pointer

Pointer to Pointer 顾名思义就是指标的指标~ 它可能是一个变数的地址的地址~ 我们...

[30天 Vue学好学满 DAY30] 总结 & 完赛感言

最後一篇文,挤出了一些觉得在开发上容易踩的雷以及要注意的事情 要使用、渲染的变数除了传递进入元件的,...

Day15-Kubernetes 那些事 - Deployment 与 ReplicaSet(三)

前言 终於进入 Deployment 系列文的最後一篇也是非常重要的一篇了,在 K8s 系列文中的第...

day 12 - API组装实作

零件都准备好就可以组装起来了! 前几天分别完成了redis, error, log的封装, 接下来就...