.NET之生成資料庫全流程

AZRNG發表於2021-05-12

開篇語

本文主要是回顧下從專案建立到生成資料到資料庫(程式碼優先)的全部過程。採用EFCore作為ORM框架。

本次示例環境:vs2019、net5、mysql

建立專案

本次事例程式碼是用過vs2019建立的ASP.NET Core Web API專案

可以通過視覺化介面建立或者通過命令列建立

dotnet new webapi -o Net5ByDocker

建立實體類

安裝元件

    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.0" />
    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql.Json.Newtonsoft" Version="5.0.0" />

增加實體類

    [Table("user")]
    public class User
    {
        public User()
        {
            Id = Guid.NewGuid().ToString();
        }

        public User(string account, string password, string creater) : this()
        {
            Account = account;
            Password = password;
            Deleted = false;
            SetCreater(creater);
        }

        [Key]
        [Comment("主鍵")]
        [StringLength(36)]
        [Required]
        public string Id { get; private set; }

        [Comment("帳號")]
        [StringLength(36)]
        [Required]
        public string Account { get; private set; }

        [Comment("密碼")]
        [StringLength(36)]
        [Required]
        public string Password { get; private set; }

        [Comment("餘額")]
        [Column(TypeName = "decimal(18, 2)")]
        [Required]
        public decimal Money { get; set; }

        [Comment("是否刪除")]
        [Column(TypeName = "tinyint(1)")]
        [Required]
        public bool Deleted { get; private set; }

        [Comment("建立人")]
        [StringLength(20)]
        [Required]
        public string Creater { get; private set; }

        [Comment("建立時間")]
        [Required]
        public DateTime CreateTime { get; private set; }

        [Comment("修改人")]
        [StringLength(20)]
        [Required]
        public string Modifyer { get; private set; }

        [Comment("修改時間")]
        [Required]
        public DateTime ModifyTime { get; private set; }

        public void SetCreater(string name)
        {
            Creater = name;
            CreateTime = DateTime.Now;
            SetModifyer(name);
        }

        public void SetModifyer(string name)
        {
            Modifyer = name;
            ModifyTime = DateTime.Now;
        }
    }

這種只是增加實體類型別的一種方式,可能這種看著比較亂,還可以通過OnModelCreating實現,詳情看參考文件

增加資料庫上下文OpenDbContext

    public class OpenDbContext : DbContext
    {
        public OpenDbContext(DbContextOptions<OpenDbContext> options)
            : base(options)
        {
        }

        public DbSet<User> Users { get; set; }
    }

Startup注入連線資料庫操作

            var connection = Configuration["DbConfig:Mysql:ConnectionString"];
            var migrationsAssembly = IntrospectionExtensions.GetTypeInfo(typeof(Startup)).Assembly.GetName().Name;
            services.AddDbContext<OpenDbContext>(option => option.UseMySql(connection, ServerVersion.AutoDetect(connection), x =>
            {
                x.UseNewtonsoftJson();
                x.MigrationsAssembly(migrationsAssembly);
            }));

生成遷移檔案

引用元件

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.5">

遷移命令

add-migration Init

結果

image.png
image.png

要看下生成的遷移檔案是否是自己預期的那樣子,也可以在這一步就生成資料庫,命令:Update-Database

資料種子

增加OpenDbSend類,新增資料種子

    public class OpenDbSend
    {
        /// <summary>
        /// 生成資料庫以及資料種子
        /// </summary>
        /// <param name="dbContext">資料庫上下文</param>
        /// <param name="loggerFactory">日誌</param>
        /// <param name="retry">重試次數</param>
        /// <returns></returns>
        public static async Task SeedAsync(OpenDbContext dbContext,
            ILoggerFactory loggerFactory,
            int? retry = 0)
        {
            int retryForAvailability = retry.Value;
            try
            {
                dbContext.Database.Migrate();//如果當前資料庫不存在按照當前 model 建立,如果存在則將資料庫調整到和當前 model 匹配
                await InitializeAsync(dbContext).ConfigureAwait(false);

                //if (dbContext.Database.EnsureCreated())//如果當前資料庫不存在按照當前 model建立,如果存在則不管了。
                //  await InitializeAsync(dbContext).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                if (retryForAvailability < 3)
                {
                    retryForAvailability++;
                    var log = loggerFactory.CreateLogger<OpenDbSend>();
                    log.LogError(ex.Message);
                    await SeedAsync(dbContext, loggerFactory, retryForAvailability).ConfigureAwait(false);
                }
            }
        }

        /// <summary>
        /// 初始化資料
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static async Task InitializeAsync(OpenDbContext context)
        {
            if (!context.Set<User>().Any())
            {
                await context.Set<User>().AddAsync(new User("azrng""123456""azrng")).ConfigureAwait(false);
                await context.Set<User>().AddAsync(new User("張三""123456""azrng")).ConfigureAwait(false);
            }
            await context.SaveChangesAsync().ConfigureAwait(false);
        }
    }

設定專案啟動時候呼叫

        public static async Task Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                var loggerFactory = services.GetRequiredService<ILoggerFactory>();
                var _logger = loggerFactory.CreateLogger<Program>();
                try
                {
                    var openContext = services.GetRequiredService<OpenDbContext>();
                    await OpenDbSend.SeedAsync(openContext, loggerFactory).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"專案啟動出錯  {ex.Message}");
                }
            }

            await host.RunAsync().ConfigureAwait(false);
        }

生成資料庫

啟動專案,自動生成資料庫

image.png
image.png

表結構如下

image.png
image.png

如果後期資料庫欄位或者結構有變動,可以再次生成遷移檔案然後生成資料庫

查詢資料

    /// <summary>
    /// 使用者介面
    /// </summary>
    public interface IUserService
    {
        string GetName();

        /// <summary>
        /// 查詢使用者資訊
        /// </summary>
        /// <param name="account"></param>
        /// <returns></returns>
        Task<User> GetDetailsAsync(string account);
    }

    /// <summary>
    /// 使用者實現
    /// </summary>
    public class UserService : IUserService
    {
        private readonly OpenDbContext _dbContext;

        public UserService(OpenDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public string GetName()
        {
            return "AZRNG";
        }

        ///<inheritdoc cref="IUserService.GetDetailsAsync(string)"/>
        public async Task<User> GetDetailsAsync(string account)
        {
            return await _dbContext.Set<User>().FirstOrDefaultAsync(t => t.Account == account).ConfigureAwait(false);
        }
    }

一般更推薦建立指定的返回Model類,然後只查詢需要的內容,不直接返回實體類

控制器方法

        /// <summary>
        /// 查詢使用者詳情
        /// </summary>
        /// <param name="account"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<ActionResult<User>> GetDetailsAsync(string account)
        {
            return await _userService.GetDetailsAsync(account).ConfigureAwait(false);
        }

查詢結果

{
  "id""e8976d0a-6ee9-4e2e-b8d8-1fe6e85b727b",
  "account""azrng",
  "password""123456",
  "money": 0,
  "deleted"false,
  "creater""azrng",
  "createTime""2021-05-09T15:48:45.730302",
  "modifyer""azrng",
  "modifyTime""2021-05-09T15:48:45.730425"
}

參考文件

實體型別:https://docs.microsoft.com/zh-cn/ef/core/modeling/entity-types?tabs=data-annotations

實體屬性:https://docs.microsoft.com/zh-cn/ef/core/modeling/entity-properties?tabs=data-annotations%2Cwithout-nrt

微信公眾號

相關文章