হ্যালো হাসিব! এই লেকচারটি EF Core-এর সবচেয়ে গুরুত্বপূর্ণ এবং রিয়েল-ওয়ার্ল্ড প্রজেক্টে সবচেয়ে বেশি ব্যবহৃত একটি টপিক নিয়ে—Table Relations, Foreign Keys, এবং Navigation Properties।
ডাটাবেসে আমরা সাধারণত JOIN কুয়েরি ব্যবহার করে একাধিক টেবিল থেকে ডাটা একসাথে নিয়ে আসি। কিন্তু EF Core-এ ഈ কাজটি অনেক স্মার্ট এবং অবজেক্ট-ওরিয়েন্টেড উপায়ে করা যায়।
প্রথমে তোমার রিভিশনের সুবিধার্থে পুরো লেকচারের একটি কুইক সামারি দিয়ে দিচ্ছি:
📝 Lecture Summary at a Glance
- Referential Integrity: Master টেবিল (যেমন:
Countries) এবং Detail/Child টেবিলের (যেমন:Persons) মধ্যে Foreign Key দিয়ে লজিক্যাল সম্পর্ক তৈরি করা। - Navigation Properties: C# Model Class-এ রিলেটেড টেবিলের ডাটা সরাসরি অবজেক্ট বা কালেকশন হিসেবে পাওয়ার সুবিধা, যার ফলে ম্যানুয়ালি SQL JOIN লিখতে হয় না।
- Eager Loading (
Include): পারফরম্যান্স বাঁচানোর জন্য Navigation Property-র ডাটা ডিফল্টভাবেnullথাকে। ডাটাবেস থেকে রিলেটেড ডাটা ফেচ করার জন্য LINQ-এ.Include()মেথড ব্যবহার করতে হয়। - Fluent API Relations: Data Annotations ছাড়াও
OnModelCreating-এ Fluent API দিয়ে টেবিল রিলেশনশিপ ডিফাইন করা যায়। - Null-conditional Operator (
?.): রিলেটেড ডাটাnullথাকলে যেন অ্যাপ্লিকেশন ক্র্যাশ না করে, সেজন্য DTO ম্যাপিংয়ে?.ব্যবহার করে রিফ্যাক্টর করা হয়েছে।
🧠 Comprehensive Breakdown & Deep Dive
১. Navigation Properties এবং Foreign Key [Importance: 10/10]
- The “Why”: SQL-এ দুটি টেবিলের ডাটা একসাথে আনতে
INNER JOINলিখতে হয়। কিন্তু C#-এ আমরা অবজেক্ট নিয়ে কাজ করি। EF Core-এ Navigation Property ব্যবহার করলে তুমি জাস্টPerson.Country.CountryNameলিখে রিলেটেড ডাটা পেয়ে যাবে, EF Core ব্যাকএন্ডে নিজেই JOIN কুয়েরি জেনারেট করে নেবে! - Parent-Child Relationship: এখানে
Countryহলো Master (Parent) এবংPersonহলো Detail (Child)।
💻 Code Implementation: Model Class-গুলোতে Navigation Property এভাবে ডিফাইন করতে হয়:
// Child Class
public class Person
{
public Guid PersonId { get; set; }
public string PersonName { get; set; }
// Foreign Key Column
public Guid CountryId { get; set; }
// Navigation Property (Parent Object)
[ForeignKey("CountryId")]
public virtual Country? Country { get; set; }
}
// Parent/Master Class
public class Country
{
public Guid CountryId { get; set; }
public string CountryName { get; set; }
// Navigation Property (Collection of Children)
public virtual ICollection<Person>? Persons { get; set; }
}
(নোট: প্রপার্টির আগে virtual কিওয়ার্ড দিলে EF Core ভবিষ্যতে চাইলে Lazy Loading সাপোর্ট দিতে পারে, তবে এটি ম্যান্ডেটরি নয়)।
২. Eager Loading using .Include() [Importance: 10/10]
- The “Why”: তুমি যখন
_db.Persons.ToList()কল করো, EF Core শুধুPersonsটেবিলের ডাটাই আনে। পারফরম্যান্স সেভ করার জন্য সে নিজ থেকেCountriesটেবিলের ডাটা আনে না (Navigation propertynullথাকে)। যখন তোমার রিলেটেড ডাটা দরকার হবে, তখন তোমাকে explicitly.Include()কল করে Eager Loading করতে হবে।
💻 Code Implementation:
using Microsoft.EntityFrameworkCore; // Include() ব্যবহার করার জন্য এটি মাস্ট
public List<Person> GetAllPersons()
{
// Include ব্যবহার করলে EF Core ইন্টারনালি SQL JOIN তৈরি করে ডাটা আনবে
return _db.Persons
.Include(p => p.Country)
.ToList();
}
৩. Fluent API দিয়ে Relationship কনফিগারেশন [Importance: 7/10]
- The “Why”: Model Class-এর ভেতরে
[ForeignKey]না লিখে Clean Architecture মেইনটেইন করার জন্য Fluent API দিয়ে রিলেশনশিপ ডিফাইন করাটা বেশি প্রফেশনাল। লেকচারার এটি সংক্ষেপে দেখিয়েছেন।
💻 Modern Fluent API Approach:
PersonsDbContext ক্লাসের OnModelCreating মেথডে নিচের মতো করে One-to-Many রিলেশনশিপ লিখতে হয়:
modelBuilder.Entity<Person>()
.HasOne(p => p.Country) // একজন Person-এর একটি Country আছে
.WithMany(c => c.Persons) // একটি Country-র আন্ডারে অনেক Person থাকতে পারে
.HasForeignKey(p => p.CountryId); // Foreign Key কলামের নাম
৪. Refactoring DTO & The Null-conditional Operator (?.) [Importance: 8/10]
- The “Why”: যদি কোনো Person-এর ডাটাবেসে
CountryIdনা থাকে (বা Country ডিলিট হয়ে যায়), তবেPerson.Countryএর ভ্যালুnullহবে। তখনPerson.Country.CountryNameকল করলে NullReferenceException খেয়ে অ্যাপ ক্র্যাশ করবে। এটি এড়ানোর জন্য Null-conditional Operator (?.) ব্যবহার করা হয়।
💻 Code Implementation (Extension Method Refactoring): লেকচারার সার্ভিস ক্লাস থেকে ম্যাপিং লজিক সরিয়ে একটি Extension Method বানিয়েছেন, যা অত্যন্ত স্মার্ট কাজ।
public static class PersonExtensions
{
public static PersonResponse ToPersonResponse(this Person person)
{
return new PersonResponse
{
PersonId = person.PersonId,
PersonName = person.PersonName,
// '?. ' চেক করবে Country null কিনা, null হলে CountryName এ null বসিয়ে দেবে, ক্র্যাশ করবে না।
CountryName = person.Country?.CountryName
};
}
}
🚀 Modern .NET Updates & Best Practices
যেহেতু তুমি .NET ইকোসিস্টেমে প্রোডাকশন-লেভেল কাজ করবে, তাই Navigation Properties নিয়ে নিচের বেস্ট প্র্যাকটিসগুলো সবসময় মাথায় রাখবে:
১. Avoid Include() if you are using .Select() (Projection):
এটি পারফরম্যান্স অপটিমাইজেশনের সবচেয়ে বড় হ্যাক! যদি তোমার শুধু CountryName দরকার হয়, তবে .Include() করে পুরো Country অবজেক্ট মেমোরিতে আনার কোনো দরকার নেই। সরাসরি .Select() ব্যবহার করলে EF Core শুধু ওই স্পেসিফিক কলামটিই SQL কুয়েরিতে ফায়ার করবে।
// ✅ Smart & Fastest Way (No Include required!):
var response = await _db.Persons
.Select(p => new PersonResponse
{
PersonId = p.PersonId,
PersonName = p.PersonName,
CountryName = p.Country.CountryName // EF Core নিজে থেকেই স্মার্ট JOIN জেনারেট করে শুধু নামটা আনবে
})
.ToListAsync();
২. Beware of Lazy Loading (The N+1 Query Problem):
লেকচারার virtual কিওয়ার্ড ইউজ করার কথা বলেছেন যা Lazy Loading এনাবল করতে পারে। রিয়েল ওয়ার্ল্ডে Lazy Loading চরম মাত্রায় পারফরম্যান্স ড্রপ করে (লুপের ভেতর বারবার ডাটাবেস কল হয়)। তাই সবসময় Include (Eager Loading) বা Select (Projection) ব্যবহার করাটাই ইন্ডাস্ট্রির নিয়ম।
৩. Multiple Includes and .AsSplitQuery() (.NET 8/10 Feature):
যখন তোমার Parent ক্লাসের সাথে একাধিক ICollection (List) Include করতে হয়, তখন SQL সার্ভারে ডাটা মাল্টিপ্লাই (Cartesian Explosion) হয়ে যায়। আধুনিক .NET-এ এই সমস্যা সমাধানের জন্য কুয়েরির শেষে .AsSplitQuery() কল করা বেস্ট প্র্যাকটিস।
var countries = await _db.Countries
.Include(c => c.Persons)
.AsSplitQuery() // এটি ডাটাবেসে একাধিক ছোট কুয়েরি পাঠাবে, যা বিশাল একটি JOIN-এর চেয়ে অনেক ফাস্ট।
.ToListAsync();
এই লেকচারের পর তোমার EF Core-এর বেসিক এবং অ্যাডভান্সড কুয়েরিং কনসেপ্ট পুরোপুরি রেডি। তুমি চাইলে পরবর্তী লেকচারের ট্রান্সক্রিপ্ট দিতে পারো!