Khác

ExtremeML

Trong bài lần trước, mình có giới thiệu về Linq2Excel.

Một trong các hạn chế của Linq2Excel là thư viện này chỉ cho phép query dữ liệu, và ta không thể thêm, thay đổi hay xóa sửa dữ liệu trong đó được.

Để giải quyết vấn đề cập nhập thông tin trong file excel. Từ phiên bản Excel 2007 trở đi, Microsoft đưa ra chuẩn OfficeOpenXML (gọi tắt là OpenXML), tất cả dữ liệu office được lưu giữ dưới dạng XML, để kiểm tra bạn chỉ cần đổi đuôi file của MS Office thành .zip và mở bằng Winzip bạn sẽ thấy được dữ liệu XML của file đó. Để hỗ trợ lập trình với OpenXML, MS đưa ra bộ Open XML SDK mà bạn có thể download tại đây.

Tuy nhiên, việc dùng Open XML SDK vẫn phức tạp, do đó Tim Coulter viết ra thư viện ExtremeML, một thư viện Opensource tuyệt vời để làm việc với Excel 2007.

ExtremeML được mô tả như sau:


Sau đây là các tính năng chính của ExtremeML:

  • Tạo mới hoặc mở 1 workbook.
  • Đọc và ghi dữ liệu từ stream.
  • Thêm, xóa, thay đổi các worksheet.
  • Thêm, xóa, sửa row và column.
  • Hỗ trợ strongly-typed.
  • Đọc và ghi các formula của cells.
  • Đọc, ghí và thay đổi dữ liệu ảnh, rich text.
  • Hỗ trợ footer và header.

Ngoài ra ExtremeML còn hỗ trợ các tính năng sau:

  • Hỗ trợ dữ liệu dựa trên dạng bảng của Excel table.
  • Hỗ trợ template.
  • Các tính năng nâng cao như: group, merge, macro và phiên bản mới nhất hỗ trợ Excel 2010.

Ví dụ đoạn code sau:

public static void PopulateDataTypesRandom()
{

using (var package = SpreadsheetDocumentWrapper.Open(“MyWorkbook.xlsx”))
{
var table = package.WorkbookPart.GetTablePart(“DataTypes”).Table;

var data = new List<object[]>();

for (var i = 0; i < 10; i++)
{
data.Add(new
object[]
{
Utility.GetRandomString(),
Utility.GetRandomUri(),
Utility.GetRandomDateTime(),
Utility.GetRandomTimeSpan(),
Utility.GetRandomInteger(),
Utility.GetRandomDouble(),
Utility.GetRandomDecimal(),
Utility.GetRandomBoolean()
});
}

table.Fill(data.ToArray());
}
}

Đoạn code này insert dữ liệu vào bảng như sau:

Happy coding!

Standard
C#

