হ্যালো হাসিব! চমৎকার একটি টপিক। Excel ডাটা ইমপোর্ট বা আপলোড ফিচারটি রিয়েল-ওয়ার্ল্ড SaaS প্রোজেক্টের (যেমন তোমার Chatrabash) জন্য খুবই কমন এবং দরকারি একটি রিকোয়ারমেন্ট। ক্লায়েন্টকে যদি এক এক করে ডাটা এন্ট্রি করতে হয়, তবে সেটি অনেক বোরিং। Excel আপলোড সেই কাজকে অনেক সহজ করে দেয়।
ভবিষ্যতে রিভিশন দেওয়ার সুবিধার জন্য প্রথমে পুরো লেকচারের একটি শর্ট সামারি দিয়ে দিচ্ছি।
📝 Lecture Summary at a Glance
- File Reception: ASP.NET Core-এ ব্রাউজার থেকে ফাইল রিসিভ করার জন্য
IFormFileইন্টারফেস ব্যবহার করা হয়। - Memory Processing: ফিজিক্যাল হার্ডডিস্কে ফাইল সেভ না করে
MemoryStreamব্যবহার করে সরাসরি RAM-এ ফাইলটি প্রসেস করা হয়। - EPPlus Data Extraction:
ExcelPackageদিয়ে নির্দিষ্ট শিট (যেমন: “countries”) সিলেক্ট করে লুপের মাধ্যমে রো-বাই-রো (Row-by-Row) ডাটা রিড করা হয়। - Validation: Excel ফাইলের প্রথম রো (Row) সাধারণত হেডার থাকে, তাই লুপ ২ নম্বর রো থেকে শুরু হয়। খালি সেল (Empty Cell) এবং ডাটাবেসে ডুপ্লিকেট ভ্যালু ইগনোর করে শুধু নতুন ডাটা ইনসার্ট করা হয়।
🧠 Comprehensive Breakdown & Deep Dive
১. IFormFile এবং Service Interface [Importance: 9/10]
- The “Why”: যখন ক্লায়েন্ট (যেমন ফ্রন্টএন্ড থেকে) কোনো ফাইল আপলোড করে, তখন সেটি HTTP রিকোয়েস্টের অংশ হিসেবে আসে। ASP.NET Core-এ এই আপলোড হওয়া ফাইলকে রিপ্রেজেন্ট করার জন্য
IFormFileব্যবহার করা হয়। এটিMicrosoft.AspNetCore.Httpনেমস্পেসের অংশ।
💻 Code Implementation:
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
public interface ICountriesService
{
// ফাইল রিসিভ করে কয়টি ডাটা ইনসার্ট হলো সেই সংখ্যা (int) রিটার্ন করবে
Task<int> UploadCountriesFromExcelFile(IFormFile formFile);
}
২. MemoryStream-এ ফাইল কনভার্সন [Importance: 10/10]
- The “Why”: আপলোড হওয়া ফাইলটি সরাসরি সার্ভারের হার্ডডিস্কে সেভ করাটা ব্যাড প্র্যাকটিস (যদি না ফাইলটি পার্মানেন্টলি রাখার দরকার হয়)। যেহেতু আমরা শুধু এক্সেল ফাইলের ভেতরের টেক্সট ডাটা চাই, তাই
CopyToAsyncব্যবহার করে ফাইলটিকেMemoryStream(RAM) এ নিয়ে আসা হয়। এতে প্রসেসিং অনেক ফাস্ট হয়।
৩. EPPlus দিয়ে Excel Data Read করা [Importance: 10/10]
- The “Why”:
MemoryStreamএ থাকা বাইনারি ডাটাকে মানুষের বোধগম্য এক্সেল শিটে রূপান্তর করার জন্যExcelPackageব্যবহার করা হয়। - Row count & Loop:
Worksheet.Dimension.Rowsপ্রপার্টি দিয়ে জানা যায় এক্সেলে কতগুলো রো-তে ডাটা আছে। যেহেতু প্রথম রো-টি হেডার (যেমন: “Country Name”), তাই লুপ সবসময়row = 2থেকে শুরু হয়।
৪. Duplicate Check এবং EF Core Insert [Importance: 10/10]
- The “Why”: ইউজারের দেওয়া এক্সেল ফাইলে এমন ডাটা থাকতে পারে যা ডাটাবেসে আগে থেকেই আছে। তাই ইনসার্ট করার আগে EF Core দিয়ে চেক করা হয় যে ওই নামের কোনো Country অলরেডি আছে কিনা।
🚀 Modern .NET 10 & Architecture Best Practices (Critical Fixes)
হাসিব, লেকচারার কনসেপ্ট বোঝানোর জন্য যে কোডটি লিখেছেন, তাতে একটি মারাত্মক Performance Flaw (পারফরম্যান্স সমস্যা) আছে, যা প্রোডাকশন লেভেল অ্যাপ্লিকেশনে ক্র্যাশ বা চরম স্লোনেসের কারণ হতে পারে। নিচে আমি আধুনিক C# 13 এবং .NET 10 স্ট্যান্ডার্ড অনুযায়ী এর অপটিমাইজড সল্যুশন দিচ্ছি:
১. The N+1 Query Problem (Loop এর ভেতর Database Call):
লেকচারার for লুপের ভেতরে ডাটাবেস চেক করেছেন (_db.Countries.Where...) এবং লুপের ভেতরেই SaveChangesAsync কল করেছেন। এর মানে হলো, এক্সেল ফাইলে যদি ৫,০০০ রো থাকে, তবে ডাটাবেসে ৫,০০০ বার SELECT কুয়েরি এবং ৫,০০০ বার INSERT কুয়েরি ফায়ার হবে! এটি ডাটাবেস সার্ভারকে বসিয়ে দেবে।
✅ The Smart Solution (Bulk Operation):
লুপ চালানোর আগেই ডাটাবেস থেকে এক্সিস্টিং ডাটাগুলো মেমোরিতে (HashSet) নিয়ে আসতে হবে এবং নতুন ডাটাগুলো একটি লিস্টে জমিয়ে লুপের বাইরে একবারে সেভ করতে হবে (AddRangeAsync)।
💻 Optimized Code Implementation:
public async Task<int> UploadCountriesFromExcelFile(IFormFile formFile)
{
if (formFile == null || formFile.Length == 0) return 0;
int countriesInserted = 0;
var newCountriesToInsert = new List<Country>();
// ১. Data Validation: আগেই এক্সিস্টিং ডাটাবেসের নামগুলো একটি HashSet এ নিয়ে আসা (Fast Lookup)
var existingCountries = new HashSet<string>(
await _db.Countries.Select(c => c.CountryName).ToListAsync(),
StringComparer.OrdinalIgnoreCase // Case-insensitive চেকিংয়ের জন্য
);
using (var memoryStream = new MemoryStream())
{
await formFile.CopyToAsync(memoryStream);
using (var excelPackage = new ExcelPackage(memoryStream))
{
var worksheet = excelPackage.Workbook.Worksheets["Countries"];
if (worksheet == null) return 0; // শিট না পেলে রিটার্ন
int rowCount = worksheet.Dimension.Rows;
// ২. লুপের ভেতরে কোনো ডাটাবেস কল নেই!
for (int row = 2; row <= rowCount; row++)
{
string? cellValue = worksheet.Cells[row, 1].Value?.ToString()?.Trim();
if (!string.IsNullOrEmpty(cellValue))
{
// HashSet থেকে চেক করা (O(1) টাইম কমপ্লেক্সিটি)
if (!existingCountries.Contains(cellValue))
{
var newCountry = new Country
{
CountryId = Guid.NewGuid(),
CountryName = cellValue
};
newCountriesToInsert.Add(newCountry);
existingCountries.Add(cellValue); // ডুপ্লিকেট এড়াতে লোকাল সেটেও অ্যাড করা
countriesInserted++;
}
}
}
}
}
// ৩. লুপের বাইরে একবারে Bulk Insert এবং Save
if (newCountriesToInsert.Any())
{
await _db.Countries.AddRangeAsync(newCountriesToInsert);
await _db.SaveChangesAsync(); // ডাটাবেসে মাত্র ১ বার হিট হবে!
}
return countriesInserted;
}
২. IDisposable Resource Management:
MemoryStream এবং ExcelPackage আনম্যানেজড রিসোর্স ব্যবহার করে। মেমোরি লিক এড়াতে আধুনিক C#-এ using ডিক্লারেশন ব্যবহার করা ম্যান্ডেটরি (যা আমি উপরের কোডে দেখিয়েছি)।
এই অপটিমাইজড অ্যাপ্রোচটি ব্যবহার করলে তোমার অ্যাপ্লিকেশন ৫ লাইনের এক্সেল বা ৫০ হাজার লাইনের এক্সেল—সবই মিলি-সেকেন্ডে প্রসেস করে ফেলবে!
লেকচারার দ্বিতীয় পার্টে Controller থেকে কীভাবে এই মেথড কল করতে হয় তা দেখাবেন বলেছেন। তুমি রেডি হলে সেই ট্রান্সক্রিপ্টটি দিতে পারো!