【EF Core】自動生成的欄位值

来源:https://www.cnblogs.com/tcjiaan/p/18340772
-Advertisement-
Play Games

自動生成欄位值,咱們首先想到的是主鍵列(帶 IDENTITY 的主鍵)。EF Core 預設的主鍵配置也是啟用 Identity 自增長的,而且可以自動標識主鍵。前提是代表主鍵的實體屬性名要符合以下規則: 1、名字叫 ID、id、或 Id,就是不分大小寫; 2、名字由實體類名 + Id 構成。比如, ...


自動生成欄位值,咱們首先想到的是主鍵列(帶 IDENTITY 的主鍵)。EF Core 預設的主鍵配置也是啟用 Identity 自增長的,而且可以自動標識主鍵。前提是代表主鍵的實體屬性名要符合以下規則:

1、名字叫 ID、id、或 Id,就是不分大小寫;

2、名字由實體類名 + Id 構成。比如,Car 實體類,包含一個屬性叫 CarID 或 CarId;

3、屬性類型是整數類型(int、long、ushort 等,但不是 byte)或 GUID。

這些識別主鍵的規則是由一種叫“約定”(Convension)的東西實現的,具體來說,是一個叫 KeyDiscoveryConvention 的類。老周放一小段源代碼給各位瞧瞧。

public class KeyDiscoveryConvention :
    IEntityTypeAddedConvention,
    IPropertyAddedConvention,
    IKeyRemovedConvention,
    IEntityTypeBaseTypeChangedConvention,
    IEntityTypeMemberIgnoredConvention,
    IForeignKeyAddedConvention,
    IForeignKeyRemovedConvention,
    IForeignKeyPropertiesChangedConvention,
    IForeignKeyUniquenessChangedConvention,
    IForeignKeyOwnershipChangedConvention,
    ISkipNavigationForeignKeyChangedConvention
{
    private const string KeySuffix = "Id";

    ……

    public static IEnumerable<IConventionProperty> DiscoverKeyProperties(
        IConventionEntityType entityType,
        IEnumerable<IConventionProperty> candidateProperties)
    {
        Check.NotNull(entityType, nameof(entityType));

        // ReSharper disable PossibleMultipleEnumeration
        var keyProperties = candidateProperties.Where(p => string.Equals(p.Name, KeySuffix, StringComparison.OrdinalIgnoreCase));
        if (!keyProperties.Any())
        {
            var entityTypeName = entityType.ShortName();
            keyProperties = candidateProperties.Where(
                p => p.Name.Length == entityTypeName.Length + KeySuffix.Length
                    && p.Name.StartsWith(entityTypeName, StringComparison.OrdinalIgnoreCase)
                    && p.Name.EndsWith(KeySuffix, StringComparison.OrdinalIgnoreCase));
        }

        return keyProperties;
        // ReSharper restore PossibleMultipleEnumeration
    }
   ……
}

這幾個邏輯 And 其實就是查找 <類名>Id 格式的屬性名,如 StudentID、CarId、OrderID…… 外鍵的發現原理也跟主鍵一樣。

用 Sqlite 數據舉一個簡單的例子。下麵是實體類(假設它用來表示輸入法信息):

public class InputMethod
{
    public ushort RecoId { get; set; }
    public string? MethodDisplay { get; set; }
    public string? Description { get; set; }
    public string? Culture { get; set; }
}

如你所見,這個類作為主鍵的屬性是 RecoId,但是,它的命名是無法被自動識別的,咱們必須明確地告訴 EF,它是主鍵。方法有二:

1、批註法。直接在屬性上應用相關的特性類。如

public class InputMethod
{
    [Key]
    public ushort RecoId { get; set; }
    ……
}

2、重寫 DbContext 類的 OnModelCreating 方法。如

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<InputMethod>().HasKey(e => e.RecoId);
}

如果使用了上面重寫 OnModelCreating 方法,那麼,你的 DbContext 派生類已經能識別 InputMethod 實體類了。但如果你用的是在屬性上應用 [Key] 特性的方式,那麼 DbContext 的派生類是識別不到實體類的,你需要將它的集合聲明為 DbContext 的屬性。

