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!
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
- Dễ dàng dùng lại cho các responsibility
-
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
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.
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
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ả:
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.
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.
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
- Test có giá trị:
Chi tiết về AutoMapper, mời các bạn tham khảo tại: http://automapper.codeplex.com/
Fluent interface
Về định nghĩa fluent interface, mời các bạn xem tại đây
Giả sử bạn muốn đi từ Hà Nội tới Sài Gòn qua các thành phố khác nhau và bằng các phương tiện khác nhau theo thứ tự sau:
- Đi taxi tới ga.
- Đi tàu hỏa tới Huế.
- Nghỉ tại Huế 1 tuần.
- Đi xe khách tới Đà Nẵng.
- Nghỉ tại Đà Nẵng 3 ngày.
-
Đi máy bay tới Sài Gòn.
Nếu theo cách lập trình thông thường, bạn sẽ có 1 class HanhTrinh, trong class đó sẽ có 5 phương thức DiBangTaxi(Địa điểm), DiBangTauHoa(Địa điểm), DiBangXeKhach(Địa điểm), NghiTai(Địa Điểm), DiBangMayBay(Địa điểm) như sau:
public class HanhTrinh
{
public void DiBangTaxi(string diaDiem)
{
Console.WriteLine(“Di bang taxi toi {0}”, diaDiem);
}
public void DiBangTauHoa(string diaDiem)
{
Console.WriteLine(“Di bang tau hoa toi {0}”, diaDiem);
}
public void DiBangXeKhach(string diaDiem)
{
Console.WriteLine(“Di bang xe khach toi {0}”, diaDiem);
}
public void DiBangMayBay(string diaDiem)
{
Console.WriteLine(“Di bang may bay toi {0}”, diaDiem);
}
public void NghiTai(string diaDiem,int ngay)
{
Console.WriteLine(“Nghi tai {0} {1} ngay”, diaDiem, ngay);
}
}
Khi đi từ Hà Nội tới Sài Gòn, ta gọi class HanhTrinh như sau:
static void Main(string[] args)
{
var hanhtrinh = new
HanhTrinh();
hanhtrinh.DiBangTaxi(“Ga Ha Noi”);
hanhtrinh.DiBangTauHoa(“Hue”);
hanhtrinh.NghiTai(“Hue”, 7);
hanhtrinh.DiBangXeKhach(“Da Nang”);
hanhtrinh.NghiTai(“Da Nang”, 3);
hanhtrinh.DiBangMayBay(“Sai Gon”);
}
Nếu ta viết lại class HanhTrinh theo cách thức “fluent” thì nó có dạng như sau:
public class FluentHanhTrinh
{
public FluentHanhTrinh DiBangTaxi(string diaDiem)
{
Console.WriteLine(“Di bang taxi toi {0}”, diaDiem);
return this;
}
public FluentHanhTrinh DiBangTauHoa(string diaDiem)
{
Console.WriteLine(“Di bang tau hoa toi {0}”, diaDiem);
return this;
}
public FluentHanhTrinh DiBangXeKhach(string diaDiem)
{
Console.WriteLine(“Di bang xe khach toi {0}”, diaDiem);
return this;
}
public FluentHanhTrinh DiBangMayBay(string diaDiem)
{
Console.WriteLine(“Di bang may bay toi {0}”, diaDiem);
return this;
}
public FluentHanhTrinh NghiTai(string diaDiem, int ngay)
{
Console.WriteLine(“Nghi tai {0} {1} ngay”, diaDiem, ngay);
return this;
}
}
Với cách viết này, ta gọi class FluentHanhTrinh như sau:
static void Main(string[] args)
{
new FluentHanhTrinh()
.DiBangTaxi(“Ga Ha Noi”)
.DiBangTauHoa(“Hue”)
.NghiTai(“Hue”, 7)
.DiBangXeKhach(“Da Nang”)
.NghiTai(“Da Nang”, 3)
.DiBangMayBay(“Sai Gon”);
}
Và đây là kết quả chương trình:
Phản hồi gần đây