Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之添加實體

  1. 嘗試新的開發組合:Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS
  2. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之配置IdentityServer
  3. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之數據遷移
  4. Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之添加實體

在ABP框架中,實體類是在Core項目中定義的。根據模版提供的Core項目,可以看到,實體類都是根據功能劃分到不同的文件夾的。在這裏,我們可以將SimpleCMS都放到CMS文件夾內,也可以單獨方在獨立的文件夾內。在本練習將使用獨立文件夾的方式。

要定義實體,可以從EntityEntity<T&>IEntity和IEntity<T>等類或接口中派生。這4個類或接口中,Entity派生於Entity<int>IEntityIEntity<T>,使用整型作爲實體的主鍵;Entity<T>是接口IEntity<T>的實現,也就是已經爲你實現了接口的功能,不再需要自己去實現接口功能。從這4個類或接口的定義來看,一般情況下,我們從Entity類或Entity<T>類派生實體類就行,如果有特殊需求,就從接口中派生。

在定義實體類時,還可以爲實體類添加以下常用接口用來實現一些常用功能:

  • IHasCreationTime:爲實體添加CreationTime屬性,用來記錄實體的創建時間
  • IHasDeletionTime:爲實體添加DeletionTime屬性,用來記錄實體的刪除時間,這個只有在使用軟刪除的時候纔有效。如果不是使用軟刪除,記錄都刪除了,這個字段沒有任何意義。
  • IHasModificationTime:爲實體添加LastModificationTime屬性,用來記錄實體的最後修改時間
  • ICreationAudited :在IHasCreationTime的基礎上添加CreatorUserId屬性,用來記錄創建實體的用戶的Id
  • IDeletionAudited:在IHasDeletionTime的基礎上添加DeleterUserId屬性,用來記錄刪除實體的用戶的Id
  • IModificationAudited:在IHasModificationTime的基礎上添加LastModifierUserId屬性,用來記錄最後修改實體的用戶的Id
  • IAuditedICreationAuditedIModificationAudited的合體,主要用於非軟刪除的情景
  • IFullAuditedIAuditedIDeletionAudited的合體,主要用於軟刪除的情景
  • ISoftDelete:爲實體添加IsDeleted屬性,用於判斷實體是否已經被刪除,主要用於軟刪除的情景
  • IPassivable:爲實體添加IsActive屬性,用於判斷實體是否處於活躍狀態
  • IMayHaveTenant:爲實體添加TenantId屬性,用於指定實體所屬的租戶。該屬性允許值爲null,也就是可以指定租戶,也可以不指定
  • IMustHaveTenant:該接口與IMayHaveTenant接口的主要區別是,必須指定租戶
  • IExtendableObject:爲實體添加ExtensionData屬性,用於存儲JSON格式的數據。在實體中可通過SetData方法來設置存儲的數據,通過GetData來獲取存儲的數據

瞭解了實體類的定義方式後,我們來編寫類別實體類,在Core項目下新建一個Categories文件夾,並添加一個名爲Category的類,具體定義如下:

   [Table("AppCategories")]
    public class Category :Entity<long>, IFullAudited, IMustHaveTenant
    {
        public const int MaxStringLength = 255;
        public const int MaxContentLength = 4000;

        public long? ParentId { get; set; }

        [ForeignKey("ParentId")]
        public virtual Category Parent { get; set; }

        [Required]
        [MaxLength(MaxStringLength)]
        public string Title { get; set; }

        [MaxLength(MaxStringLength)]
        public string Image { get; set; }

        [MaxLength(MaxContentLength)]
        public string Content { get; set; }

        [DefaultValue(0)]
        public int SortOrder { get; set; }

        public virtual ICollection<Category> SubCategories { get; set; }
        public virtual ICollection<Content> Contents { get; set; }

        public DateTime CreationTime { get; set; }
        public DateTime? LastModificationTime { get; set; }
        public DateTime? DeletionTime { get; set; }
        public long? CreatorUserId { get; set; }
        public long? LastModifierUserId { get; set; }
        public long? DeleterUserId { get; set; }
        public bool IsDeleted { get; set; }
        public int TenantId { get; set; }

        public Category()
        {
            CreationTime = Clock.Now;
            SortOrder = 0;
        }


    }

在代碼中,使用了Table特性將實體對應的表的名稱定義爲了AppCategories。在類中,還加入了IFullAuditedIMustHaveTenant接口,說明類別實體將採用完整的審計功能,使用軟刪除來實現刪除,而且必須爲它設置租戶。

在實體的構造函數中,將創建時間設置爲了當前時間。在這裏沒有使用DataTimeNow屬性是因爲用戶可能在不同的時區使用系統,爲了能很好的處理這個問題,ABP定義了自己的時間操作功能。如果不考慮時區問題,這裏可以換回DataTime對象。