internal class TestDBContext : DbContext
{
    // 構造函數
    public TestDBContext(DbContextOptions<TestDBContext> opt)
        : base(opt)
    { }

    // 將實體集合聲明為屬性
    public DbSet<InputMethod> InputMethods { get; set; }
}

註意,數據記錄的集合要用 DbSet<>,其他類型的集合是不行的喲。比如,你改成這樣,就會報錯。

public List<InputMethod> InputMethods { get; set; }

說明人家只認 DbSet 集合,其他集合無效。

這裡老周選用服務容器來配置。

static void Main(string[] args)
{
    IServiceCollection services = new ServiceCollection();
    // 構建連接字元串
    SqliteConnectionStringBuilder constrbd = new();
    constrbd.DataSource = "abc.db";
    // 添加 Sqlite 功能
    services.AddSqlite<TestDBContext>(
            connectionString:    constrbd.ToString(),
            optionsAction:       dcopt =>
            {
                dcopt.LogTo(msg => Console.WriteLine(msg), LogLevel.Information);
            }
        );
    // 生成服務列表
    var svcProd = services.BuildServiceProvider();
    if(svcProd == null)
    {
        return;
    }

    // 訪問數據上下文
    using TestDBContext dbc = svcProd.GetRequiredService<TestDBContext>();
    ……
}

連接字元串你可以直接用字元串寫,不用 ConnectionStringBuilder。預設的 SQLite 庫是不支持密碼的,所以老周就不設置密碼了。在調用 AddSqlite 方法時,有一個名為 optionsAction 的參數,咱們可以用它配置日誌輸出。LogTo 方法配置簡單,只要提供一個委托,它綁定的方法只要有一個 string 類型的輸入參數就行,這個字元串參數就是日誌文本。

配置日誌功能後,運行程式時,控制台能看到執行的 SQL 語句。

下麵咱們來創建資料庫,然後插入兩條 InputMethod 記錄。

// 訪問數據上下文
using TestDBContext dbc = svcProd.GetRequiredService<TestDBContext>();
// 刪除資料庫
dbc.Database.EnsureDeleted();
// 創建資料庫
dbc.Database.EnsureCreated();

// 嘗試插入兩條記錄
InputMethod[] ents = [
        new(){MethodDisplay = "雙拼輸入", Description="按兩個鍵完成一個音節",Culture="zh-CN"},
        new() {MethodDisplay = "六指輸入", Description="專供六個指頭的人使用",Culture="zh-CN"}
    ];
dbc.Set<InputMethod>().AddRange(ents);
int result = dbc.SaveChanges();
Console.WriteLine($"更新記錄數:{result}");

// 列印插入的記錄
foreach(InputMethod im in dbc.Set<InputMethod>())
{
    Console.WriteLine($"ID={im.RecoId}, Display={im.MethodDisplay}, Culture={im.Culture}");
}

這裡是為了測試,調用了 EnsureDeleted 方法,實際應用時一般不要調用。因為這個方法的功能是把現存的資料庫刪除。如果調用了此方法,那應用程式每次啟動都會刪掉資料庫,那用戶肯定會投訴你的。EnsureCreated 方法可以使用,它的功能是如果資料庫不存在,就創建新資料庫;如果資料庫存在,那啥也不做。所以,調用 EnsureCreated 方法不會造成數據丟失,放心用。

插入數據和調用 SaveChanges 方法保存到資料庫的代碼,相信大伙都很熟了,老周就不介紹了。

程式運行之後,將得到這樣的日誌:

