zoukankan      html  css  js  c++  java
  • MVC5使用EF6 Code First--创建EF数据模型(一)

    此Web应用程序演示如何使用Entity Framework 6和Visual Studio 2015创建ASP.NET MVC 5应用程序。本教程使用“Code First ”即代码先行。有关如何在“Code First”,“Database First”和“Model First”之间进行选择,请参阅实体框架开发工作流程。如下:

    • Database First

      如果已经拥有数据库,Visual Studio中内置的Entity Framework设计器可以自动生成一个数据模型,该模型由对应于现有数据库对象(如表和列)的类和属性组成。有关数据库结构,数据模型及映射之间的信息以XML格式存储在.edmx文件中。实体框架设计器提供了一个可视化界面,您可以使用它来显示和编辑.edmx文件。

    • Model First

      如果您还没有数据库,则可以使用Visual Studio中的Entity Framework设计器在.edmx文件中创建一个模型。当模型建完后,可以执行.edmx文件来创建数据库。

    • Code First

      无论您是否拥有数据库,都可以使用Code First。如果没有数据库,可以编写类和对应于表和列的属性。如果有数据库,那么Entity Framework可以生成与现有表和列对应的类和属性。如果使用Code First创建数据库,则可以使用“migration(迁移)”来将数据库部署到生产环境。当数据模型更改时,可以将更改部署到生产环境中,而不改变原有的数据。

    Contoso University Web应用程序

    此教程中构建的应用程序是一个简单的web网站。
    用户可以查看和更新学生课程和教师信息。以下是创建的几个页面。

    创建一个MVC Web应用程序

    打开Visual Studio 2015并创建一个名为“EFDemo”的新C# Web项目。

    在“ 新建ASP.NET项目”对话框中,选择MVC模板。

    更改身份验证,改为不进行身份验证(N)。

    单击“ 确定”创建项目。

    做几个简单的更改。打开视图 Shared _Layout.cshtml,并进行以下更改,如下图出显示:

     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     5     <meta charset="utf-8" />
     6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     7     <title>@ViewBag.Title - Contoso University </title>
     8     @Styles.Render("~/Content/css")
     9     @Scripts.Render("~/bundles/modernizr")
    10 </head>
    11 <body>
    12     <div class="navbar navbar-inverse navbar-fixed-top">
    13         <div class="container">
    14             <div class="navbar-header">
    15                 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
    16                     <span class="icon-bar"></span>
    17                     <span class="icon-bar"></span>
    18                     <span class="icon-bar"></span>
    19                 </button>
    20                 @Html.ActionLink("Contoso University ", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
    21             </div>
    22             <div class="navbar-collapse collapse">
    23                 <ul class="nav navbar-nav">
    24                     <li>@Html.ActionLink("主页", "Index", "Home")</li>
    25                     <li>@Html.ActionLink("关于", "About", "Home")</li>
    26                     <li>@Html.ActionLink("学生", "Index", "Student")</li>
    27                     <li>@Html.ActionLink("课程", "Index", "Course")</li>
    28                     <li>@Html.ActionLink("老师", "Index", "Instructor")</li>
    29                     <li>@Html.ActionLink("部门", "Index", "Department")</li>
    30                 </ul>
    31             </div>
    32         </div>
    33     </div>
    34     <div class="container body-content">
    35         @RenderBody()
    36         <hr />
    37         <footer>
    38             <p>&copy; @DateTime.Now.Year - Contoso University </p>
    39         </footer>
    40     </div>
    41 
    42     @Scripts.Render("~/bundles/jquery")
    43     @Scripts.Render("~/bundles/bootstrap")
    44     @RenderSection("scripts", required: false)
    45 </body>
    46 </html>

    Views Home Index.cshtml中,使用以下代码替换原有内容:

     1 @{
     2     ViewBag.Title = "Home Page";
     3 }
     4 
     5 <div class="jumbotron">
     6     <h1>Contoso University </h1>
     7 </div>
     8 <div class="row">
     9     <div class="col-md-4">
    10         <h2>Welcome to Contoso University </h2>
    11         <p>
    12             Contoso University is a sample application that
    13             demonstrates how to use Entity Framework 6 in an
    14             ASP.NET MVC 5 web application.
    15         </p>
    16     </div>
    17     <div class="col-md-4">
    18         <h2>Build it from scratch</h2>
    19         <p>You can build the application by following the steps in the tutorial series on the ASP.NET site.</p>
    20         <p><a class="btn btn-default" href="http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/">See the tutorial &raquo;</a></p>
    21     </div>
    22     <div class="col-md-4">
    23         <h2>Download it</h2>
    24         <p>You can download the completed project from the Microsoft Code Gallery.</p>
    25         <p><a class="btn btn-default" href="http://code.msdn.microsoft.com/ASPNET-MVC-Application-b01a9fe8">Download &raquo;</a></p>
    26     </div>
    27 </div>
    HomeIndex.cshtml

    CTRL + F5运行网站,显示如下:

    安装EF6

    工具菜单中单击NuGet包管理器,然后单击程序包管理器控制台

    在“ 管理器管理器”窗口中,输入以下命令:

    Install-Package EntityFramework

    第二种方法:项目上右击选择管理NuGet程序包,选择浏览输入EntityFramework搜索,列表就会列出结果,选择要安装的包,点击右侧的安装(我已经安装过了,所以显示的是"卸载")

    这里显示安装的是Entity Framework6.1.3,NuGet将安装最新版本的Entity Framework(不包括预发行版本)。

    创建数据模型

    接下来,您将为Contoso University 应用程序创建实体。将从以下三个实体开始:

    StudentEnrollment是一对多关系CourseEnrollment也是一对多的关系换句话说,学生可以注册任何数量的课程,课程可以被任何数量的学生注册(学生和课程是多对多关系)。

    接下来将为每个实体创建一个类。

    Student

    Models文件夹中,创建一个名为Student.cs的类文件,并使用以下代码替换模板代码:

     1 using System;
     2 using System.Collections.Generic;
     3 
     4 namespace EFDemo.Models
     5 {
     6     public class Student
     7     {
     8         public int ID { get; set; }
     9         public string LastName { get; set; }
    10         public string FirstMidName { get; set; }
    11         public DateTime EnrollmentDate { get; set; }
    12         
    13         public virtual ICollection<Enrollment> Enrollments { get; set; }
    14     }
    15 }
    Student.cs

    ID属性将成为数据库表的主键列。默认情况下,Entity Framework将一个名为IDclassnameID的属性作为主键。

    该 Enrollments属性是导航属性导航属性包含与该实体相关的其他实体信息。在这种情况下,Enrollments持有与该Student实体相关的Enrollment实体的所有信息。

    导航属性通常被定义为virtual使得它们可以利用某些实体框架功能,例如延迟加载

    如果导航属性包含多个实体(如多对多或一对多关系),则其类型必须是list集合,例如ICollection

    Enrollment

    Models文件夹中,创建Enrollment.cs并使用以下代码替换现有代码:

     1 namespace EFDemo.Models
     2 {
     3     public enum Grade
     4     {
     5         A, B, C, D, F
     6     }
     7 
     8     public class Enrollment
     9     {
    10         public int EnrollmentID { get; set; }
    11         public int CourseID { get; set; }
    12         public int StudentID { get; set; }
    13         public Grade? Grade { get; set; }
    14         
    15         public virtual Course Course { get; set; }
    16         public virtual Student Student { get; set; }
    17     }
    18 }
    Enrollment.cs

    该EnrollmentID属性是主键; 该实体主键使用类名 加ID的命名方式,而不是直接使用ID通常会选择一种固定的命名方式,并在所有数据模型中使用。在这里,可以使用任意命名方式。在后面的教程中,将看到如何使用ID,而不用classnameID,这样更容易在数据模型中实现继承。

    Grade属性是一个枚举类型Grade类型后的问号表示该Grade属性可以为空空值表示未知等级或尚未分配。

    StudentID属性是一个外键,以及相应的导航属性Student一个Enrollment实体与一个Student实体相关联,因此该属性只能保存一个Student实体

    CourseID属性也是一个外键,以及相应的导航属性Course一个Enrollment实体与一个Course实体相关联

    Course

    Models文件夹中,创建Course.cs,使用以下代码替换模板代码: 

     1 using System.Collections.Generic;
     2 using System.ComponentModel.DataAnnotations.Schema;
     3 
     4 namespace EFDemo.Models
     5 {
     6     public class Course
     7     {
     8         [DatabaseGenerated(DatabaseGeneratedOption.None)]
     9         public int CourseID { get; set; }
    10         public string Title { get; set; }
    11         public int Credits { get; set; }
    12         
    13         public virtual ICollection<Enrollment> Enrollments { get; set; }
    14     }
    15 }
    Course.cs

    Enrollments属性是导航属性。一个Course实体可以有任意数量的Enrollment实体。

    DatabaseGenerated属性,DatabaseGeneratedOption.None 设置允许输入课程主键,而不是让数据库自动生成它(不是自动增长)。

    创建数据库上下文

    右键单击该项目解决方案资源管理器,然后单击新建文件夹。命名新文件夹DAL(用于数据访问层)。在该文件夹中创建一个名为SchoolContext.cs的类,代码如下:

     1 using EFDemo.Models;
     2 using System.Data.Entity;
     3 using System.Data.Entity.ModelConfiguration.Conventions;
     4 
     5 namespace EFDemo.DAL
     6 {
     7     public class SchoolContext : DbContext
     8     {
     9         public SchoolContext() : base("SchoolDbContext")
    10         {
    11 
    12         }
    13 
    14         public DbSet<Student> Students { get; set; }
    15         public DbSet<Enrollment> Enrollments { get; set; }
    16         public DbSet<Course> Courses { get; set; }
    17 
    18         protected override void OnModelCreating(DbModelBuilder modelBuilder)
    19         {
    20             //阻止表名复数形式
    21             modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    22         }
    23     }
    24 }
    SchoolContext.cs

    指定连接字符串

    连接字符串的名称传递给构造函数

    1 public SchoolContext() : base("SchoolDbContext")
    2 {
    3 }

    Web.config文件中连接字符串如以下所示:

    1  <connectionStrings>
    2        <add name="SchoolDbContext" providerName="System.Data.SqlClient" connectionString="Data Source=ServerName;Initial Catalog=DatabaseName;Integrated Security=False;User Id=userid;Password=password;MultipleActiveResultSets=True" />
    3    </connectionStrings>

    也可以传递连接字符串本身,而不是存储在Web.config文件中。

    如果不指定连接字符串或显式指定名称,则Entity Framework假定连接字符串名称与类名称相同。

    指定实体集DbSet<>

    每个实体集创建一个DbSet属性。在EF中,实体集对应于数据库的表,实体对应于表中的一行。

    注意:可以省略声明DbSet<Enrollment>DbSet<Course>,它们将正常工作。EF将默认隐式地包含它们。

    指定单数表名

    OnModelCreating 方法中modelBuilder.Conventions.Remove语句阻止表名的复数形式。如果不这样做,在数据库中生成的表将被命名为复数形式(Students)。

    设置使用测试数据来初始化数据库

    EF可以在应用程序运行时(或者当模型与现有数据库不同步时)自动创建(或删除或重新创建)数据库。还可以编写一个 Seed 方法,EF将在创建数据库后自动调用,以便添加测试数据。

    EF默认的是在数据库不存在(CreateDatabaseIfNotExists)时创建数据库(如果模型已更改,数据库已经存在),则抛出异常。在本章中,将指定每当模型改变时,才删除和重新创建数据库。删除和重新创建数据库会丢失数据库中原先的所有数据。这通常只在开发过程中使用,因为Seed方法将在数据库重新创建时才运行,并重新添加测试数据。但在生产中环境,不可能每次更改数据库时丢失所有数据。在下面的章节,将学习如何通过使用Code First  migration(迁移)来更改数据库,而不影响原来的数据。


     CreateDatabaseIfNotExists

    这是默认的数据库初始化类,除非手动指定其他类。顾名思义,CreateDatabaseIfNotExists类仅在不存在数据库时创建数据库。

    DropCreateDatabaseIfModelChanges

    只要当模型类和表之间不匹配时,就会删除数据库并重新创建它。

    DropCreateDatabaseAlways

    每次运行应用程序时,都将删除并重新创建数据库,而不管它是否已经存在。


     在DAL文件夹中,创建一个名为SchoolInitializer.cs的类,代码如下:

     1 using EFDemo.Models;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.Data.Entity;
     5 
     6 namespace EFDemo.DAL
     7 {
     8     public class SchoolInitializer : DropCreateDatabaseIfModelChanges<SchoolContext>
     9     {
    10         protected override void Seed(SchoolContext context)
    11         {
    12             var students = new List<Student>
    13             {
    14             new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
    15             new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
    16             new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
    17             new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
    18             new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
    19             new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
    20             new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
    21             new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
    22             };
    23             students.ForEach(s => context.Students.Add(s));
    24             context.SaveChanges();
    25 
    26             var courses = new List<Course>
    27             {
    28             new Course{CourseID=1050,Title="Chemistry",Credits=3,},
    29             new Course{CourseID=4022,Title="Microeconomics",Credits=3,},
    30             new Course{CourseID=4041,Title="Macroeconomics",Credits=3,},
    31             new Course{CourseID=1045,Title="Calculus",Credits=4,},
    32             new Course{CourseID=3141,Title="Trigonometry",Credits=4,},
    33             new Course{CourseID=2021,Title="Composition",Credits=3,},
    34             new Course{CourseID=2042,Title="Literature",Credits=4,}
    35             };
    36             courses.ForEach(s => context.Courses.Add(s));
    37             context.SaveChanges();
    38 
    39             var enrollments = new List<Enrollment>
    40             {
    41             new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
    42             new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
    43             new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
    44             new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
    45             new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
    46             new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
    47             new Enrollment{StudentID=3,CourseID=1050},
    48             new Enrollment{StudentID=4,CourseID=1050,},
    49             new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
    50             new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
    51             new Enrollment{StudentID=6,CourseID=1045},
    52             new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
    53             };
    54             enrollments.ForEach(s => context.Enrollments.Add(s));
    55             context.SaveChanges();
    56         }
    57     }
    58 }
    SchoolInitializer.cs

    该Seed方法用于添加测试数据。

    指定数据库初始化程序

    打开Web.config文件,添加代码如下: 

     1 <entityFramework>
     2 
     3     <contexts>
     4       <context type="EFDemo.DAL.SchoolContext, EFDemo">
     5         <databaseInitializer type="EFDemo.DAL.SchoolInitializer, EFDemo" />
     6       </context>
     7     </contexts>
     8 
     9     <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
    10       <parameters>
    11         <parameter value="mssqllocaldb" />
    12       </parameters>
    13     </defaultConnectionFactory>
    14     <providers>
    15       <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    16     </providers>
    17   </entityFramework>

    或者

    1   <appSettings>
    2     <add key = "DatabaseInitializerForType EFDemo.DAL.SchoolContext,EFDemo" value = "EFDemo.DAL.SchoolInitializer,EFDemo" />
    3   </appSettings>

    它必须以预定义值DatabaseInitializerForType开头,后跟一个空格,然后是上下文类的全名称。

    context节点,type指定上下文类的全名称,后面是程序集的名称。当你不想EF使用初始化,您可以给context元素设置一个属性disableDatabaseInitialization="true"

    作为在Web.config文件中设置初始化程序的替代方法是在Global.asax.cs文件中通过执行代码来设置:

     1  protected void Application_Start()
     2  {
     3    AreaRegistration.RegisterAllAreas();
     4    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
     5    RouteConfig.RegisterRoutes(RouteTable.Routes);
     6    BundleConfig.RegisterBundles(BundleTable.Bundles);
     7 
     8    //指定数据库初始化程序
     9    Database.SetInitializer(new SchoolInitializer());
    10  }

    SetInitializer() 方法接受数据库初始化程序类的实例,并将其设置为当前应用程序数据库初始化程序。设置数据库初始化程序时,不会立即调用。当第一次使用上下文(SchoolContext)时,它将被调用。

    当首次访问数据库时,Entity Framework会将数据库与模型进行比较,如果有差异,将删除并重新创建数据库。

    如果在数据库初始化时,不想使用延迟加载,可以使用Initialize()方法覆盖。如果模型复杂且初始化需要很多时间,可以显式调用数据库初始化程序。这样就可以在某个已知步骤执行初始化(并且可以向用户显示等待消息)。使用Initialize()方法,代码如下所示:

    1  Database.Initialize(false);//立刻初始化数据,不延迟加载

    在上述代码中,数据库将在调用Initialize()方法后立即创建,而不是延迟加载。Initialize()方法接受一个布尔类型的参数,用于控制是否重新执行初始化。指定false,如果已经执行,则跳过初始化过程。指定true,将重新初始化,即使它已被初始化。

    有时可能不希望执行数据库初始化逻辑。在这种情况下,可以通过将null传递给SetInitializer()方法,则将跳过初始化。

    1 Database.SetInitializer<SchoolContext>(null);

    注意:如果将应用程序部署到生产环境,则必须删除或禁用初始化代码。

    创建自定义数据库初始化程序

    在上述示例中,使用内置的数据库初始化程序。还可以通过实现IDatabaseInitializer<>接口来创建自定义数据库初始化程序。需要实现IDatabaseInitializer<>接口的InitializeDatabase()方法,并编写自己的数据库创建逻辑。以下代码显示了InitializeDatabase()方法的示例实现:

     1 public class SchoolInitializer : IDatabaseInitializer<SchoolContext>
     2     {
     3         #region 继承 IDatabaseInitializer<SchoolContext>接口,实现InitializeDatabase()方法
     4         public void InitializeDatabase(SchoolContext context)
     5         {
     6             //判断数据库是否已经存在
     7             if (context.Database.Exists())
     8             {
     9                 //数据库模式是否与模型兼容
    10                 if (!context.Database.CompatibleWithModel(true))
    11                 {
    12                     context.Database.Delete();
    13                 }
    14             }
    15             context.Database.Create();
    16             //使用自定义初始值设置来执行自定义任务
    17             context.Database.ExecuteSqlCommand("CREATE TABLE CS_DATA([Id] int identity(1,1) primary key, [Name] NVARCHAR(50) not null)");
    18         }
    19         #endregion
    20     }

    注意:代码使用ExecuteSqlCommand()方法来创建不属于模型的表。虽然我们在示例中不使用该表,但它说明了如何使用自定义初始化设置来执行自定义任务。

    创建Student控制器和视图

    现在,将创建一个页面来显示数据,请求数据的过程将自动触发数据库的创建。先创建一个新的控制器。但是在这之前,需要重新生成整个项目,因为我们将使用MVC脚手架自动生成CRUD页面。

    1. 右键单击解决方案资源管理器中Controllers文件夹,选择添加,然后单击控制器
    2. 在弹出的对话框中,选择包含视图的MVC 5控制器(使用Entity Framework)

    3. 在“添加控制器”对话框中,进行以下选择,然后单击“ 添加”

      • 模特类:学生(Student(EFDemo.Models))(如果在下拉列表中没有看到此选项,则重新生成项目,然后重试)
      • 数据上下文类:SchoolContext(EFDemo.DAL)
      • 控制器名称:StudentController
      • 其他保留默认值。

     

    当单击添加时,将创建一个StudentController.cs文件和一组与控制器配合使用的视图(.cshtml文件)。

    4.Visual Studio打开Controllers StudentController.cs文件。您看到已经创建了一个实例化数据库上下文对象的类变量:

    1  private SchoolContext db = new SchoolContext();

    Index操作方法通过数据库上下文实例的属性查询出所有学生信息:

    1   // GET: Student
    2   public ActionResult Index()
    3   {
    4     return View(db.Students.ToList());
    5   }

    学生 Index.cshtml视图显示此列表中的表:

     1 @model IEnumerable<EFDemo.Models.Student>
     2 
     3 @{
     4     ViewBag.Title = "Index";
     5 }
     6 
     7 <h2>Index</h2>
     8 
     9 <p>
    10     @Html.ActionLink("Create New", "Create")
    11 </p>
    12 <table class="table">
    13     <tr>
    14         <th>
    15             @Html.DisplayNameFor(model => model.LastName)
    16         </th>
    17         <th>
    18             @Html.DisplayNameFor(model => model.FirstMidName)
    19         </th>
    20         <th>
    21             @Html.DisplayNameFor(model => model.EnrollmentDate)
    22         </th>
    23         <th></th>
    24     </tr>
    25 
    26 @foreach (var item in Model) {
    27     <tr>
    28         <td>
    29             @Html.DisplayFor(modelItem => item.LastName)
    30         </td>
    31         <td>
    32             @Html.DisplayFor(modelItem => item.FirstMidName)
    33         </td>
    34         <td>
    35             @Html.DisplayFor(modelItem => item.EnrollmentDate)
    36         </td>
    37         <td>
    38             @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
    39             @Html.ActionLink("Details", "Details", new { id=item.ID }) |
    40             @Html.ActionLink("Delete", "Delete", new { id=item.ID })
    41         </td>
    42     </tr>
    43 }
    44 
    45 </table>
    View Code

    5.按CTRL + F5运行项目。(或Index.cshtml文件右击在浏览器中查看或者直接按F5)

    单击“ 学生”选项卡以查看Seed()方法插入的测试数据

    查看数据库

    当您运行学生页面,应用程序尝试访问数据库时,EF看到没有数据库,因此它创建了一个数据库,然后运行Seed()方法添加测试数据。

    因为正在使用DropCreateDatabaseIfModelChanges初始化程序,现在可以对Student进行更改,再次运行应用程序,将自动重新创建数据库。例如,如果Student该类添加一个属性EmailAddress,再次运行“学生”页面,然后再次查看该表,将看到一个新列EmailAddress

    1   public string EmailAddress { get; set; }

    当重新运行时,可能会报这个错:

    解决办法:先删除原数据库,勾选关闭现有连接,然后再运行程序。后面利用迁移就不需要手动删除数据库了。

    然后查看数据库,增加了新的列

    约定

    使用Entity Framework能够创建一个完整的数据库,发现我们只编写了很少的代码,因为Entity Framework有默认约定

    • 实体类名一般用作表名。
    • 实体属性名称用于列名。
    • 名称ID或classnameID属性被识别为主键属性。
    • 还有外键属性,例如,StudentID用于Student导航属性

    当然我们可以将属性显式地标记为外键属性等。在本系列后面继续学习。

    参考:https://docs.microsoft.com/zh-cn/aspnet/

  • 相关阅读:
    xprintf
    Socket调试助手
    ESP8266开发
    WPS for Linux 界面改为中文
    uBlock Origin 广告屏蔽
    Ubuntu删除多余旧内核
    FFmpeg命令
    Mysql-connector-java驱动版本问题
    crontab任务重复执行?不执行?不按照配置执行?大概率是配置出错了!!!
    Binding(四):数据校验
  • 原文地址:https://www.cnblogs.com/vanblog/p/7543793.html
Copyright © 2011-2022 走看看