Single responsibility principle

  • Giới thiệu

    SOLID là cách viết tắt của các nguyên lý cơ bản trong thiết kế hướng đối tượng, 5 nguyên lý này rất quan trọng trong lập trình hướng đối tượng.

    + Single Resposibility principle – SRP

    + Open Closed Principle – OCP

    + Liskov Substitution Principle – LSP

    + Interface Segregation Principle – ISP

    + Dependency Inversion Principle – DIP

    Bài viết này tôi sẽ trình bày về nguyên lý đầu tiên: Single respónibility principle

  • Định nghĩa SRP
    • Định nghĩa responsibility

      Robert C. Martin chỉ ra rằng responsibility là một lý do để thay đổi. Để dễ hiểu, ta hãy hình dung responsibility là mục đích. Mục đích là những thứ bạn cần trong class của bạn, do đó một class chỉ nên có một mục đích mà thôi!

    • Định nghĩa SRP

      SRP chỉ ra rằng tất cả các đối tượng (object) chỉ có một resposibility duy nhất, và responsibility đó được gói trong một lớp. Từ đó Martin chỉ ra rằng một lớp hoặc module chỉ được có một và chỉ một lý do để thay đổi.

    • Ví dụ: Một module tạo và in một report, module này bị thay đổi bởi một trong 2 lý do. Đó là nội dung của report thay đổi hoặc format của report bị thay đổi. 2 sự thay đổi này được sinh ra bởi các nguyên nhân khác nhau. SRP chỉ ra rằng 2 nguyên nhân thay đổi này chính là 2 phần liên quan tới 2 responsibility khác nhau. Vì vậy người lập trình không tốt nếu ghép cả 2 phần này vào một lớp (Class).
  • Lợi ích của SRP:
    • Dễ dàng dùng lại cho các responsibility
    • Mã lệnh rõ ràng
    • Dễ dàng chọn tên phù hợp với responsibility
    • Dễ dàng để đọc hiểu đoạn code
  • Ví dụ một class là RepositoryBase dùng để truy cập database, ta định nghĩa 1 function là GetAll để lấy về tất cả dữ liệu của một bảng nào đó:

    public abstract class
    RepositoryBase<TEntity, TIDType, TLinqEntity, TContext> : IRepository<TEntity, TIDType, TLinqEntity, TContext>,IDisposable

    where TEntity : AbstractBaseData<TIDType>

    where TLinqEntity : EntityObject

    where TContext : ObjectContext,new()

    {

    public IList<TLinqEntity> SelectAll()

    {

    try

    {

    return DoQuery().ToList(); //_ctx.CreateQuery<TLinqZEntity>(“[" + typeof(TLinqEntity).Name + "]“);

    }

    catch (Exception ex)

    {

    ExceptionHandler.Handle(ex);

    return null;

    }

    }

     public virtual IList<TEntity> GetAll()
    { 

  • var entities = this.SelectAll();

    var listOfEntites = GetEntitiesFromEF(entities);

      return listOfEntites;

    }

     protected IList<TEntity> GetEntitiesFromEF(IList<TLinqEntity> linqEntities)
    {
      Mapper.Initialize

    (

     cfg =>
    {

    cfg.CreateMap<TLinqEntity, TEntity>();

    cfg.CreateMap<TEntity, TLinqEntity>();

    }

    );

     var listOfEntities = Mapper.Map<IList<TLinqEntity>, IList<TEntity>>(linqEntities);
    return listOfEntities;
    }

    }

    Trong ví dụ này, chúng ta đã vị phạm nguyên tắc của SRP, class RepositoryBase làm việc có 2 mục đích khác nhau là DataAccess và DataMapping: Để tuân thủ nguyên tắc này, ta tạo ra một class riêng chịu trách nhiệm về DataMapper:

     public class DataMapper<TEntity, TIDType, TLinqEntity> : IDataMapper<TEntity, TIDType, TLinqEntity>

    where TEntity : AbstractBaseData<TIDType>

    where TLinqEntity : EntityObject

    {

    public IList<TEntity> GetEntitiesFromEF(IList<TLinqEntity> linqEntities)

    {

    Mapper.Initialize

    (

    cfg =>

    {

    cfg.CreateMap<TLinqEntity, TEntity>();

    cfg.CreateMap<TEntity, TLinqEntity>();

    }

    );

    var listOfEntities = Mapper.Map<IList<TLinqEntity>, IList<TEntity>>(linqEntities);

    return listOfEntities;

    }

    }

    Hàm GetAll sẽ có dạng như sau:

    public virtual IList<TEntity> GetAll()

    {

    var entities = this.SelectAll(); 

    var listOfEntites = Factory.CreateDataMapper<TEntity, TIDType, TLinqEntity>().GetEntitiesFromEF(entities);

    return listOfEntites;

    }

  • Tham khảo thêm về SRP tại:

    PDF file: http://www.objectmentor.com/resources/articles/srp.pdf

    Video: http://dimecasts.net/Casts/CastDetails/88

     
     

     
     

Standard
C#

Validate winform bằng SpecExpress

SpecExpress là một Fluent validation framework, trong bài viết này, tôi xin giới thiệu cách validate Winform bằng SpecExpress và Error provider.

Xem giới thiệu về SpecExpress.

Trước hết tạo một Entity kế thừa từ IDataErrorInfo interface.

image

Sau đó tạo một dictionary đẻ lưu lại các message lỗi của các Property trong Entity đó.

   1: /// <summary>

   2:        /// Dictionary of validation errors, it contains Property and Error messages...

   3:        /// </summary>

   4:        public Dictionary<string, string> Errors

   5:        {

   6:            get;

   7:            set;

   8:        }

 