info: 2024/8/4 12:48:11.517 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (10ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      PRAGMA journal_mode = 'wal';
info: 2024/8/4 12:48:11.582 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "tb_ims" (
          "RecoId" INTEGER NOT NULL CONSTRAINT "PK_tb_ims""MethodDisplay""Description""Culture" TEXT NULL
      );
info: 2024/8/4 12:48:11.700 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (3ms) [Parameters=[@p0='?' (Size = 5), @p1='?' (Size = 10), @p2='?' (Size = 4)], CommandType='Text', CommandTimeout='30']
      INSERT INTO "tb_ims" ("Culture", "Description", "MethodDisplay")
      VALUES (@p0, @p1, @p2)
      RETURNING "RecoId";
info: 2024/8/4 12:48:11.712 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[@p0='?' (Size = 5), @p1='?' (Size = 10), @p2='?' (Size = 4)], CommandType='Text', CommandTimeout='30']
      INSERT INTO "tb_ims" ("Culture", "Description", "MethodDisplay")
      VALUES (@p0, @p1, @p2)
      RETURNING "RecoId";
更新記錄數:2
info: 2024/8/4 12:48:11.849 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT "t"."RecoId", "t"."Culture", "t"."Description", "t"."MethodDisplay"
      FROM "tb_ims" AS "t"
ID=1, Display=雙拼輸入, Culture=zh-CN
ID=2, Display=六指輸入, Culture=zh-CN

這樣你會發現,對於整數類型的主鍵,預設是自動生成遞增ID的。註意,這個是由資料庫生成的,而不是 EF Core 的生成器。不同資料庫的 SQL 語句會有差異。

為了對比,咱們不防改為 SQL Server,看看輸出的日誌。

// 構建連接字元串
SqlConnectionStringBuilder constrbd = new();
constrbd.DataSource = ".\\SQLTEST";
constrbd.InitialCatalog = "CrazyDB";
constrbd.IntegratedSecurity = true;
// 不信任伺服器證書有時候會連不上
constrbd.TrustServerCertificate = true;
// 可讀可寫
constrbd.ApplicationIntent = ApplicationIntent.ReadWrite;

// 添加 SQL Server 功能
services.AddSqlServer<TestDBContext>(
        connectionString: constrbd.ToString(),
        optionsAction: opt =>
        {
            opt.LogTo(logmsg => Console.WriteLine(logmsg), LogLevel.Information);
        });

其他代碼不變,再次運行。輸出的日誌如下:

