More

    Using of framework baseClass inheritance and functional interface (Ch.3)

    To implement more standardize features and data repository features, the framework defined 3 data “base class” and several “data interface” so that more data automation will be provided from data repository.

    Base Class

    To declare a data table in EF, you are required to inherit a base class so that you can use the IRepository

    (A) baseEntity

    “baseEntity” is used for the table which use integer identity field “Id” as primary key. The class will inherit the “Id” field from baseEntity class, therefore, developer is no needed to declare the “Id” field in their class structure.

    /// <summary>
        /// Represents a log record
        /// </summary>
        [Table("SystemLog")]
        public partial class SystemLog : BaseEntity
        {
            /// <summary>
            /// Gets or sets the log level identifier
            /// </summary>
            public int LogLevelId { get; set; }
    
            /// <summary>
            /// Gets or sets the short message
            /// </summary>
            [Column(TypeName = "nvarchar(100)")]
            public string ShortMessage { get; set; }
    
            /// <summary>
            /// Gets or sets the full exception
            /// </summary>
            [Column(TypeName = "nvarchar(max)")]
            public string FullMessage { get; set; }
    
            /// <summary>
            /// Gets or sets the IP address
            /// </summary>
            [Column(TypeName = "nvarchar(50)")]
            public string IpAddress { get; set; }
    
            /// <summary>
            /// Gets or sets the page URL
            /// </summary>
            [Column(TypeName = "nvarchar(500)")]
            public string PageUrl { get; set; }
    
            /// <summary>
            /// Gets or sets the referrer URL
            /// </summary>
            [Column(TypeName = "nvarchar(500)")]
            public string ReferrerUrl { get; set; }
    
            /// <summary>
            /// Gets or sets the referrer URL
            /// </summary>
            public bool IsRead { get; set; }
    
            // From Interface IUpdateTrackingEntity
            [Column(TypeName = "datetime")]
            public DateTime? CreateTimeUtc { get; set; }
            [Column(TypeName = "datetime")]
            public DateTime? UpdateTimeUtc { get; set; }
            [Column(TypeName = "datetime")]
            public DateTime? ImportTimeUtc { get; set; }
            [Column(TypeName = "varchar(50)")]
            public string UpdateMan { get; set; }
            [Column(TypeName = "varchar(50)")]
            public string UpdateZone { get; set; }
    
            /// <summary>
            /// Gets or sets the log level
            /// </summary>
            public LogLevel LogLevel
            {
                get => (LogLevel)LogLevelId;
                set => LogLevelId = (int)value;
            }
        }

    In this example, a “Id” identity key field will be inherit to data class and set key “Id” as primary key. The data class will map to “IRepository” class for data operation controls. For using of “IRepository” Class, it will discuss in later chapters.

    (B) baseGuidEntity

    “baseGuidEntity” is used for the data table using in “guid” field as primary key. In the following example, data table is a “guid” field in Guid data type is defined and inherit to data table.

        [Table("ClientMaster")]
        public partial class Client : BaseGuidEntity, IUpdateTrackingEntity, IStatusDeletedEntity, ILocalizedEntity
        {
            /// <summary> Organization / Entity Guid </summary>
            public Guid EntityGuid { get; set; }
            /// <summary> Client Code (for User Customizable) </summary>
            [Column(TypeName = "varchar(30)")]
            public string ClientCode { get; set; }
            /// <summary> Master Guid </summary>
            [Column(TypeName = "varchar(30)")]
            public Guid? MasterClientGuid { get; set; }
            /// <summary> For Nop Commerce Sychronization </summary>
            public int? NopClientId { get; set; }
            /// <summary> Sales Code </summary>
            [Column(TypeName = "varchar(30)")]
            public string SalesCode { get; set; }
            /// <summary> Short Name </summary>
            [Column(TypeName = "nvarchar(20)")]
            public string ShortName { get; set; }
            /// <summary> Client Type Code (Foriegn Key) </summary>
            [Column(TypeName = "varchar(2)")]
            public string ClientTypeCode { get; set; } = "I";
            ..............
        }

    For “baseGuidEntity” inherit class, we will map to use “IGuidRepository” for data operations. For more information of “IGuidRepostory”, we will discuss in later chapters

    (C) baseCustomKeyEntity

    “baseCustomKeyEntity” is used for the data table using in string code fields as primary key. The main different of this table definition developer is required to define the primary key in the class and the base class will not inherit the primary key field. The base class will only inherit a virtual field “CustomKeyCode” that is for a calculated field for the data class, which will use in Localization Service. Below is the class definition:

    public abstract partial class BaseCustomKeyEntity
    {
        /// <summary>
        /// Must declare Computed query defintion in OnModelCreating
        /// </summary>
        [NotMapped]
        [Column(TypeName = "varchar(200)")]
        public virtual string CustomKey { get; private set; }
    }

    For the data class, you should declare like this.

        [Table("CountyDef")]
        public class County : BaseCustomKeyEntity, IActiveFilterEntity, ISoftDeletedEntity ,ILocalizedEntity
        {
            [Column(TypeName = "varchar(2)")]
            public string CountryCode { get; set; }
            [Column(TypeName = "varchar(5)")]
            public string StateProvinceCode { get; set; }
            [Column(TypeName = "varchar(5)")]
            public string CountyCode { get; set; }
            public int? NopStateId { get; set; }
            [Column(TypeName = "varchar(100)")]
            public string CountyName { get; set; }        
            public int? DisplayOrder { get; set; }
    
            /// <summary> Implement for Localization Service for CustomKeyEntity, to define the CustomKey Value </summary>
            [NotMapped]
            public override string CustomKey { get { return CountryCode + "," + StateProvinceCode + "," + CountyCode; } }
    
            // From Interface IActiveFilterEntity
            public bool Active { get; set; }
            // From Interface ISoftDeletedEntity
            public bool Deleted { get; set; }
    
            public County()
            {
                Active = true;
                Deleted = false;
            }
        }
            [Column(TypeName = "varchar(2)")]
            public string CountryCode { get; set; }
            [Column(TypeName = "varchar(5)")]
            public string StateProvinceCode { get; set; }
            [Column(TypeName = "varchar(5)")]
            public string CountyCode { get; set; }
            public int? NopStateId { get; set; }

    Add developer should declare a custom key value key class as below. The CustomKey field is used on Localization Service to locate a unique key value for each record of data in database.

            /// <summary> Implement for Localization Service for CustomKeyEntity, to define the CustomKey Value </summary>
            [NotMapped]
            public override string CustomKey { get { return CountryCode + "," + StateProvinceCode + "," + CountyCode; } }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {           
                modelBuilder.HasDefaultSchema("dbo");
    
                .......
                
                modelBuilder.Entity<County>().HasKey(u => new
                {
                    u.CountryCode,
                    u.StateProvinceCode,
                    u.CountyCode
                });
    
                
    .......
            }

    Same rule as before, “baseCustomKeyEntity” inherit class, we will map to use “ICustomKeyRepository” for data operations. For more information of “IbaseCustomRepository”, we will discuss in later chapters.

    Functional Interface Class

    The framework has designed several interface classes for functional inheritance. Each services provides different data filtering or functional implementation in Repository Classes, for example, Filtering “Active” record, using Localization Service, etc. To declare a data table to inherit the interface, see below:

        [Table("CountyDef")]
        public class County : BaseCustomKeyEntity, IActiveFilterEntity, ISoftDeletedEntity ,ILocalizedEntity
        {
             ........
        }

    (A) IActiveFilterEntity

    This filter is used to implement a Boolean field “Active” to control the data display or hidden on frontend or backend. Normally, with Active = true, the record will be included and shows to users. And it will be filter out if Active = false. To attach this interface, developer is required to add the implementation as follows to the table class.

    /// <summary>Implementation of IActiveFilterEntity </summary>
    public bool Active { get; set; }
            

    When you use the member function of IRepository, you are always allow to add the “actionOnly:” options to confirm the filter of the records.

    public async Task RecoverPageAsync(string PageCode)
    {
        // Rewritted by Sammy Cheng, 2022-05-08
        var rec = await _sitePageRepository.GetByExpressionFirstOrDefaultAsync(o => o.PageCode == PageCode, includeDeleted: true, activeOnly: false);
        if (rec == null)
        {    
            throw new AppException(_context.errorList.Response(ErrorCode.RecordNotFound).ToString());
        }
                
        try
        {
            await _sitePageRepository.UnDeleteAsync(rec);
        }
        catch (Exception E)
        {
            throw new AppException(_context.errorList.Response(ErrorCode.DataUpdateError, E.Message).ToString());
        }
    }

    In the “IRepository”, “IGuidRepository”, “ICustomKeyRepository”, all function for data retrieving support “activeOnly:” parameters. A function of SetActiveAsync(bool) provided for standard routine for setting Active or Inactive for the records.

    (B) ISoftDeletedEntity

    This filter implement soft-deleted logic by setting a record “Deleted” to indicate the record is deleted or not, so developer can easy to “undelete” the record through repository classes. To interface with this interface, developer should include the current implementation:

    /// <summary> Implementation of Interface ISoftDeletedEntity </summary>
    public bool Deleted { get; set; } = false;

    In the Repository classes, all function for data retrieving support “includeDeleted:” parameters. They provides also “DeleteAsync()” and “UnDeleteAsync()” function to handle the soft delete operations. But if the class don’t inherit “ISoftDeletedEntity”, the record will be deleted directly with Delete(Async). “DeletePermentantly:” parameter is also provided in “DeleteAsync()” so that developer can confirm to delete the record instead of setting “Deleted” to true;

    if (command.EntityGuid != Guid.Empty && command.EntityGuid != null)
        query = query.Where(g => g.EntityGuid == command.EntityGuid);
        if (command.MasterClientGuid != Guid.Empty && command.EntityGuid != null)
            query = query.Where(g => g.MasterClientGuid == command.MasterClientGuid);   
        if (String.IsNullOrEmpty(command.SalesCode))
            query = query.Where(s => s.SalesCode == command.SalesCode);
        if (string.IsNullOrEmpty(command.ClientTypeCode))
            query = query.Where(t => t.ClientTypeCode == command.ClientTypeCode);
        if (string.IsNullOrEmpty(command.ClientName))
            query = query.Where(c => c.ClientName.Contains(command.ClientName));
        if (string.IsNullOrEmpty(command.CountryCode))
            query = query.Where(c => c.CountryCode == command.CountryCode);
        if (string.IsNullOrEmpty(command.StateProvinceCode))
            query = query.Where(s => s.StateProvinceCode == command.StateProvinceCode);
        if (string.IsNullOrEmpty(command.CountyCode))
            query = query.Where(c => c.CountyCode == command.CountyCode);
        if (string.IsNullOrEmpty(command.City))
            query = query.Where(c => c.City.Contains(command.City));
        if (string.IsNullOrEmpty(command.Address))
            query = query.Where(a => a.Add1.Contains(command.Address) || a.Add2.Contains(command.Address));
        if (string.IsNullOrEmpty(command.Email))
            query = query.Where(e => e.Email.Contains(command.Email));
        if (string.IsNullOrEmpty(command.Phone))
            query = query.Where(p => p.MobilePhone.Contains(command.Phone) || p.Tel1.Contains(command.Phone)
                    || p.Tel2.Contains(command.Phone) || p.Fax1.Contains(command.Phone) ||                  
                    p.Fax2.Contains(command.Phone));
        if (command.Status.HasValue)
            query = query.Where(s => s.Status == command.Status);
            switch (command.OrderBy)
            {
                  case 1:
                       query = query.OrderBy(o => o.ClientCode);
                       break;
                  case 2:
                       query = query.OrderBy(o => o.ClientName);
                       break;
                  default:                    
                       query = query.OrderBy(o => o.CreateTimeUtc);
                       break;
           }
    
    
           return query;
    }, pageIndex: command.PageIndex, pageSize: command.PageSize, activeOnly: (bool)command.ActiveOnly, includeDeleted: (bool)command.IncludeDeleted );
    

    (C) IStatusControlEntity

    This Filter is using a “Status” to control the active status or delete status for the data records. The most often like “User” Class, we always defined that the user in different status like this

    -1 - Deleted
    0  - New User
    1  - Activated User
    2  - Certified User

    For this filter, it looks like a merging of “IActiveFilterEntity” and “ISoftDeletedEntity” and with more extra status defined by developers. The “IRepository” classes will perform in the same way with “IActiveFilterEntity” and “ISoftDeletedEntity” to filter with “activeOnly:” and “includedDeleted”. To implement this filter, please add the following code to the data class:

    /// <summary> Implementation of Interface IStatusDeletedEntity </summary>
    public short Status { get; set; } = 1;

    (D) IUpdateTrackingEntity

    For this filter, the framework add some fields to record the man and time of creation, update, import. This is a compatible way to let system to handle with cross platform data synchronization or import/export operations. The “IRepository” classes will automatically update the fields in real time.

    /// <summary> Implementation of Interface IUpdateTrackingEntity </summary>
    [Column(TypeName = "datetime")]
    public DateTime? CreateTimeUtc { get; set; }
    [Column(TypeName = "datetime")]
    public DateTime? UpdateTimeUtc { get; set; }
    [Column(TypeName = "datetime")]
    public DateTime? ImportTimeUtc { get; set; }
    [Column(TypeName = "varchar(50)")]
    public string UpdateMan { get; set; }
    [Column(TypeName = "varchar(50)")]
    public string UpdateZone { get; set; }

    You are advised to add this filter to table that if the data table may have chance to export or synchronize data with other system. But there is not necessary to add this filter to all tables. For example, if you store a “ORDER” in 4 tables :

    • OrderMaster
    • OrderDetails
    • OrderPacking
    • OrderCostItems

    If the main table is “OrderMaster” and the others table is sub-table of “OrderMaster”, then you just at this filter to “OrderMaster” is enough. It is because all 4 table records with the same “OrderCode” (Key) is treated as single set of table when processing a transaction.

    (E) ILocalizedEntity

    For this Filter, it is used to indicate the data records contains multi-language fields and required to be handled by Localization Service. If you don’t Inherit this class, you cannot work with Localization Service to support multi-locale. There are not required to add any implementation fields and will discuss how to use Localization Service in later chapter.

    Overview : Developer Guide for BlueSky .NETCORE API Framework

    Previous : Definition of Domain (Entities) of Database Tables (Ch. 2 )

    Next : Updating DataContent, Creation of Initial Data and Generate Database Migration Script (Ch. 4)

    (c) 2022, BlueSky Information Technology (Int’l) Co. Ltd, All Rights Reserved.

    Recent Articles

    spot_img

    Related Stories

    Stay on op - Ge the daily news in your inbox