Việc tiếp theo là implement interface IDataError: index this và thuộc tính Error:

   1: public string Error

   2:        {

   3:            get

   4:            {

   5:                return lastError; 

   6:            }

   7:              

   8:        }

index this[propertyName] trả về Error message cho Error Provider

 

   1: /// <summary>

   2:     /// Gets the error of a specific property

   3:     /// </summary>

   4:     /// <param name="propertyName">The property name</param>d

   5:     /// <returns>The error message</returns>

   6:     public string this[string propertyName]

   7:     {

   8:         #region Doan Manh Ha commented for write new Error validation method by Specexpress

   9:         //get { return _propError[propertyName]; }

  10:         //set 

  11:         //{

  12:         //    _propError[propertyName] = value;

  13:         //    PropertyHasChanged(); 

  14:         //}

  15:         #endregion

  16:  

  17:         get

  18:         {

  19:             if (!IsNeedValidation)

  20:             {

  21:                 if (Errors!=null && Errors.ContainsKey(propertyName))

  22:                     return Errors[propertyName];

  23:                 else

  24:                     return string.Empty;

  25:             }

  26:  

  27:             var notification = this.ValidateProperty(propertyName);

  28:  

  29:             

  30:  

  31:             if (notification==null || notification.IsValid)

  32:             {

  33:                 lastError = string.Empty;

  34:                

  35:             }

  36:             else

  37:             { 

  38:                 // TODO: aggregate notification messages in readable form and assign to lastError 

  39:                 lastError = this.BuildErrorMessages(notification.ErrorSummary());

  40:             }

  41:  

  42:             AddError(propertyName, lastError);

  43:             return lastError;

  44:         }

  45:         set

  46:         {

  47:             AddError(propertyName, value);

  48:         }

  49:  

  50:     }

- IsNeedValidation là một thuộc tính xác định xem có cần phải validate không.

- Validate chính là Virtual method để validate property đó, trả về ValidationNotification

   1: protected virtual ValidationNotification ValidateProperty(string propertyName)

   2:     {

   3:         throw new NotImplementedException();

   4:     }

- AddError là hàm add nội dung Error vào Error dictionary:

   1: private void AddError(string propertyName, string error)

   2:         {

   3:             //lastError = value;

   4:             if (Errors == null)

   5:                 Errors = new Dictionary<string, string>();

   6:  

   7:             if (Errors.ContainsKey(propertyName))

   8:                 Errors[propertyName] = error;

   9:             else

  10:                 Errors.Add(propertyName, error);

  11:         }

 

Tiếp theo ta viết Entity cho User, kế thừa từ AbstractBaseData:

 

   1: public class User:AbstractBaseData<int>

   2:    {

   3:  

   4:        private int id;

   5:        private string name;

   6:        private string password;

   7:        private string rules;

   8:  

   9:        #region Public properties

  10:  

  11:        public int Id

  12:        {

  13:            get

  14:            {

  15:                return this.id;

  16:            }

  17:            set

  18:            {

  19:                if ((this.id != value))

  20:                {

  21:                    this.id = value;

  22:                    this.OnPropertyChanged("Id");

  23:  

  24:                }

  25:            }

  26:        }

  27:  

  28:        public string Name

  29:        {

  30:            get

  31:            {

  32:                return this.name;

  33:            }

  34:            set

  35:            {

  36:                if ((this.name != value))

  37:                {

  38:                    this.name = value;

  39:                    this.OnPropertyChanged("Name");

  40:  

  41:                }

  42:            }

  43:        }

  44:  

  45:        public string Password

  46:        {

  47:            get

  48:            {

  49:                return this.password;

  50:            }

  51:            set

  52:            {

  53:                if ((this.password != value))

  54:                {

  55:                    this.password = value;

  56:                    this.OnPropertyChanged("Password");

  57:  

  58:                }

  59:            }

  60:        }

  61:  

  62:        public string Rules

  63:        {

  64:            get

  65:            {

  66:                return this.rules;

  67:            }

  68:            set

  69:            {

  70:                if ((this.rules != value))

  71:                {

  72:                    this.rules = value;

  73:                    this.OnPropertyChanged("Rules");

  74:  

  75:                }

  76:            }

  77:        }

  78:  

  79:        public IList<Role> Roles

  80:        {

  81:            get;

  82:            set;

  83:        }

  84:  

  85:        #endregion

  86:  

  87:        #region Validations

  88:  

  89:        UserSpecification spec = new UserSpecification();

  90:  

  91:        protected override ValidationNotification ValidateProperty(string propertyName)

  92:        {

  93:  

  94:            try

  95:            {

  96:  

  97:                return ValidationCatalog.ValidateProperty(this, propertyName, spec);

  98:            }

  99:            catch (Exception)

 100:            {

 101:                return null;

 102:            }

 103:        }

 104:  

 105:        public override bool Validate()

 106:        {

 107:           

 108:            this.IsNeedValidation = true;

 109:           

 110:            this.PropertyHasChanged();

 111:            this.IsNeedValidation = false;

 112:            return this.IsValid;

 113:        }

 114:        #endregion

 115:    }

 

