Xây dựng mô hình dữ liệu cho ứng dụng XAF

    0

    Việc phát triển một ứng dụng XAF luôn bắt đầu bằng việc xây dựng các lớp thực thể. Trong ứng dụng XAF, các lớp thực thể được gọi chung là mô hình dữ liệu (data model). Trong bài học này chúng ta sẽ triển khai các lớp thực thể cho ứng dụng cơ bản đã tạo từ bài học trước. Qua đây, chúng ta sẽ giải thích các khái niệm cơ bản về việc xây dựng giao diện người dùng tự động dựa trên mô hình dữ liệu.

    Phần mềm quản lý dự án đơn giản

    Trong dự án minh họa này chúng ta sẽ xây dựng một ứng dụng hỗ trợ quản lý dự án và chăm sóc khách hàng đơn giản. Phần mềm này bao gồm hai nhóm chức năng: quản lý tiếp thị và quản lý kế hoạch.

    Kết thúc bài học này chúng ta sẽ thu được hai phần mềm, một phần mềm winforms và một phần mềm Blazor server.

    Phần mềm winforms kết nối với cơ sở dữ liệu hoạt động trên mạng LAN. Đây là loại phần mềm desktop quen thuộc và thông dụng trước đây. Tuy nhiên, hiện nay mô hình phần mềm này ít được triển khai rộng rãi. Tuy nhiên, nó vẫn là mô hình phần mềm quan trọng phục vụ trong nội bộ doanh nghiệp.

    XAF cũng có thể tạo ra ứng dụng winforms kết nối với web API. Đến giai đoạn sau chúng ta sẽ làm quen với mô hình ứng dụng này.

    Đây là phần mềm web đơn trang sử dụng công nghệ Blazor server mới nhất của Microsoft. Đây là loại ứng dụng đang trở nên phổ biến thay thế cho ứng dụng desktop. Ứng dụng web đơn trang có hiệu suất cao, dễ triển khai trên quy mô lớn, dễ bảo trì và cập nhật.

    Phân tích bài toán

    Mô hình dữ liệu của ứng dụng bao gồm hai nhóm:

    1. Nhóm chức năng quản lý tiếp thị: các lớp Customer (khách hàng) và Testimonial (phản hồi của khách hàng).
    2. Nhóm chức năng quản lý kế hoạch: các lớp Project (dự án), ProjectTask (các nhiệm vụ của dự án) và Employee (nhân viên).

    Cụ thể như sau:

    • Lớp Customer chứa các thuộc tính mô tả khách hàng, bao gồm họ, tên, email, công ty, chức vụ, ảnh avatar, danh sách phản hồi
    • Lớp Testimonial chứa các thuộc tính mô tả phản hồi của khách hàng, bao gồm: tóm tắt, mô tả chi tiết, thời gian nhận, tag phân loại, người phản hồi.
    • Lớp Project chứa các thuộc tính mô tả một dự án, bao gồm: tên dự án, người quản lý, mô tả chi tiết, danh sách công việc.
    • Lớp ProjectTask chứa các thuộc tính mô tả một công việc trong dự án, bao gồm tiêu đề, trạng thái, người nhận việc, hạn thực hiện, ngày bắt đầu, ngày kết thúc, các ghi chú.
    • Lớp Employee chứa các thuộc tính mô tả một nhân viên, bao gồm họ và tên.

    Các class mô tả dữ liệu cần quản lý như trên được gọi là các lớp thực thể (entity class). Đặc thù của các lớp thực thể là chúng chỉ chứa các thuộc tính thể hiện thông tin của đối tượng cần quản lý. Các lớp thực thể không chứa các phương thức xuất nhập hay tính toán. Lớp thực thể cũng chỉ chứa các thuộc tính thuộc về các kiểu cơ sở của .NET hoặc thuộc một kiểu thực thể khác. Đặc điểm này giúp lớp thực thể có thể ánh xạ thành một bảng dữ liệu và ngược lại. Cũng vì vậy các lớp thực thể cũng được gọi là các lớp POCO (Plain Old C# Object).

    Trong ứng dụng XAF, tập hợp các lớp thực thể được gọi là mô hình dữ liệu (data model). Mô hình dữ liệu sẽ được ánh xạ tự động thành các bảng của một cơ sở dữ liệu thông qua công cụ ORM. XAF hỗ trợ hai ORM: Entity Framework Core (EF core) và eXpress Persistent Object (XPO). Các phiên bản cũ khuyến nghị sử dụng XPO, trong khi phiên bản mới nhất khuyến nghị sử dụng EF Core. Hiện nay các tài liệu hướng dẫn của XAF đã chuyển ví dụ sang EF Core.

    Các lớp thực thể thường được biểu diễn ở dạng sơ đồ lớp UML để nhìn rõ các thuộc tính và quan hệ giữa các class. Có thể biểu diễn giản lược như dưới đây

    hoặc có thể biểu diễn chi tiết như sau:

    Cũng có thể chỉ rõ các quan hệ giữa các class, như quan hệ association, aggregation, composition, generation. Ở đây chúng ta không vẽ đầy đủ các quan hệ.

    Các lớp thực thể không phụ thuộc vào giao diện người dùng. Do đó chúng được triển khai trong một dự án thư viện độc lập (dự án Module mà chúng ta đã tạo trong bài học trước). Kiến trúc này cho phép các ứng dụng XAF và ứng dụng khác chia sẻ các thực thể, ví dụ: sử dụng Web API Service với client phát triển bằng .NET MAUI, JavaScript, hoặc Blazor.

    Các lớp thực thể là kết quả của quá trình phân tích nghiệp vụ về mặt dữ liệu. Đây là một công đoạn riêng trong quy trình phát triển phần mềm. Nếu sử dụng XAF framework, sau khi phân tích được sơ đồ lớp thực thể là có thể bắt đầu thi công phần mềm ngay.

    Tiếp theo đây chúng ta sẽ cùng thực hiện các công việc sau:

    1. Xây dựng các lớp thực thể của mô hình dữ liệu.
    2. Sử dụng kỹ thuật migration để ánh xạ cấu trúc mô hình dữ liệu vào cơ sở dữ liệu.
    3. Tạo dữ liệu ban đầu.

    Xây dựng các lớp thực thể

    Trong ứng dụng XAF, lớp thực thể luôn đặt trong dự án Module, thư mục BusinessObjects. Vì lý do này, các lớp thực thể trong XAF cũng thường được gọi là lớp Business Object, hoặc gọi tắt là lớp BO.

    Đây chỉ là một quy ước. Tuy nhiên bạn nên tuân thủ quy ước này để dễ quản lý code hơn vì trong dự án Module còn các thư mục cho các nhiệm vụ khác.

    Bước 1. Tạo 5 class mới trong thư mục BusinessObjects: Customer, Employee, Project, ProjectTask, Testimonial.

    Cách làm đơn giản là click phải chuột vào thư mục BusinessObjects và chọn Add | Class …

    Đặt tên phù hợp cho class và ấn nút Add.

    Sau khi làm xong bạn sẽ thu được cấu trúc file mã nguồn như sau:

    Bước 2. Viết code cho các file như sau:

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    using System.Collections.ObjectModel;
    namespace SimpleProjectManager.Module.BusinessObjects;
    [NavigationItem("Marketing")]
    public class Customer : BaseObject {
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual string Email { get; set; }
        public virtual string Company { get; set; }
        public virtual string Occupation { get; set; }
        public virtual IList<Testimonial> Testimonials { get; set; } = new ObservableCollection<Testimonial>();
        public string FullName => ObjectFormatter.Format("{FirstName} {LastName} ({Company})", this, EmptyEntriesMode.RemoveDelimiterWhenEntryIsEmpty);
        [VisibleInListView(false)]
        [ImageEditor(ListViewImageEditorCustomHeight = 75, DetailViewImageEditorFixedHeight = 150)]
        public virtual MediaDataObject Photo { get; set; }
    }
    using DevExpress.ExpressApp.DC;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    using System.Collections.ObjectModel;
    namespace SimpleProjectManager.Module.BusinessObjects;
    [NavigationItem("Marketing")]
    public class Testimonial : BaseObject {
        public virtual string Quote { get; set; }
        [FieldSize(512)]
        public virtual string Highlight { get; set; }
        [VisibleInListView(false)]
        public virtual DateTime CreatedOn { get; set; }
        public virtual string Tags { get; set; }
        public virtual IList<Customer> Customers { get; set; } = new ObservableCollection<Customer>();
    }
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    using System.ComponentModel;
    namespace SimpleProjectManager.Module.BusinessObjects;
    [DefaultProperty(nameof(FullName))]
    public class Employee : BaseObject {
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public string FullName => ObjectFormatter.Format("{FirstName} {LastName}",
            this, EmptyEntriesMode.RemoveDelimiterWhenEntryIsEmpty);
    }
    using DevExpress.ExpressApp.DC;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    using System.ComponentModel.DataAnnotations;
    namespace SimpleProjectManager.Module.BusinessObjects;
    [NavigationItem("Planning")]
    public class ProjectTask : BaseObject {
        [FieldSize(255)]
        public virtual string Subject { get; set; }
        public virtual ProjectTaskStatus Status { get; set; }
        public virtual Employee AssignedTo { get; set; }
        public virtual DateTime? DueDate { get; set; }
        public virtual DateTime? StartDate { get; set; }
        public virtual DateTime? EndDate { get; set; }
        [StringLength(4096)]
        public virtual string Notes { get; set; }
        public virtual Project Project { get; set; }
    }
    public enum ProjectTaskStatus {
        NotStarted = 0,
        InProgress = 1,
        Completed = 2,
        Deferred = 3
    }
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    namespace SimpleProjectManager.Module.BusinessObjects;
    [NavigationItem("Planning")]
    [DefaultProperty(nameof(Name))]
    public class Project : BaseObject {
        public virtual string Name { get; set; }
        public virtual Employee Manager { get; set; }
        [StringLength(4096)]
        public virtual string Description { get; set; }
        public virtual IList<ProjectTask> ProjectTasks { get; set; } = new ObservableCollection<ProjectTask>();
    }

    Khi xây dựng các lớp thực thể bạn có thể để ý thấy chúng ta đang sử dụng một số attribute khá lạ mắt. Tạm thời bạn chưa cần quan tâm đến các attribute này. Chúng ta sẽ có một bài học riêng để giải thích chi tiết cách dùng attribute trong XAF.

    Bước 3. Xây dựng DbContext.

    Tìm file SimpleProjectManagerDbContext.cs cùng trong thư mục BusinessObjects.

    Trong file này có sẵn một class SimpleProjectManagerEFCoreDbContext (nằm ở cuối file code). Hãy điều chỉnh code của class này như sau:

    [TypesInfoInitializer(typeof(SimpleProjectManagerContextInitializer))]
    public class SimpleProjectManagerEFCoreDbContext : DbContext {
        public SimpleProjectManagerEFCoreDbContext(DbContextOptions<SimpleProjectManagerEFCoreDbContext> options) : base(options) {
        }
        //public DbSet<ModuleInfo> ModulesInfo { get; set; }
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Testimonial> Testimonials { get; set; }
        public DbSet<Project> Projects { get; set; }
        public DbSet<ProjectTask> ProjectTasks { get; set; }
        public DbSet<Employee> Employees { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder) {
            base.OnModelCreating(modelBuilder);
            modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues);
        }
    }

    DbContext là một class đặc sản của Entity Framework và Entity Framework Core. Lớp DbContext biểu diễn một phiên làm việc với cơ sở dữ liệu, cung cấp khả năng truy vấn, theo dõi object và lưu dữ liệu. Mỗi lớp thực thể cần ánh xạ sang cơ sở dữ liệu phải xây dựng thành một thuộc tính trong lớp DbContext.

    XAF sử dụng EF Core ORM, do vậy bạn cần hiểu nguyên lý hoạt động của ORM nói chung, cũng như cách làm việc với EF Core nói riêng. Nếu không biết gì về vấn đề này, chúng tôi khuyên bạn nên tự học EF Core trước khi học XAF. Một phần lớn thời gian trong quá trình xây dựng ứng dụng XAF chính là xây dựng Entity Data Model cho EF Core.

    Sử dụng migration

    Migration là một cơ chế đặc biệt trong Entity Framework cho phép cập nhật CSDL theo cấu trúc model class mà không làm mất dữ liệu hiện có. Đây cũng là cơ chế phải sử dụng đến nếu trong CSDL thử nghiệm của bạn có nhiều dữ liệu và bạn không muốn hủy bỏ CSDL cũ để làm lại cái mới.

    Bước 1. Cài đặt gói thư viện

    Trước khi sử dụng migration, chúng ta cần cài đặt gói thư viện Microsoft.EntityFrameworkCore.Tools cho dự án Module.

    Click phải chuột vào nút Dependencies của dự án Module và chọn mục Manage Nuget Packages. Sử dụng giao diện Nuget Package Manager để cài đặt như sau:

    Trong ô tìm kiếm của tab Browse gõ tìm Microsoft.EntityFrameworkCore.Tools.

    Đến đây cần chú ý lựa chọn phiên bản phù hợp, nếu không về sau migration sẽ không hoạt động do lỗi “Build Failed”. Hãy mở rộng nút Packages trong nút Dependencies của dự án Module, bạn sẽ nhìn thấy các gói thư viện đang cài đặt.

    Mặc định khi chọn EF Core làm ORM, XAF cài đặt sẵn hai gói InMemory và SqlServer. Khi đó gói Tools cũng phải trùng khớp về phiên bản với các gói kia.

    Quay lại giao diện Nuget Package Manager và chọn phiên bản phù hợp (trong ví dụ này là 6.0.3) và ấn nút Install để bắt đầu cài đặt. Khi cài đặt thành công bạn thu được cấu trúc package như sau:

    Bước 2. Cập nhật code

    Mở file SimpleProjectManagerDbContext.cs và tìm đến class SimpleProjectManagerDesignTimeDbContextFactory. Class này có sẵn một số code như sau:

    public class SimpleProjectManagerDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SimpleProjectManagerEFCoreDbContext> {
        public SimpleProjectManagerEFCoreDbContext CreateDbContext(string[] args) {
            throw new InvalidOperationException("Make sure that the database connection string and connection provider are correct. After that, uncomment the code below and remove this exception.");
            //var optionsBuilder = new DbContextOptionsBuilder<SimpleProjectManagerEFCoreDbContext>();
            //optionsBuilder.UseSqlServer("Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=SimpleProjectManager");
            //optionsBuilder.UseChangeTrackingProxies();
            //optionsBuilder.UseObjectSpaceLinkProxies();
            //return new SimpleProjectManagerEFCoreDbContext(optionsBuilder.Options);
        }
    }

    Trong code của phương thức CreateDbContext chúng ta nhìn thấy một nhóm code đang bị đánh comment, và 1 dòng code phát ngoại lệ throw new InvalidOperationException(...). Bây giờ hãy đảo ngược lại: comment dòng code phát ngoại lệ, và uncomment phần code còn lại. Chúng ta thu được code như sau:

    public class SimpleProjectManagerDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SimpleProjectManagerEFCoreDbContext> {
        public SimpleProjectManagerEFCoreDbContext CreateDbContext(string[] args) {
            //throw new InvalidOperationException("Make sure that the database connection string and connection provider are correct. After that, uncomment the code below and remove this exception.");
            var optionsBuilder = new DbContextOptionsBuilder<SimpleProjectManagerEFCoreDbContext>();
            optionsBuilder.UseSqlServer("Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=SimpleProjectManager");
            optionsBuilder.UseChangeTrackingProxies();
            optionsBuilder.UseObjectSpaceLinkProxies();
            return new SimpleProjectManagerEFCoreDbContext(optionsBuilder.Options);
        }
    }

    Đến đây có một vấn đề nhỏ, tùy vào việc cài đặt SQL Server trên máy bạn. Hãy nhìn dòng code:

    optionsBuilder.UseSqlServer("Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=SimpleProjectManager");

    Dòng này chỉ định chuỗi kết nối sẽ được sử dụng bởi cơ chế migration. Mặc định XAF sử dụng localdb nên trong chuỗi kết nôi, giá trị tham số Data Source là Data Source=(localdb)\\mssqllocaldb. Nếu bạn không sử dụng localdb, bạn cần tự xác định thông tin về instance của SQL Server và địa chỉ máy. Giả sử máy bạn cài đặt sẵn SQL Server Express, thông thường giá trị tham số Data Source sẽ là localhost\\sqlexpress hoặc localhost\\mssqlserver.

    Nếu bạn chưa biết: LocalDB là một tính năng của SQL Server dành cho các nhà phát triển ứng dụng. LocalDB sử dụng SQL Server Database Engine. Khi kết nối, hạ tầng của SQL Server sẽ tự động được tạo và khởi động, cho phép ứng dụng sử dụng cơ sở dữ liệu mà không cần các thao tác cấu hình phức tạp. LocalDB được cài đặt mặc định cùng SQL Server Express.

    Bước 3. Chạy lệnh migration

    Để kích hoạt migration cho một project, bạn mở cửa sổ Package Manager Console sau đó chọn Default project là project Module chứa lớp context. Package Manager Console mở từ Tools → Library Package Manager → Package Manager Console.

    Chạy các lệnh lần lượt theo thứ tự:

    PM> add-migration MyInitialMigrationName -StartupProject "SimpleProjectManager.Module" -Project "SimpleProjectManager.Module"

    PM> update-database -StartupProject "SimpleProjectManager.Module" -Project "SimpleProjectManager.Module"

    Nếu các lệnh này thực hiện thành công, một cơ sở dữ liệu mới đã được tạo ra theo cấu trúc của các lớp thực thể:

    Chạy thử ứng dụng

    Đến đây thực tế là hai ứng dụng của chúng ta đã hoàn thành (!).

    Hãy cùng chạy thử xem kết quả như thế nào. Tuy nhiên, trước khi chạy thử chúng ta cần thực hiện một vài thao tác nhỏ.

    Chạy thử ứng dụng Blazor

    Bước 1. Click phải chuột vào tên dự án Blazor.Server và chọn Set as Startup Project.

    Bước 2. Tìm mở file appsettings.json, mục ConnectionStrings. Đây là file cấu hình của ứng dụng Blazor.

    "ConnectionStrings": {
        "ConnectionString": "Integrated Security=SSPI;Pooling=false;MultipleActiveResultSets=true;Data Source=localhost\\sqlexpress;Initial Catalog=SimpleProjectManager",
        "EasyTestConnectionString": "Integrated Security=SSPI;Pooling=false;MultipleActiveResultSets=true;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=SimpleProjectManagerEasyTest"
      }

    Ở đây bạn cần chú ý điều chỉnh giá trị của tham số Data Source giống như đã làm ở phần migration. Trong ví dụ này, máy của tôi cài đặt SQL Express với tên instance là sqlexpress nên tham số Data Source có giá trị localhost\\sqlexpress. Chú ý sử dụng hai ký tự \\ vì chuỗi json sử dụng ký tự \ với ý nghĩa đặt biệt.

    Ấn F5 hoặc Ctrl+F5 để chạy thử ứng dụng, bạn sẽ thu được kết quả là một ứng dụng Blazor server hoàn chỉnh:

    Ứng dụng này có đầy đủ những tính năng mà một ứng dụng quản lý LoB cần đến. Hãy dành thời gian tự khám phá ứng dụng này.

    Chạy thử ứng dụng winforms

    Tương tự, nếu muốn thử nghiệm ứng dụng winforms, hãy đặt nó làm Startup Project, sau đó tìm mở file App.config. Đây là file cấu hình của ứng dụng. File App.config có sẵn node connectionStrings như sau:

    <connectionStrings>
        <add name="EasyTestConnectionString" connectionString="Integrated Security=SSPI;MultipleActiveResultSets=true;Data Source=(localdb)\mssqllocaldb;Initial Catalog=SimpleProjectManagerEasyTest" providerName="System.Data.SqlClient"/>
        <add name="ConnectionString" connectionString="Integrated Security=SSPI;Pooling=false;MultipleActiveResultSets=true;Data Source=localhost\sqlexpress;Initial Catalog=SimpleProjectManager" providerName="System.Data.SqlClient" />
        <!--
        Use the following connection string to connect to a Jet (Microsoft Access) database:
        <add name="ConnectionString" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Password=;User ID=Admin;Data Source=SimpleProjectManager.mdb;Mode=Share Deny None;"/>
        -->
      </connectionStrings>

    Tương tự, hãy điều chỉnh giá trị tham số Data Source cho phù hợp với cấu hình thực tế của máy. Ở đây tôi sử dụng Data Source=localhost\sqlexpress. Chú ý chỉ sử dụng 1 dấu \ để phân tách tên máy và tên instance. Nếu sử dụng hai dấu \\ sẽ dẫn đến lỗi khi chạy.

    Ấn F5 hoặc Ctrl + F5 để chạy thử ứng dụng.

    Hãy dành thời gian để khám phá ứng dụng vừa tạo ra.

    Đến đây hẳn các bạn đã thấy sức mạnh của XAF! Chỉ cần xây dựng được lớp thực thể phù hợp với nghiệp vụ của bài toán, XAF sẽ hỗ trợ tất cả các công việc còn lại, từ tạo cơ sở dữ liệu (thực tế là do EF core làm) đến sinh giao diện tự động với đầy đủ các tính năng cần có của một ứng dụng quản lý LoB. Chúng ta không cần tự mình xử lý các chi tiết này.

    + Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
    + Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
    + Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
    Cảm ơn bạn!

    Kết luận

    Qua bài học này chúng ta đã bước đầu làm quen với việc xây dựng một ứng dụng quản lý sử dụng XAF framework. Có thể nhận thấy rằng, bạn vẫn cần viết code, nhưng lượng code viết rất ít, và chỉ cần tập trung viết code xây dựng lớp thực thể riêng của từng bài toán. Những thao tác khác như xây dựng cơ sở dữ liệu, xây dựng giao diện, tạo các tính năng để làm việc với dữ liệu đều được XAF thực hiện tự động thay chúng ta. Điều này dẫn đến quy trình phát triển một phần mềm với XAF được đơn giản hóa rất nhiều. Người phát triển ứng dụng chỉ việc tập trung vào phân tích nghiệp vụ của bài toán để ra được mô hình dữ liệu (tức là sơ đồ lớp thực thể) mà không cần quan tâm đến các chi tiết khác.

    Tuy nhiên, XAF không chỉ đơn giản như vậy. Thực tế XAF cho phép người lập trình can thiệp rất sâu vào quá trình tạo ra ứng dụng. Trong các bài học tiếp theo chúng ta sẽ lần lượt xem xét hết các khả năng của framework này.

    Theo dõi
    Thông báo của
    guest

    0 Thảo luận
    Phản hồi nội tuyến
    Xem tất cả bình luận