估計很多人都會覺得奇怪,爲什麼在定義字符串的最大長度時,都要在實體類內定義一個常量呢?這是因爲在使用AutoMap來實現DTO類的時候,還需要定義一次最大長度,如果直接使用數字,那麼,當需要修改字符串長度的時候,就需要修改2次了,而使用常量的方式,只需要修改一次就行了。

由於在MySQL中觸發器與SQL Server的表現有點不同,因而沒有定義HierarchyLevel和FullPath這兩個字段。

由於Entity Framework Core不支持使用Index特性來聲明索引,只能使用Fluent API來創建索引。切換到EntityFrameworkCore項目,打開SimpleCmsWithAbpDbContext.cs文件,在類內先添加實體集,代碼如下:

public DbSet<Category> Categories { get; set; }

然後在OnModelCreating方法的最底部,添加以下代碼創建索引:

modelBuilder.Entity<Category>().HasIndex(p => p.SortOrder);

至此,類別實體就已經定義完了,相當的簡單。下面來定義文章實體,具體代碼如下:

    [Table("AppContents")]
    public class Content : Entity<long>, IFullAudited, IMustHaveTenant
    {
        public const int MaxStringLength = 255;
        public const int MaxSummaryLength = 500;

        [Required]
        [MaxLength(MaxStringLength)]
        public string Title { get; set; }

        [Required]
        public long CategoryId { get; set; }

        [ForeignKey("CategoryId")]
        public virtual Category Category { get; set; }

        [MaxLength(MaxStringLength)]
        public string Image { get; set; }

        [MaxLength(MaxSummaryLength)]
        public string Summary { get; set; }

        [Required]
        [Column(TypeName = "text")]
        public string Body { get; set; }

        [Required]
        [DefaultValue(0)]
        public int Hits { get; set; }

        [Required]
        [DefaultValue(0)]
        public int SortOrder { get; set; }

        public virtual ICollection<ContentTag> ContentTags { get; set; }


        public DateTime CreationTime { get; set; }
        public DateTime? LastModificationTime { get; set; }
        public DateTime? DeletionTime { get; set; }
        public long? CreatorUserId { get; set; }
        public long? LastModifierUserId { get; set; }
        public long? DeleterUserId { get; set; }
        public bool IsDeleted { get; set; }
        public int TenantId { get; set; }

        public Content()
        {
            CreationTime = Clock.Now;
            Hits = 0;
            SortOrder = 0;

        }
    }

在這裏需要注意的是數據庫的區別,由於MySQL的存儲超長字符的數據類型有text、mediumtext和longtext等,大家需要根據需要進行選擇。在這裏我覺得使用text就足夠了,它可以存儲65535個字符。如果認爲不足夠,可以修改爲longtext,當然,一勞永逸的方法就是無論什麼情況,都設置爲longtext。

文章實體創建後,別忘記在Context中添加實體集和索引。

由於Entity Framework Core不再支持自動創建多對多關係的關聯表,需要顯式定義關聯表,因而,我們需要在Contents文件夾下再創建一個名爲ContentTag的實體,作爲文章和標籤的關聯實體。對於ContentTag實體,很有意思,如果從Entity<T>派生,那就會爲它添加一個主鍵,而不能使用文章的Id和標籤的Id來創建主鍵。爲這個問題,我特意搜索了一下,找到了《Excluding the default Id primary key from an Entity….》這個帖子。在帖子中,ABP官方的答覆是使用文章的Id和標籤的Id創建一個唯一索引,而不去管那個主鍵,因爲這個主鍵是人畜無害的,而且在刪除的時候可以使用這個主鍵去刪除實體,也挺方便的。不過,官方的答覆人員對於這樣的結構也有點不爽,進一步的方法是使用NotMapped特性屏蔽Id字段,不寫到數據庫,但帶來的問題是,要使用Repository來處理實體的CURD操作,沒有Id主鍵會出現問題。除非重寫Repository類,不然解決不了這個問題,但在ABP官方文檔《Repositories 》的最佳實踐(Repository Best Practices)一節中,建議不要去自定義存儲,而且重寫存儲也確實是比較大的工程,因而,筆者的看法是,雖然這樣使用是醜陋了點,但有時候做開發只能這樣折衷一下。

定義好的ContentTag實體代碼如下:

    [Table("AppContentTags")]
    public class ContentTag:Entity<long>
    {
        [Required]
        public long ContentId { get; set; }
        [ForeignKey("ContentId")]
        public virtual Content Content { get; set; }

        [Required]
        public long TagId { get; set; }
        [ForeignKey("TagId")]
        public virtual Tag Tag { get; set; }

    }