Và Specification cho User entity:

   1: public class UserSpecification:Validates<User>

   2:   {

   3:       public UserSpecification()

   4:       {

   5:           Check(user => user.Name).Required(ValidationConstants.USER_NAME_REQ);

   6:  

   7:           Check(user => user.Password).Required(ValidationConstants.PASSWORD_REQ)

   8:                                       .MinLength(6);

   9:  

  10:           Check(user => user.Roles).Required(ValidationConstants.USER_ROLE_REQ);

  11:       }

  12:   }

Xử lý ở Winform UI

image

Sự kiện cho button Cập nhật:

   1: private void btnUpdate_Click(object sender, EventArgs e)

   2:    {

   3:        string typeOfUserViewData = typeof(UserViewData).Name;

   4:        UserViewData userViewData = this.GetSessionData(typeOfUserViewData) as UserViewData;

   5:        

   6:        var isValid = userViewData.User.Validate();

   7:        

   8:        //Récupération de la VSD

   9:        if (!isValid)

  10:        {

  11:            epUserView.DataSource = userViewData.User;

  12:            return;

  13:        }

  14:  

  15:        

  16:        userViewData.IsCreation = false;

  17:        //Transfert de l'exécution vers le controleur

  18:        //this.InvokeController(ControllerConstants.USER_CONTROLLER, ControllerActionConstants.SAVE_ACTION);   

  19:    }

Và đây là kết quả:

image

Standard
C#

SpecExpress – A fluent validation

SpecExpress là một fluent API để validate dữ liệu có hợp lệ hay không.

SpecExpress không dùng các Custom attribute để decorator thuộc tính như các Validation framework khác (ví dụ: Validation application block,…), mà SpecExpress dùng riêng một lớp gọi là Specification, lớp này sẽ chứa các rule cho tất cả các thuộc tính cần validate. Việc này giúp cho code dễ đọc hơn (vì dùng Fluent API) và code cũng dễ maintaince hơn.

Ví dụ:

   1: public class UserSpecification:Validates<User>

   2:   {

   3:       public UserSpecification()

   4:       {

   5:           Check(user => user.Name).Required(ValidationConstants.USER_NAME_REQ);

   6:  

   7:           Check(user => user.Password).Required(ValidationConstants.PASSWORD_REQ)

   8:                                       .MinLength(6);

   9:  

  10:           Check(user => user.Roles).Required(ValidationConstants.USER_ROLE_REQ);

  11:       }

  12:   }

Class này kế thừa từ class Validates<T> của SpecExpress.

Constructor định nghĩa các rule để validate Entity User: Cần phải có Tên đăng nhập, cần phải có mật khẩu tối thiểu 6 ký tự, và User cần phải có ít nhất một Role.

Chi tiết hơn, các bạn xem tại đây.

Standard
C#

Linq2Excel: Truy vấn Excel bằng Linq

Linq2Excel hỗ trợ bạn truy vấn dữ liệu Excel bằng Linq như sample sau:

   1: var repo = new ExcelQueryFactory();

   2: repo.FileName = "pathToExcelFile";

   3:  

   4: var peopleWithSiblings = from p in repo.Worksheet<Person>()

   5:                          where p.Siblings > 0

   6:                          select p;

Trong trường hợp này, các property name của class Person có header tương tự trong các columns của file excel tại Sheet1.

 