info: 2024/8/4 13:01:06.087 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (115ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      CREATE DATABASE [CrazyDB];
info: 2024/8/4 13:01:06.122 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (31ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [CrazyDB] SET READ_COMMITTED_SNAPSHOT ON;
      END;
info: 2024/8/4 13:01:06.137 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 2024/8/4 13:01:06.181 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (10ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [tb_ims] (
          [RecoId] int NOT NULL IDENTITY,
          [MethodDisplay] nvarchar(12) NOT NULL,
          [Description] nvarchar(max) NULL,
          [Culture] nvarchar(max) NULL,
          CONSTRAINT [PK_tb_ims] PRIMARY KEY ([RecoId])
      );
info: 2024/8/4 13:01:06.317 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (30ms) [Parameters=[@p0='?' (Size = 4000), @p1='?' (Size = 4000), @p2='?' (Size = 12), @p3='?' (Size = 4000), @p4='?' (Size = 4000), @p5='?' (Size = 12)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      MERGE [tb_ims] USING (
      VALUES (@p0, @p1, @p2, 0),
      (@p3, @p4, @p5, 1)) AS i ([Culture], [Description], [MethodDisplay], _Position) ON 1=0
      WHEN NOT MATCHED THEN
      INSERT ([Culture], [Description], [MethodDisplay])
      VALUES (i.[Culture], i.[Description], i.[MethodDisplay])
      OUTPUT INSERTED.[RecoId], i._Position;
更新記錄數:2
info: 2024/8/4 13:01:06.438 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [t].[RecoId], [t].[Culture], [t].[Description], [t].[MethodDisplay]
      FROM [tb_ims] AS [t]
ID=1, Display=雙拼輸入, Culture=zh-CN
ID=2, Display=六指輸入, Culture=zh-CN

A、使用 Sqlite 資料庫時,生成的 CREATE TABLE 語句,主鍵列是 PRIMARY KEY AUTOINCREMENT;

B、使用 SQL Server 時,主鍵列使用的是 IDENTITY,預設以 1 為種子,增量是 1。所以插入記錄的鍵值是1和2。

 

有時候我們並不希望主鍵列自動生成值,同樣有兩種配置方法:

1、通過特性類來批註。如

public class InputMethod
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public ushort RecoId { get; set; }
    public string? MethodDisplay { get; set; }
    public string? Description { get; set; }
    public string? Culture { get; set; }
}

將 DatabaseGeneratedOption 設置為 None,就取消列的自動生成了。

2、通過模型配置,即重寫 OnModelCreating 方法實現。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<InputMethod>().HasKey(e => e.RecoId);
    modelBuilder.Entity<InputMethod>()
        .Property(k => k.RecoId)
        .ValueGeneratedNever();
}

這種情況下,插入數據時主鍵列就需要咱們手動賦值了。

======================================================================================

上面的是熱身運動,是比較簡單的應用方案。下麵老周給大伙伴解決一個問題。老周看到在 GitHub 等平臺上有人提問,但沒有得到解決。如果你看到老周這篇水文並且你有此困惑,那你運氣不錯。好,F話不多說,咱們看問題。

需求:主鍵不變,但是我不想讓它帶有 IDENTITY,插入記錄時用我自定義的方式生成主鍵的值。這個需要的本質就是:我不要資料庫給我生成遞增ID,我要在程式里生成。

前面老周提過,預設行為下主鍵列如果是整數類型或 GUID,就會產生自增長的列。所以,咱們有一個很關鍵的步驟——就是怎麼禁止 EF 去產生 IDENTITY 列。如果你看到 EF Core SQL Server 的源代碼,可能你會知道有個約定類叫 SqlServerValueGenerationStrategyConvention。這個約定類預設會設置主鍵列的自動生成策略為 IdentityColumn。

 public virtual void ProcessModelInitialized(
     IConventionModelBuilder modelBuilder,
     IConventionContext<IConventionModelBuilder> context)
     => modelBuilder.HasValueGenerationStrategy(SqlServerValueGenerationStrategy.IdentityColumn);

於是,有大伙伴可能會想到,那我從 SqlServerValueGenerationStrategyConvention 派生出一個類,重寫 ProcessModelInitialized 方法,把自動生成策略改為 None,然後在約定集合中替換掉 SqlServerValueGenerationStrategyConvention。

這個思路不是不行,就是工作量大一些。你不僅要定義個新類,還要把它註冊到服務容器中替換 SqlServerValueGenerationStrategyConvention 。畢竟 EF Core 框架內部也是使用了服務容器和依賴註入的方式來組織各種組件的。具體做法是在初始化 DbContext 類(包括你派生的類)時會傳遞一個 DbContextOptions<TContext> 對象,它有一個 ReplaceService 方法,可以替換容器中的服務。在調用 AddSqlServer 方法時就可以配置。

 public static IServiceCollection AddSqlServer<TContext>(
     this IServiceCollection serviceCollection,
     string? connectionString,
     Action<SqlServerDbContextOptionsBuilder>? sqlServerOptionsAction = null,
     Action<DbContextOptionsBuilder>? optionsAction = null)
     where TContext : DbContext

上述方案太麻煩,故老周未採用。其實,就算服務初始化時設置了生成策略是 Identity,可我們可以在構建模型時修改它呀。做法就是重寫 DbContext 類的 OnModelCreating 方法,然後通過 IConventionModelBuilder.HasValueGenerationStrategy 方法就能修改生成策略。當然,這裡頭是有點波折的,我們不能在 ModelBuilder 實例上調用,因為這貨並不是直接實現 IConventionModelBuilder 介面的,它是這麼搞的:

public class ModelBuilder : IInfrastructure<IConventionModelBuilder>

IInfrastructure<T> 介面的作用是把 T 隱藏,不希望程式代碼訪問類型T。DbContext 類也實現這個介面,但它隱藏的是 IServiceProvider 對象,不想讓咱們訪問裡面註冊的服務。也就是說,IConventionModelBuilder 的實現者被隱藏了。不過,EF Core 並沒有把事情做得太絕,好歹給了一個擴展方法 GetInfrastructure。用這個擴展方法我們能得到 IConventionModelBuilder 類型的引用。

弄清楚這個原理,代碼就好寫了。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    IConventionModelBuilder cvbd = modelBuilder.GetInfrastructure();
    if (cvbd.CanSetValueGenerationStrategy(Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.None))
    {
        cvbd.HasValueGenerationStrategy(Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.None);
    }

    ……
}

把生成策略改為 None 後,生成主鍵列時就不會有 IDENTITY 了。

如果你樂意,可以在插入記錄時手動給主鍵列賦值也行的。不過,為了能自動生成值,我們應該寫一個自己的生成類。

public class MyValueGenerator : ValueGenerator<int>
{
    // 返回false表示這個生成的值不是臨時,它最終要存入資料庫的
    public override bool GeneratesTemporaryValues => false;

    private static readonly Random rand = new((int)DateTime.Now.Ticks);

    public override int Next(EntityEntry entry)
    {
        // 獲取所有實體
        DbSet<InputMethod> ents = entry.Context.Set<InputMethod>();
        int newID = default;
        do
        {
            // 生成隨機ID
            newID = rand.Next();
        }
        // 保證不重覆
        while (ents.Any(x => x.RecoId == newID));
        // 返回新值
        return newID;
    }
}

我這裡的邏輯是這樣的,值是隨機生成的,但要用一個迴圈去檢查這個值是不是已存在資料庫中,如果存在,繼續生成,直到數值不重覆。

實現自定義生成器,有兩個抽象類可供選擇:

1、如果你生成的值,類型不確定(可能是int,可能是 long,可能是……),那就實現 ValueGenerator 類;

2、如果要生成的值是明確類型的,比如這裡是 int,那就實現帶泛型參數的 ValueGenerator<TValue> 類。

這兩個類有繼承關係,ValueGenerator<TValue> 派生自 ValueGenerator 類。需要實現的抽象成員:

A、GeneratesTemporaryValues 屬性:只讀,返回 bool 值。如果你生成的值是臨時的,返回 true,不是臨時的,返回 false。啥意思呢。臨時的值表示暫時賦值給屬性/欄位,但 INSERT、UPDATE 時,這個值不會存入資料庫;如果不是臨時的值,最終會存進資料庫。上面例子中,老周讓它返回 false,就說明生成的這個值,要寫入資料庫的。

B、如果繼承 ValueGenerator 類,請實現 NextValue 抽象方法,返回類型是 object,就是生成的值;如果繼承的是 ValueGenerator<TValue>,請實現 Next 方法,此方法返回的類型由泛型參數決定。上面例子中是 int。

寫好生成類後,要把它應用到實體模型中,同樣是重寫 DbContext 類的 OnModelCreating 方法。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    IConventionModelBuilder cvbd = modelBuilder.GetInfrastructure();
    if (cvbd.CanSetValueGenerationStrategy(Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.None))
    {
        cvbd.HasValueGenerationStrategy(Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.None);
    }

    modelBuilder.Entity<InputMethod>().HasKey(e => e.RecoId);
    modelBuilder.Entity<InputMethod>()
        .Property(k => k.RecoId)
        .HasValueGenerator<MyValueGenerator>()
        .ValueGeneratedOnAdd();
    modelBuilder.Entity<InputMethod>().ToTable("tb_ims")
        .Property(x => x.MethodDisplay)
        .IsRequired()
        .HasMaxLength(12);
}

ValueGeneratedOnAdd 方法表示在記錄插入資料庫時自動生成值,HasValueGenerator 方法設置你自定義的生成器。

現在,有了自定義生成規則,在插入數據時,主鍵不能賦值。一旦賦值,生成器就無效了。

// 嘗試插入兩條記錄
InputMethod[] ents = [
        new(){ MethodDisplay = "雙拼輸入", Description="按兩個鍵完成一個音節",Culture="zh-CN"},
        new() { MethodDisplay = "六指輸入", Description="專供六個指頭的人使用",Culture="zh-CN"}
    ];
dbc.Set<InputMethod>().AddRange(ents);
int result = dbc.SaveChanges();

運行應用程式,你會發現,這次生成的 CREATE TABLE 語句中,RecoId 列已經沒有 IDENTITY 關鍵字了。

info: 2024/8/4 18:41:24.956 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 2024/8/4 18:41:24.982 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [CrazyDB] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
      END;
info: 2024/8/4 18:41:25.003 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (21ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      DROP DATABASE [CrazyDB];
info: 2024/8/4 18:41:25.104 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (82ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      CREATE DATABASE [CrazyDB];
info: 2024/8/4 18:41:25.137 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (32ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [CrazyDB] SET READ_COMMITTED_SNAPSHOT ON;
      END;
info: 2024/8/4 18:41:25.142 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: 2024/8/4 18:41:25.194 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (6ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [tb_ims] (
          [RecoId] int NOT NULL,
          [MethodDisplay] nvarchar(12) NOT NULL,
          [Description] nvarchar(max) NULL,
          [Culture] nvarchar(max) NULL,
          CONSTRAINT [PK_tb_ims] PRIMARY KEY ([RecoId])
      );
info: 2024/	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 節點結構體設計 struct LinkNode { // 數據域 void* data; // 指針域 struct LinkNode * next; }; data:一個 void* 類型的指針,指向節點存儲的數據。使用 void* 是為了鏈表能夠存儲不同類型的數據。 next:一個指向下一個 L ...
  • strcat 描述 char *strcat(char *dest, const char *src) 把 src 所指向的字元串追加到 dest 所指向的字元串的結尾。 聲明 下麵是 strcat() 函數的聲明。 char *strcat(char *dest, const char *src) ...
  • Java RMI(Remote Method Invocation)是一種允許Java虛擬機之間進行通信和交互的技術。它使得遠程Java對象能夠像本地對象一樣被訪問和操作,從而簡化了分散式應用程式的開發。一些應用依然會使用 RMI 來實現通信和交互,今天的內容我們來聊聊 RMI 的那些事兒。 一、先 ...
  • 作者:小塵 鏈接:https://juejin.cn/post/7357172505961578511 前言 見過幾千行代碼的 controller嗎?我見過。 見過全是 try catch 的 controller 嗎,我見過。 見過全是欄位校驗的 controller 嗎,我見過。 見過全是業務 ...
  • memset() 描述 C 庫函數 void *memset(void *str, int c, size_t n) 用於將一段記憶體區域設置為指定的值。 memset() 函數將指定的值 c 複製到 str 所指向的記憶體區域的前 n 個位元組中,這可以用於將記憶體塊清零或設置為特定值。 在一些情況下,需 ...
  • C 庫函數 - strcmp() 描述 C 庫函數 int strcmp(const char *str1, const char *str2) 把 str1 所指向的字元串和 str2 所指向的字元串進行比較。 聲明 下麵是 strcmp() 函數的聲明。 int strcmp(const cha ...
  • 前言 今天推薦一款用 .NET 和 Vue3 實現的開源許可權管理系統。它的界面清爽乾凈,功能強大,還具備靈活的角色許可權分配功能,能夠滿足不同規模企業的管理需求。無論你是開發新手還是大神,都能輕鬆上手,快速搭建起自己的許可權管理體系。別再猶豫了,趕快來試試吧! 項目簡介 Malus是海棠的意思,顧名思義 ...
  • 委托與事件是C#中歷史比較悠久的技術,從C#1.0開始就有了,核心作用就是將方法作為參數(變數)來傳遞和使用。其中委托是基礎,需要熟練掌握,編程中常用的Lambda表達式、Action、Func都是委托,包括事件也是基於委托實現的。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...