আমি আপনার “Simple Coding Tutor”। চলুন, আজকের লেকচারটি অত্যন্ত সহজভাবে এবং বিস্তারিতভাবে বুঝে নেওয়া যাক। (আপনি যেহেতু visual নিষেধ করেছেন, আমি কোনো সিমুলেশন বা ভিজ্যুয়াল যুক্ত করছি না)।
📝 Lecture Summary (Quick Revision)
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য সম্পূর্ণ লেকচারের মূল কাজগুলো নিচে লিস্ট আকারে দেওয়া হলো:
ToPerson()Method:PersonAddRequestDTO-তে একটি মেথড তৈরি করা হয়েছে, যা DTO অবজেক্টের ডেটাগুলোকে মেইনPerson(Domain Model) অবজেক্টে কপি করে কনভার্ট করে।PersonResponseDTO: ক্লায়েন্ট বা Controller-কে রেসপন্স দেওয়ার জন্য নতুন একটি DTO ক্লাস তৈরি করা হয়েছে, যেখানে কিছু ফিল্ড বাদ দেওয়া হয়েছে এবংAgeএর মতো কিছু নতুন ক্যালকুলেটেড ফিল্ড যোগ করা হয়েছে।- Overriding
Equals&GetHashCode: Unit Test-এর সময়Assert.Contains()যেন সঠিকভাবে কাজ করে (Value Equality চেক করে), সেজন্যPersonResponseক্লাসে এই মেথডগুলো ওভাররাইড করা হয়েছে। - Extension Method (
ToPersonResponse):Personক্লাসকে না টেম্পার করেই তার ভেতরToPersonResponse()মেথড ইনজেক্ট করার জন্য একটি Static Extension ক্লাস তৈরি করা হয়েছে। - Age Calculation:
DateOfBirthথেকে ইউজারের বয়স (Age) বের করার ম্যাথামেটিকাল ক্যালকুলেশন এই Extension মেথডের ভেতরেই করা হয়েছে।
🧠 Comprehensive Breakdown
এই লেকচারে আমরা DTO থেকে Domain Model-এ যাওয়া এবং আবার Domain Model থেকে রেসপন্স DTO-তে ফিরে আসার পুরো সাইকেলটি কোডের মাধ্যমে ইমপ্লিমেন্ট করেছি।
১. DTO থেকে Domain Model এ কনভার্ট করা (ToPerson) (Priority: 9/10)
Concept & Why: যখন Controller থেকে নতুন Person অ্যাড করার রিকোয়েস্ট আসবে, তখন সেটি PersonAddRequest (DTO) হিসেবে আসবে। কিন্তু ডাটাবেসে সেভ করার জন্য আমাদের এটিকে Person (Domain Model) এ কনভার্ট করতে হবে।
এর জন্য PersonAddRequest ক্লাসের ভেতরে ToPerson() নামে একটি মেথড লেখা হয়েছে। এখানে জেন্ডার enum কে .ToString() এর মাধ্যমে string এ কনভার্ট করা হয়েছে।
// PersonAddRequest.cs ক্লাসের ভেতরে
public Person ToPerson()
{
return new Person() {
PersonName = PersonName,
Email = Email,
DateOfBirth = DateOfBirth,
Gender = Gender.ToString(), // Enum থেকে String এ কনভার্ট
Address = Address,
CountryID = CountryID,
ReceiveNewsLetters = ReceiveNewsLetters
};
}
২. PersonResponse DTO তৈরি করা (Priority: 10/10)
Concept & Why: Service লেয়ার কখনোই মেইন Person (Entity) ক্লাস সরাসরি রিটার্ন করবে না। কারণ Entity ক্লাসে এমন অনেক ডেটা থাকতে পারে যা আমরা ইউজারকে দেখাতে চাই না।
আবার, ইউজারকে এমন কিছু ডেটা দেখাতে হতে পারে যা ডাটাবেসে নেই (যেমন ইউজারের বর্তমান বয়স বা Age)। তাই আমরা PersonResponse নামের একটি নতুন DTO ক্লাস তৈরি করেছি।
// DTO/PersonResponse.cs
public class PersonResponse
{
public Guid PersonID { get; set; }
public string? PersonName { get; set; }
public string? Email { get; set; }
public string? Gender { get; set; }
public Guid? CountryID { get; set; }
public string? Country { get; set; } // Country Name দেখানোর জন্য
public string? Address { get; set; }
public bool ReceiveNewsLetters { get; set; }
public double? Age { get; set; } // DateOfBirth থেকে ক্যালকুলেট করা হবে
}
৩. Unit Testing এর জন্য Equals ও GetHashCode Override (Priority: 10/10)
Concept & Why: আগের লেকচারগুলোর মতো এখানেও, xUnit-এ যখন আমরা Assert.Contains() দিয়ে চেক করব যে কোনো Person লিস্টে আছে কিনা, তখন মেমরি রেফারেন্সের বদলে যেন ভেতরের ডেটা (Values) কম্পেয়ার হয়, সেজন্য Equals মেথড Override করতে হবে।
public override bool Equals(object? obj)
{
if (obj == null) return false;
if (obj.GetType() != typeof(PersonResponse)) return false;
// Type casting
PersonResponse person_to_compare = (PersonResponse)obj;
// সব প্রপার্টি কম্পেয়ার করা (সংক্ষিপ্ত রূপ)
return this.PersonID == person_to_compare.PersonID &&
this.PersonName == person_to_compare.PersonName &&
this.Email == person_to_compare.Email; // একইভাবে বাকিগুলোও চেক করতে হবে
}
public override int GetHashCode()
{
return base.GetHashCode();
}
৪. Extension Method: ToPersonResponse() (Priority: 9/10)
Concept & Why: যখন ডাটাবেস থেকে আমরা ডেটা তুলব, তখন সেটি Person টাইপে আসবে। তাকে PersonResponse-এ কনভার্ট করতে হবে। কিন্তু আমরা Person (Entity) ক্লাসের ভেতর সরাসরি মেথড লিখতে চাই না (যাতে Entity ক্লাস ক্লিন থাকে)।
এজন্য আমরা C#-এর Extension Method ফিচার ব্যবহার করেছি। একটি static ক্লাস বানিয়ে this Person person প্যারামিটার দিলে সেটি Person ক্লাসের নিজস্ব মেথড হিসেবে ইনজেক্ট হয়ে যায়। এখানেই আমরা ইউজারের Age ক্যালকুলেট করেছি।
public static class PersonExtensions
{
public static PersonResponse ToPersonResponse(this Person person)
{
return new PersonResponse()
{
PersonID = person.PersonID,
PersonName = person.PersonName,
Email = person.Email,
Gender = person.Gender,
Address = person.Address,
CountryID = person.CountryID,
ReceiveNewsLetters = person.ReceiveNewsLetters,
// Age Calculation Logic
Age = (person.DateOfBirth != null) ?
Math.Round((DateTime.Now - person.DateOfBirth.Value).TotalDays / 365.25) : null
};
}
}
(VS / VS Code Shortcut Tip: কোনো ক্লাস বা নেমস্পেস খুঁজে না পেলে বা ইমপোর্ট করতে হলে কার্সর সেখানে রেখে Ctrl + . চাপলে Quick Actions মেনু আসবে, যেখান থেকে সহজেই Namespace ইমপোর্ট করা যায়।)
🚀 Modern C# (.NET 10) Updates & Smarter Approach
লেকচারের পদ্ধতিটি সঠিক হলেও, .NET 8, 9 বা 10 ব্যবহার করলে কোডকে আরও অনেক বেশি অপ্টিমাইজড এবং স্মার্ট করা যায়।
1. The Magic of record (No more manual Equals):
যেহেতু PersonResponse শুধুমাত্র ডেটা ক্যারি করার একটি DTO, তাই একে class এর বদলে record বানালে আমাদের কষ্ট করে ১০০ লাইনের Equals এবং GetHashCode মেথড লিখতে হবে না! C# কম্পাইলার নিজে থেকেই record এর জন্য Value Equality চেক তৈরি করে দেয়।
Modern Update Code:
// just write 'record' instead of 'class'
public record PersonResponse
{
public Guid PersonID { get; init; }
public string? PersonName { get; init; }
// ... other properties
public double? Age { get; init; }
}
// Now Assert.Contains() will automatically work perfectly!
2. Modern Age Calculation using TimeProvider (Testing friendly):
DateTime.Now ব্যবহার করলে Unit Testing এর সময় টাইম মক (Mock) করা কঠিন হয়। .NET 8+ এ TimeProvider নিয়ে আসা হয়েছে যা টাইম-রিলেটেড টেস্টিংকে অনেক সহজ করে।
🏆 Best Practices (DTOs and Mapping)
- Use Records for DTOs: DTO-এর জন্য সব সময়
record(C# 9+) ব্যবহার করুন। এটি Immutable এবং Value-based equality ডিফল্টভাবে সাপোর্ট করে। - Avoid Manual Mapping in Large Projects: লেকচারে আমরা ম্যানুয়ালি
ToPerson()এবংToPersonResponse()লিখে প্রপার্টি টু প্রপার্টি ম্যাপ করেছি। প্রজেক্ট বড় হলে এটি মেইনটেইন করা কঠিন। রিয়েল-ওয়ার্ল্ড প্রজেক্টে এই কাজের জন্য AutoMapper বা Mapster এর মতো থার্ড-পার্টি লাইব্রেরি ব্যবহার করা হয়। - Accurate Age Calculation:
(TotalDays / 365.25)ব্যবহার করাটা একটা শর্টকাট অ্যাপ্রোচ, যা সবসময় ১০০% সঠিক রেজাল্ট (যেমন লিপ ইয়ারের ক্ষেত্রে) নাও দিতে পারে। প্রোডাকশন লেভেলেDateTimeএর Year, Month, Day সাবট্রাক্ট করে সঠিক লজিক লেখা উচিত। তবে শেখার জন্য বর্তমান লজিকটি যথেষ্ট ভালো।