Trong trường hợp, header của các column trong file Excel tại sheet1 không giống như tên các property của class, bạn dùng hàm AddMapping như sau:

   1: public class ExcelStudentRepository

   2:    {

   3:        /// <summary>

   4:        /// Get all Student from excel file

   5:        /// </summary>

   6:        /// <param name="excelFile"></param>

   7:        /// <returns></returns>

   8:        public IList<ExcelStudent> GetAll(string excelFile)

   9:        {

  10:            var excel = new ExcelQueryFactory(excelFile);

  11:            excel.AddMapping<ExcelStudent>(student => student.MSV, "MSV");

  12:            excel.AddMapping<ExcelStudent>(student => student.HoDem, "Ho dem");

  13:            excel.AddMapping<ExcelStudent>(student => student.NgaySinh, "ngay sinh");

  14:            excel.AddMapping<ExcelStudent>(student => student.NienKhoa, "nien khoa");

  15:            excel.AddMapping<ExcelStudent>(student => student.Ten, "ten");

  16:            excel.AddMapping<ExcelStudent>(student => student.Phai, "phai");

  17:  

  18:            var students = from s in excel.Worksheet<ExcelStudent>()

  19:                           select s;

  20:  

  21:            return students.ToList();

  22:  

  23:  

  24:        }

  25:  

  26:    }

Linq2Excel sẽ Map thuộc tỉnh như: “HoDem” của class ExcelStudent với column có tên là “ho dem” trong sheet1 của Excel

 

Chi tiết hơn về LinqtoExcel, các bạn xem tại đây.

Standard
1

AutoMapper – a fluent configuration API to define an object-object mapping strategy

Trong bài lần trước nói về fluent interface. Lần này mình giới thiệu 1 API theo hướng “fluent” dùng để map giữa các object với nhau.

Chi tiết về AutoMapper, bạn có thể xem tại đây.

Ta đi vào ví dụ cho dễ hình dung.

 

Giả sử bạn có StudentRepository để lấy về chi tiết sinh viên.

  public override Student GetItem(int id)
  {
           

  }

Bạn dùng LINQ2SQL để lấy về chi tiết sinh viên như sau:

var context = new StudentCardDBMappings.StudentCardDBDataContext();

context.Connection.Open();

var studentLinq = (from s in context.Students
                   where s.Id.Equals(id)
                   select s).FirstOrDefault();

 

Nhưng LINQ2SQL sẽ trả về Entity mà Linq2Sql sinh ra, không phải Entity của ta mong đợi (Entity mong đợi có thể là DTO, custom entity mà support validating hoặc là business object,… ). Do đó ta phải convert từ Entity  của Linq2Sql sang Entity của ta:

 

1) Cách cổ điển: Gọi Set, Get của các thuộc tính của entity:

 var student = new Student
          {
              Id = studentLinq.Id,
              Name = studentLinq.Name,
              MidleName = studentLinq.MidleName
          };

2) Dùng AutoMapper: trong trường hợp này, AutoMapper sẽ tự động map các thuộc tính cho các entity ta cần dùng. Dùng AutoMapper như sau:

a) Config source và destination cần phải map

 Mapper.Initialize
         (
             cfg => cfg.CreateMap<StudentCardDBMappings.Student, Student>()
         );

b) Gọi hàm map để AutoMapper tự động map các thuộc tính của source và destination (trường hợp này là mặc đinh,..)

var student = Mapper.Map<StudentCardDBMappings.Student, Student>(studentLinq);

Kết quả, nếu dùng AutoMapper ta có hàm như sau:

public override Student GetItem(int id)
{


	var context = new StudentCardDBMappings.StudentCardDBDataContext();
	
	context.Connection.Open();
	
	var studentLinq = (from s in context.Students
						where s.Id.Equals(id)
						select s).FirstOrDefault();
	
	//var student = new Student
	//        {
	//            Id = studentLinq.Id,
	//            Name = studentLinq.Name,
	//            MidleName = studentLinq.MidleName
	//        };
	
	Mapper.Initialize
		(
			cfg => cfg.CreateMap<StudentCardDBMappings.Student, Student>()
		);
	
	var student = Mapper.Map<StudentCardDBMappings.Student, Student>(studentLinq);
	
	return student;

}

Và đây là kết quả Unit Test:

- Test Null

image

- Test có giá trị:

image

Chi tiết về AutoMapper, mời các bạn tham khảo tại: http://automapper.codeplex.com/

Standard