定義好ContentTag實體 後,在OnModelCreating方法中爲實體添加索引,代碼如下:

modelBuilder.Entity<ContentTag>().HasIndex(p => new {p.ContentId, p.TagId}).IsUnique();

下面來完成標籤實體,代碼如下:

    [Table("AppTags")]
    public class Tag: Entity<long>, IMustHaveTenant
    {
        public const int MaxNameLength = 50;

        [Required]
        [MaxLength(MaxNameLength)]
        public string Name { get; set; }

        public int TenantId { get; set; }

        public virtual ICollection<ContentTag> ContentTags { get; set; }

    }

在標籤實體中,沒有使用審計功能。

還要爲標籤的Name字段添加唯一索引,代碼如下:

modelBuilder.Entity<Tag>().HasIndex(p => p.Name).IsUnique();

接下來是媒體實體,代碼如下:

    public class Media : Entity<long>, ICreationAudited, IDeletionAudited, IMustHaveTenant
    {
        public const int MaxFileNameLength = 32;
        public const int MaxDescriptionLength = 255;
        public const int MaxPathLength = 10;

        [Required]
        [MaxLength(MaxFileNameLength)]
        public string Filename { get; set; }

        [Required]
        [MaxLength(MaxDescriptionLength)]
        public string Description { get; set; }

        [Required]
        [MaxLength(MaxPathLength)]
        public string Path { get; set; }

        [Required]
        [Range(0, 2)]
        [DefaultValue(0)]
        public MediaType Type { get; set; }

        [Required]
        [DefaultValue(0)]
        public int Size { get; set; }


        public DateTime CreationTime { get; set; }
        public DateTime? DeletionTime { get; set; }
        public long? CreatorUserId { get; set; }
        public long? DeleterUserId { get; set; }
        public bool IsDeleted { get; set; }
        public int TenantId { get; set; }

        public Media()
        {
            CreationTime = Clock.Now;

        }
    }

由於媒體沒有更新功能,因而不需要更新審計,不採用IFullAudited接口,直接使用ICreationAudited和IDeletionAudited接口。

在定義媒體類型的時候,使用了枚舉類型的數據,定義如下:

    public enum MediaType: byte
    {
        Image = 0,
        Audio = 1,
        Video = 2

    }

最後是用戶配置實體,代碼如下:

    [Table("AppUserProfiles")]
    public class UserProfile :Entity<long>
    {
        public const int MaxKeywordLength = 200;
        public const int MaxValueLength = 1000;

        [DefaultValue(1)]
        public UserProfileType UserProfileType { get; set; }

        public long UserId { get; set; }

        [ForeignKey("UserId")]
        public virtual User User { get; set; }

        [Required]
        [MaxLength(MaxKeywordLength)]
        public string Keyword { get; set; }

        [Required]
        [MaxLength(MaxValueLength)]
        public string Value { get; set; }
    }

這裏使用了一個UserProfileType的枚舉,代碼如下:

    public enum UserProfileType : byte
    {
        State = 1
    }

在Context中添加全部實體集後,就可調用以下語句添加遷移文件了:

Add-Migration AddCmsTables -Context SimpleCmsWithAbpDbContext

生成遷移文件後,不要使用Update-Database來更新數據庫,使用Migrator項目來進行遷移,以便爲類別表加入未分類類別。

在Seed文件夾下新建一個名爲Cms的文件夾,然後參考TenantRoleAndUserBuilder.cs文件創建一個名爲DefaultCategoryBuilder的類,代碼如下:

    public class DefaultCategoryBuilder
    {
        private readonly SimpleCmsWithAbpDbContext _context;
        private readonly int _tenantId;


        public DefaultCategoryBuilder(SimpleCmsWithAbpDbContext context, int tenantId)
        {
            _context = context;
            _tenantId = tenantId;
        }

        public void Create()
        {
            CreateDefaultTenant();
        }

        private void CreateDefaultTenant()
        {
            // Default tenant

            if(_context.Categories.Any(m=>m.Title.Equals("未分類", StringComparison.CurrentCulture))) return;
            var category = new Category() {Title = "未分類", Content = "", SortOrder = 0,TenantId = _tenantId };
            _context.Categories.Add(category);
            _context.SaveChanges();
        }

    }

接下來在SeedHelper類中的SeedHostDb方法的底部添加以下代碼創建DefaultCategoryBuilder的實例來添加未分類類別:

new DefaultCategoryBuilder(context,1).Create();

好了,現在將Migrator項目設置爲啓動項目,執行一次,就可在數據庫中看到本文創建的實體了,打開appcategories表會看到一條記錄。

發佈了179 篇原創文章 · 獲贊 153 · 訪問量 176萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章