این پروژه بخشی از .NET Foundation است و تحت قوانین رفتار حرفهای آن فعالیت میکند.
English | 简体中文 | 繁體中文 | 日本語 | 한국어 | हिन्दी | ไทย | Français | Deutsch | Español | Italiano | Русский | Português | Nederlands | Polski | العربية | فارسی | Türkçe | Tiếng Việt | Bahasa Indonesia
معرفی
MiniExcel یک ابزار ساده و کارآمد برای پردازش اکسل در .NET است که به طور خاص برای کاهش مصرف حافظه طراحی شده است.
در حال حاضر، بیشتر فریمورکهای محبوب نیاز دارند تمام دادههای یک سند اکسل را برای انجام عملیاتها به طور کامل در حافظه بارگذاری کنند، اما این کار میتواند مشکلاتی در مصرف حافظه ایجاد کند. رویکرد MiniExcel متفاوت است: دادهها به صورت ردیف به ردیف و به شکل جریانی پردازش میشوند، که مصرف اولیه را از صدها مگابایت بالقوه به تنها چند مگابایت کاهش میدهد و به طور مؤثری از بروز مشکلات Out-of-memory (OOM) جلوگیری میکند.
flowchart LR
A1(["Excel analysis
process"]) --> A2{{"Unzipping
XLSX file"}} --> A3{{"Parsing
OpenXML"}} --> A4{{"Model
conversion"}} --> A5(["Output"]) B1(["Other Excel
Frameworks"]) --> B2{{"Memory"}} --> B3{{"Memory"}} --> B4{{"Workbooks &
Worksheets"}} --> B5(["All rows at
the same time"])
C1(["MiniExcel"]) --> C2{{"Stream"}} --> C3{{"Stream"}} --> C4{{"POCO or dynamic"}} --> C5(["Deferred execution
row by row"])
classDef analysis fill:#D0E8FF,stroke:#1E88E5,color:#0D47A1,font-weight:bold;
classDef others fill:#FCE4EC,stroke:#EC407A,color:#880E4F,font-weight:bold;
classDef miniexcel fill:#E8F5E9,stroke:#388E3C,color:#1B5E20,font-weight:bold;
class A1,A2,A3,A4,A5 analysis;
class B1,B2,B3,B4,B5 others;
class C1,C2,C3,C4,C5 miniexcel;
ویژگیها
- کمینهسازی مصرف حافظه، جلوگیری از خطاهای کمبود حافظه (OOM) و اجتناب از جمعآوری کامل زبالهها
- امکان انجام عملیات دادهای در سطح ردیف به صورت بلادرنگ برای عملکرد بهتر روی دادههای بزرگ
- پشتیبانی از LINQ با اجرای به تعویق افتاده، امکان صفحهبندی سریع و کارآمد از نظر حافظه و پرسوجوهای پیچیده
- سبک و کمحجم، بدون نیاز به Microsoft Office یا اجزای COM+، و اندازه DLL کمتر از ۵۰۰ کیلوبایت
- رابط برنامهنویسی ساده و شهودی برای خواندن/نوشتن/پر کردن اکسل
نسخه ۲.۰ پیشنمایش
ما در حال کار روی نسخه آینده MiniExcel با یک API ماژولار و متمرکز جدید،
بستههای جداگانه nuget برای قابلیتهای Core و Csv، پشتیبانی کامل از پرسوجوهای جریانی ناهمگام از طریق IAsyncEnumerable
و امکانات بیشتر در آینده هستیم! این بستهها به صورت پیشانتشار در دسترس خواهند بود، پس خوشحال میشویم که آنها را بررسی کرده و بازخورد خود را ارائه دهید!
اگر این کار را کردید، حتما مستندات جدید و یادداشتهای ارتقاء را نیز مشاهده کنید.
شروع کنید
- وارد کردن/پرسوجوی اکسل
- خروجی گرفتن/ایجاد اکسل
- قالب اکسل
- ویژگی نام/ایندکس/نادیده گرفتن ستون اکسل
- نمونهها
نصب
شما میتوانید بسته را از NuGet نصب کنید
یادداشتهای انتشار
لطفاً یادداشتهای انتشار را بررسی کنید
TODO
لطفاً TODO را بررسی کنید
عملکرد
کد مربوط به بنچمارکها را میتوانید در MiniExcel.Benchmarks پیدا کنید.
فایلی که برای تست عملکرد استفاده شده است Test1,000,000x10.xlsx است؛ یک سند ۳۲ مگابایتی شامل ۱,۰۰۰,۰۰۰ ردیف * ۱۰ ستون که سلولهای آن با رشته "HelloWorld" پر شدهاند.
برای اجرای تمام بنچمارکها از دستور زیر استفاده کنید:
dotnet run -project .\benchmarks\MiniExcel.Benchmarks -c Release -f net9.0 -filter * --join
شما میتوانید نتایج بنچمارک نسخه جدید را اینجا پیدا کنید.پرسوجو/وارد کردن اکسل
#### 1. یک پرسوجو اجرا کنید و نتایج را به یک IEnumerable با نوع قوی نگاشت کنید [[امتحان کنید]](https://dotnetfiddle.net/w5WD1J)
توصیه میشود به دلیل کارایی بهتر از Stream.Query استفاده کنید.
public class UserAccount
{
public Guid ID { get; set; }
public string Name { get; set; }
public DateTime BoD { get; set; }
public int Age { get; set; }
public bool VIP { get; set; }
public decimal Points { get; set; }
}var rows = MiniExcel.Query(path);
// or
using (var stream = File.OpenRead(path))
var rows = stream.Query();

#### 2. اجرای یک کوئری و نگاشت آن به لیستی از اشیاء داینامیک بدون استفاده از head [[امتحان کنید]](https://dotnetfiddle.net/w5WD1J)
- کلید داینامیک
A.B.C.D..است
var rows = MiniExcel.Query(path).ToList();// or
using (var stream = File.OpenRead(path))
{
var rows = stream.Query().ToList();
Assert.Equal("MiniExcel", rows[0].A);
Assert.Equal(1, rows[0].B);
Assert.Equal("Github", rows[1].A);
Assert.Equal(2, rows[1].B);
}
#### 3. اجرای یک پرس و جو با ردیف اول به عنوان هدر [[امتحان کنید]](https://dotnetfiddle.net/w5WD1J)توجه : در صورت وجود نام ستون تکراری، آخرین ستون از سمت راست استفاده میشود
ورودی اکسل :
| Column1 | Column2 | |-----------|---------| | MiniExcel | 1 | | Github | 2 |
var rows = MiniExcel.Query(useHeaderRow:true).ToList();// or
using (var stream = File.OpenRead(path))
{
var rows = stream.Query(useHeaderRow:true).ToList();
Assert.Equal("MiniExcel", rows[0].Column1);
Assert.Equal(1, rows[0].Column2);
Assert.Equal("Github", rows[1].Column1);
Assert.Equal(2, rows[1].Column2);
}
#### 4. پشتیبانی از کوئری افزونه LINQ مانند First/Take/Skip و غیرهکوئری First
var row = MiniExcel.Query(path).First();
Assert.Equal("HelloWorld", row.A);// or
using (var stream = File.OpenRead(path))
{
var row = stream.Query().First();
Assert.Equal("HelloWorld", row.A);
}
عملکرد بین MiniExcel/ExcelDataReader/ClosedXML/EPPlus

#### ۵. جستجو بر اساس نام شیت
MiniExcel.Query(path, sheetName: "SheetName");
//or
stream.Query(sheetName: "SheetName");
#### 6. پرس و جوی تمام نام شیتها و ردیفهاvar sheetNames = MiniExcel.GetSheetNames(path);
foreach (var sheetName in sheetNames)
{
var rows = MiniExcel.Query(path, sheetName: sheetName);
}
#### ۷. دریافت ستونهاvar columns = MiniExcel.GetColumns(path); // e.g result : ["A","B"...]var cnt = columns.Count; // get column count
#### 8. کوئری داینامیک ردیف را به IDictionary تبدیل میکندforeach(IDictionary row in MiniExcel.Query(path))
{
//..
}// or
var rows = MiniExcel.Query(path).Cast>();
// or Query specified ranges (capitalized)
// A2 represents the second row of column A, C3 represents the third row of column C
// If you don't want to restrict rows, just don't include numbers
var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3").Cast>();
#### 9. کوئری اکسل و بازگشت DataTable
توصیه نمیشود، زیرا DataTable تمام دادهها را به حافظه بارگذاری میکند و ویژگی مصرف پایین حافظه MiniExcel را از بین میبرد.
``C#
var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true);

#### 10. مشخص کردن سلولی که خواندن داده از آن آغاز میشود
csharp MiniExcel.Query(path,useHeaderRow:true,startCell:"B3")

#### 11. پر کردن سلولهای ادغامشده
توجه: کارایی نسبت به «استفاده نکردن از پر کردن ادغامی» کندتر است
دلیل: استاندارد OpenXml بخش mergeCells را در انتهای فایل قرار میدهد که باعث نیاز به دو بار پیمایش sheetxml میشود
csharp
var config = new OpenXmlConfiguration()
{
FillMergedCells = true
};
var rows = MiniExcel.Query(path, configuration: config);

پشتیبانی از پر کردن چند سطری و ستونی با طول و عرض متغیر

#### ۱۲. خواندن فایلهای بزرگ با کش مبتنی بر دیسک (Disk-Base Cache - SharedString)
اگر اندازه SharedStrings از ۵ مگابایت بیشتر شود، MiniExcel به صورت پیشفرض از کش دیسک محلی استفاده میکند، برای مثال، 10x100000.xlsx (یک میلیون ردیف داده)، زمانی که کش دیسک غیرفعال باشد، حداکثر مصرف حافظه ۱۹۵ مگابایت است، اما با فعال بودن کش دیسک فقط به ۶۵ مگابایت نیاز دارد. توجه داشته باشید که این بهینهسازی هزینههایی در کارایی دارد، بنابراین در این حالت زمان خواندن از ۷.۴ ثانیه به ۲۷.۲ ثانیه افزایش مییابد. اگر به این ویژگی نیاز ندارید، میتوانید کش دیسک را با کد زیر غیرفعال کنید:
csharp
var config = new OpenXmlConfiguration { EnableSharedStringCache = false };
MiniExcel.Query(path,configuration: config)
csharp var config = new OpenXmlConfiguration { SharedStringCacheSize=50010241024 }; MiniExcel.Query(path, configuration: config);شما میتوانید ازSharedStringCacheSizeبرای تغییر اندازه فایل sharedString فراتر از اندازه مشخص شده برای کش کردن روی دیسک استفاده کنید.


ساخت/خروجی گرفتن از اکسل
- باید یک نوع غیرانتزاعی با سازنده عمومی بدون پارامتر باشد.
- MiniExcel از اجرای تاخیری IEnumerable پشتیبانی میکند، اگر میخواهید کمترین میزان حافظه را مصرف کنید، لطفاً از متدهایی مانند ToList استفاده نکنید.
مثال: استفاده یا عدم استفاده از ToList و مصرف حافظه

#### ۱. ناشناس یا نوع قوی [[امتحان کنید]](https://dotnetfiddle.net/w5WD1J)
csharp
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
MiniExcel.SaveAs(path, new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
});
csharp var values = new List#### 2.IEnumerable>
csharp MiniExcel.SaveAs(path, reader);ایجاد نتیجه فایل :توصیه میشود| ستون1 | ستون2 | |-----------|---------| | MiniExcel | 1 | | Github | 2 |
#### 3. IDataReader
، میتواند از بارگذاری همه دادهها در حافظه جلوگیری کند

خروجی گرفتن چندین شیت توسط DataReader (توصیه شده توسط Dapper ExecuteReader)
csharp
using (var cnn = Connection)
{
cnn.Open();
var sheets = new Dictionarycsharp var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); var table = new DataTable(); { table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column2", typeof(decimal)); table.Rows.Add("MiniExcel", 1); table.Rows.Add("Github", 2); }توصیه نمیشود#### 4. دیتاتیبل
، زیرا تمام دادهها را به حافظه بارگذاری میکنددیتاتیبل ابتدا از Caption برای نام ستون استفاده میکند، سپس از columname
MiniExcel.SaveAs(path, table);
csharp using (var connection = GetConnection(connectionString)) { var rows = connection.Query( new CommandDefinition( @"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2" , flags: CommandFlags.NoCache) ); // Note: QueryAsync will throw close connection exception MiniExcel.SaveAs(path, rows); }#### 5. پرسوجوی DapperCommandDefinition + CommandFlags.NoCacheبا تشکر از @shaofing #552 ، لطفاً از
استفاده کنید.
کد زیر تمام دادهها را به حافظه بارگذاری میکندcsharp
using (var connection = GetConnection(connectionString))
{
var rows = connection.Query(@"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2");
MiniExcel.SaveAs(path, rows);
}
#### 6. ذخیره به MemoryStream [[امتحان کنید]](https://dotnetfiddle.net/JOen0e)
csharp
using (var stream = new MemoryStream()) //support FileStream,MemoryStream ect.
{
stream.SaveAs(values);
}
به عنوان مثال: API برای خروجی گرفتن اکسلcsharp
public IActionResult DownloadExcel()
{
var values = new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
};var memoryStream = new MemoryStream(); memoryStream.SaveAs(values); memoryStream.Seek(0, SeekOrigin.Begin); return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { FileDownloadName = "demo.xlsx" }; }
#### 7. ایجاد چندین شیت
csharp
// 1. Dictionary// 2. DataSet var sheets = new DataSet(); sheets.Add(UsersDataTable); sheets.Add(DepartmentDataTable); //.. MiniExcel.SaveAs(path, sheets);

#### ۸. گزینههای TableStyles
استایل پیشفرض

بدون پیکربندی استایل
csharp
var config = new OpenXmlConfiguration()
{
TableStyles = TableStyles.None
};
MiniExcel.SaveAs(path, value,configuration:config);
csharp MiniExcel.SaveAs(path, value, configuration: new OpenXmlConfiguration() { AutoFilter = false });OpenXmlConfiguration.AutoFilter#### 9. فیلتر خودکار (AutoFilter)
از نسخه v0.19.0، با استفاده از
میتوان فیلتر خودکار را فعال یا غیرفعال کرد، مقدار پیشفرضtrueاست، و روش تنظیم AutoFilter به شرح زیر است:
#### 10. ایجاد تصویر
csharp
var value = new[] {
new { Name="github",Image=File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png"))},
new { Name="google",Image=File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png"))},
new { Name="microsoft",Image=File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png"))},
new { Name="reddit",Image=File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png"))},
new { Name="statck_overflow",Image=File.ReadAllBytes(PathHelper.GetFile("images/statck_overflow_logo.png"))},
};
MiniExcel.SaveAs(path, value);
csharp var mergedFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx");byte[]#### 11. خروجی فایل آرایه بایت
از نسخه 1.22.0، زمانی که نوع مقدار
باشد، سیستم به طور پیشفرض مسیر فایل را در سلول ذخیره میکند و هنگام وارد کردن میتواند بهbyte[]تبدیل شود. و اگر نمیخواهید از این قابلیت استفاده کنید، میتوانید مقدارOpenXmlConfiguration.EnableConvertByteArrayرا بهfalseتنظیم کنید که باعث افزایش کارایی سیستم میشود.byte[]
از نسخه 1.22.0، زمانی که نوع مقدار
باشد، سیستم به طور پیشفرض مسیر فایل را در سلول ذخیره میکند و هنگام وارد کردن میتواند بهbyte[]تبدیل شود. و اگر نمیخواهید از این قابلیت استفاده کنید، میتوانید مقدارOpenXmlConfiguration.EnableConvertByteArrayرا بهfalseتنظیم کنید که باعث افزایش کارایی سیستم میشود.xlsx
#### 12. ادغام سلولهای مشابه به صورت عمودی
این قابلیت فقط در فرمت
پشتیبانی میشود و سلولها را به صورت عمودی بین تگهای @merge و @endmerge ادغام میکند. شما میتوانید از @mergelimit برای محدود کردن محدوده ادغام سلولها به صورت عمودی استفاده کنید.
var path = @"../../../../../samples/xlsx/TestMergeWithTag.xlsx";
MiniExcel.MergeSameCells(mergedFilePath, path);
csharp
var memoryStream = new MemoryStream();var path = @"../../../../../samples/xlsx/TestMergeWithTag.xlsx";
memoryStream.MergeSameCells(path);
محتوای فایل قبل و بعد از ادغام:بدون محدودیت ادغام:


با محدودیت ادغام:


#### ۱۳. رد کردن مقادیر تهی
گزینه جدید و صریح برای نوشتن سلولهای خالی برای مقادیر null:
csharp
DataTable dt = new DataTable();/ ... /
DataRow dr = dt.NewRow();
dr["Name1"] = "Somebody once"; dr["Name2"] = null; dr["Name3"] = "told me.";
dt.Rows.Add(dr);
OpenXmlConfiguration configuration = new OpenXmlConfiguration() { EnableWriteNullValueCell = true // Default value. };
MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);

xml
رفتار قبلی:csharp
/ ... /OpenXmlConfiguration configuration = new OpenXmlConfiguration() { EnableWriteNullValueCell = false // Default value is true. };
MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);

xml
برای مقادیر null و DBNull کار میکند.#### 14. ثابت کردن پنجرهها (Freeze Panes)
csharp
/ ... /OpenXmlConfiguration configuration = new OpenXmlConfiguration() { FreezeRowCount = 1, // default is 1 FreezeColumnCount = 2 // default is 0 };
MiniExcel.SaveAs(@"C:\temp\Book1.xlsx", dt, configuration: configuration);
csharp // 1. By POCO var value = new { Name = "Jack", CreateDate = new DateTime(2021, 01, 01), VIP = true, Points = 123 }; MiniExcel.SaveAsByTemplate(path, templatePath, value);{{نام متغیر}}پر کردن داده در قالب اکسل
- اعلان متغیر مشابه قالب Vue با
است، یا رندر مجموعه با{{نام مجموعه.نام فیلد}}رندر مجموعه از انواع IEnumerable/DataTable/DapperRow پشتیبانی میکند #### 1. پر کردن پایه
قالب:
نتیجه:
کد:
// 2. By Dictionary
var value = new Dictionary#### 2. پر کردن داده با IEnumerable
نکته1: از اولین IEnumerable از همان ستون به عنوان مبنای پر کردن لیست استفاده کنید
قالب:

نتیجه:

کد:
csharp //1. By POCO var value = new { employees = new[] { new {name="Jack",department="HR"}, new {name="Lisa",department="HR"}, new {name="John",department="HR"}, new {name="Mike",department="IT"}, new {name="Neo",department="IT"}, new {name="Loan",department="IT"} } }; MiniExcel.SaveAsByTemplate(path, templatePath, value);
//2. By Dictionary
var value = new Dictionary#### 3. پرکردن دادههای پیچیده
توجه: پشتیبانی از چندین شیت و استفاده از متغیر یکسان
قالب:

نتیجه:

csharp // 1. By POCO var value = new { title = "FooCompany", managers = new[] { new {name="Jack",department="HR"}, new {name="Loan",department="IT"} }, employees = new[] { new {name="Wade",department="HR"}, new {name="Felix",department="HR"}, new {name="Eric",department="IT"}, new {name="Keaton",department="IT"} } }; MiniExcel.SaveAsByTemplate(path, templatePath, value);
// 2. By Dictionary
var value = new Dictionary#### ۴. عملکرد پر کردن دادههای بزرگ
توجه: استفاده از اجرای تعلیقی IEnumerable به جای ToList میتواند حداکثر صرفهجویی در حافظه را در MiniExcel داشته باشد

#### ۵. نگاشت خودکار نوع مقدار سلول
قالب

نتیجه

کلاس
csharp public class Poco { public string @string { get; set; } public int? @int { get; set; } public decimal? @decimal { get; set; } public double? @double { get; set; } public DateTime? datetime { get; set; } public bool? @bool { get; set; } public Guid? Guid { get; set; } }
کدcsharp
var poco = new TestIEnumerableTypePoco { @string = "string", @int = 123, @decimal = decimal.Parse("123.45"), @double = (double)123.33, @datetime = new DateTime(2021, 4, 1), @bool = true, @Guid = Guid.NewGuid() };
var value = new
{
Ts = new[] {
poco,
new TestIEnumerableTypePoco{},
null,
poco
}
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
#### 6. مثال : فهرست پروژههای گیتهابقالب

نتیجه

کد
csharp
var projects = new[]
{
new {Name = "MiniExcel",Link="https://github.com/mini-software/MiniExcel",Star=146, CreateTime=new DateTime(2021,03,01)},
new {Name = "HtmlTableHelper",Link="https://github.com/mini-software/HtmlTableHelper",Star=16, CreateTime=new DateTime(2020,02,01)},
new {Name = "PocoClassGenerator",Link="https://github.com/mini-software/PocoClassGenerator",Star=16, CreateTime=new DateTime(2019,03,17)}
};
var value = new
{
User = "ITWeiHan",
Projects = projects,
TotalStar = projects.Sum(s => s.Star)
};
MiniExcel.SaveAsByTemplate(path, templatePath, value);
#### 7. پر کردن دادههای گروهبندیشدهcsharp
var value = new Dictionarycsharp @if(name == Jack) {{employees.name}} @elseif(name == Neo) Test {{employees.name}} @else {{employees.department}} @endif##### 1. با تگ@groupو با تگ@headerقبل
بعد
##### 2. با تگ @group و بدون تگ @header
قبل
بعد
##### 3. بدون تگ @group
قبل
بعد
#### 8. عبارات If/ElseIf/Else داخل سلول
قوانین:
- از DateTime، Double، Int با عملگرهای ==، !=، >، >=، <، <= پشتیبانی میشود.
- از String با عملگرهای ==، != پشتیبانی میشود.
- هر عبارت باید در یک خط جدید باشد.
- قبل و بعد از عملگرها باید یک فاصله باشد.
- نباید خط جدید درون عبارات باشد.
- سلول باید دقیقاً طبق فرمت زیر باشد.
قبل
بعد

#### ۹. DataTable به عنوان پارامتر
csharp
var managers = new DataTable();
{
managers.Columns.Add("name");
managers.Columns.Add("department");
managers.Rows.Add("Jack", "HR");
managers.Rows.Add("Loan", "IT");
}
var value = new Dictionarycsharp var config = new OpenXmlConfiguration() { IgnoreTemplateParameterMissing = false, }; MiniExcel.SaveAsByTemplate(path, templatePath, value, config)#### ۱۰. فرمولها$##### ۱. مثال فرمول خود را با
شروع کنید و از$enumrowstartو$enumrowendبرای علامتگذاری ردیفهای شروع و پایان قابل شمارش استفاده کنید:$
زمانی که قالب رندر میشود، پیشوند
حذف شده و$enumrowstartو$enumrowendبا شماره ردیفهای شروع و پایان قابل شمارش جایگزین میشوند:$=SUM(C{{$enumrowstart}}:C{{$enumrowend}})
##### ۲. سایر مثالهای فرمول:
| | | |--------------|-------------------------------------------------------------------------------------------| | جمع |
| | میانگین جایگزین |$=SUM(C{{$enumrowstart}}:C{{$enumrowend}}) / COUNT(C{{$enumrowstart}}:C{{$enumrowend}})| | بازه |$=MAX(C{{$enumrowstart}}:C{{$enumrowend}}) - MIN(C{{$enumrowstart}}:C{{$enumrowend}})|IgnoreTemplateParameterMissing#### ۱۱. سایر
##### ۱. بررسی کلید پارامتر قالب
از نسخه V1.24.0 به بعد، به طور پیشفرض کلید پارامتر گمشده قالب نادیده گرفته شده و با رشته خالی جایگزین میشود،
میتواند کنترل کند که استثنا پرتاب شود یا نه.

ویژگی نام ستون اکسل/ایندکس/نادیدهگرفتن ستون
#### ۱. تعیین نام ستون، شماره ستون، یا نادیدهگرفتن ستون
نمونه اکسل

کد
csharp
public class ExcelAttributeDemo
{
[ExcelColumnName("Column1")]
public string Test1 { get; set; }
[ExcelColumnName("Column2")]
public string Test2 { get; set; }
[ExcelIgnore]
public string Test3 { get; set; }
[ExcelColumnIndex("I")] // system will convert "I" to 8 index
public string Test4 { get; set; }
public string Test5 { get; } //wihout set will ignore
public string Test6 { get; private set; } //un-public set will ignore
[ExcelColumnIndex(3)] // start with 0
public string Test7 { get; set; }
}var rows = MiniExcel.Query
#### 2. قالب سفارشی (ExcelFormatAttribute)
از نسخه V0.21.0 پشتیبانی از کلاسی که شامل متد ToString(string content) باشد، اضافه شده است
کلاس
csharp public class Dto { public string Name { get; set; }
[ExcelFormat("MMMM dd, yyyy")] public DateTime InDate { get; set; } }
کدcsharp
var value = new Dto[] {
new Issue241Dto{ Name="Jack",InDate=new DateTime(2021,01,04)},
new Issue241Dto{ Name="Henry",InDate=new DateTime(2020,04,05)},
};
MiniExcel.SaveAs(path, value);
نتیجه
کوئری از تبدیل فرمت سفارشی پشتیبانی میکند

#### ۳. تعیین عرض ستون (ExcelColumnWidthAttribute)
csharp
public class Dto
{
[ExcelColumnWidth(20)]
public int ID { get; set; }
[ExcelColumnWidth(15.50)]
public string Name { get; set; }
}
#### 4. نگاشت چندین نام ستون به یک ویژگی.csharp
public class Dto
{
[ExcelColumnName(excelColumnName:"EmployeeNo",aliases:new[] { "EmpNo","No" })]
public string Empno { get; set; }
public string Name { get; set; }
}
#### 5. System.ComponentModel.DisplayNameAttribute = ExcelColumnName.excelColumnNameAttribute
از نسخه 1.24.0، سیستم از System.ComponentModel.DisplayNameAttribute = ExcelColumnName.excelColumnNameAttribute پشتیبانی میکند.
C#
public class TestIssueI4TXGTDto
{
public int ID { get; set; }
public string Name { get; set; }
[DisplayName("Specification")]
public string Spc { get; set; }
[DisplayName("Unit Price")]
public decimal Up { get; set; }
}
#### ۶. ویژگی ExcelColumnAttributeاز نسخه V1.26.0، چندین ویژگی را میتوان به صورت زیر سادهسازی کرد:
از آن زمان به بعد، استفاده از چندین ویژگی به این صورت امکانپذیر است :
csharp
public class TestIssueI4ZYUUDto
{
[ExcelColumn(Name = "ID",Index =0)]
public string MyProperty { get; set; }
[ExcelColumn(Name = "CreateDate", Index = 1,Format ="yyyy-MM",Width =100)]
public DateTime MyProperty2 { get; set; }
}
#### ۷. ویژگی DynamicColumnAttributeاز نسخه V1.26.0، ما میتوانیم ویژگیهای ستون را به صورت پویا تنظیم کنیم
csharp
var config = new OpenXmlConfiguration
{
DynamicColumns = new DynamicExcelColumn[] {
new DynamicExcelColumn("id"){Ignore=true},
new DynamicExcelColumn("name"){Index=1,Width=10},
new DynamicExcelColumn("createdate"){Index=0,Format="yyyy-MM-dd",Width=15},
new DynamicExcelColumn("point"){Index=2,Name="Account Point"},
}
};
var path = PathHelper.GetTempPath();
var value = new[] { new { id = 1, name = "Jack", createdate = new DateTime(2022, 04, 12) ,point = 123.456} };
MiniExcel.SaveAs(path, value, configuration: config);

#### 8. ویژگی پویا برگه (DynamicSheetAttribute)
از نسخه V1.31.4 میتوانیم ویژگیهای برگه را به صورت پویا تنظیم کنیم. میتوانیم نام برگه و وضعیت (قابلیت مشاهده) را تعیین کنیم.
csharp
var configuration = new OpenXmlConfiguration
{
DynamicSheets = new DynamicExcelSheet[] {
new DynamicExcelSheet("usersSheet") { Name = "Users", State = SheetState.Visible },
new DynamicExcelSheet("departmentSheet") { Name = "Departments", State = SheetState.Hidden }
}
}; var users = new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } };
var department = new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } };
var sheets = new Dictionary
var path = PathHelper.GetTempPath(); MiniExcel.SaveAs(path, sheets, configuration: configuration);
ما همچنین میتوانیم از ویژگی جدید ExcelSheetAttribute استفاده کنیم:C#
[ExcelSheet(Name = "Departments", State = SheetState.Hidden)]
private class DepartmentDto
{
[ExcelColumn(Name = "ID",Index = 0)]
public string ID { get; set; }
[ExcelColumn(Name = "Name",Index = 1)]
public string Name { get; set; }
}
### افزودن، حذف، بهروزرسانی#### افزودن
پشتیبانی v1.28.0 از درج دادههای N ردیف CSV پس از آخرین ردیف
csharp
// Origin
{
var value = new[] {
new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)},
new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)},
};
MiniExcel.SaveAs(path, value);
}
// Insert 1 rows after last
{
var value = new { ID=3,Name = "Mike", InDate = new DateTime(2021, 04, 23) };
MiniExcel.Insert(path, value);
}
// Insert N rows after last
{
var value = new[] {
new { ID=4,Name ="Frank",InDate=new DateTime(2021,06,07)},
new { ID=5,Name ="Gloria",InDate=new DateTime(2022,05,03)},
};
MiniExcel.Insert(path, value);
}

نسخه 1.37.0 از اکسل پشتیبانی میکند که یک شیت جدید را به یک ورکبوک موجود اضافه کنید.
csharp
// Origin excel
{
var value = new[] {
new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)},
new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)},
};
MiniExcel.SaveAs(path, value, sheetName: "Sheet1");
}
// Insert a new sheet
{
var value = new { ID=3,Name = "Mike", InDate = new DateTime(2021, 04, 23) };
MiniExcel.Insert(path, table, sheetName: "Sheet2");
}
csharp stream.SaveAs(excelType:ExcelType.CSV); //or stream.SaveAs(excelType:ExcelType.XLSX); //or stream.Query(excelType:ExcelType.CSV); //or stream.Query(excelType:ExcelType.XLSX);#### حذف(در انتظار)پسوند فایل#### بهروزرسانی(در انتظار)
بررسی خودکار نوع اکسل
- MiniExcel به طور پیشفرض بر اساس
تشخیص میدهد که فایل xlsx است یا csv، اما ممکن است این کار دقیق نباشد، لطفاً نوع فایل را به صورت دستی مشخص کنید.از روی جریان داده (Stream) نمیتوان نوع فایل اکسل را تشخیص داد، لطفاً نوع فایل را به صورت دستی مشخص کنید.
csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { Seperator=';' }; MiniExcel.SaveAs(path, values,configuration: config);stringCSV
#### نکته
- به طور پیشفرض نوع بازگشتی
است و مقدار به اعداد یا تاریخ تبدیل نمیشود، مگر اینکه نوع با تایپ قوی جنریک تعریف شده باشد.,#### جداکننده سفارشی
به طور پیشفرض
به عنوان جداکننده استفاده میشود، میتوانید خصوصیتSeperatorرا برای سفارشیسازی تغییر دهید
از نسخه V1.30.1 قابلیت پشتیبانی از جداکننده سفارشی اضافه شده است (با تشکر از @hyzx86)csharp
var config = new CsvConfiguration()
{
SplitFn = (row) => Regex.Split(row, $"\"" target="_blank" rel="noopener noreferrer">\t,$)")
.Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")).ToArray()
};
var rows = MiniExcel.Query(path, configuration: config).ToList();
csharp var config = new MiniExcelLibs.Csv.CsvConfiguration() { NewLine='\n' }; MiniExcel.SaveAs(path, values,configuration: config);#### شکست خط سفارشی\r\nپیشفرض کاراکتر خط جدید
است، شما میتوانید ویژگیNewLineرا برای سفارشیسازی تغییر دهید
#### کدنویسی سفارشی
- کدگذاری پیشفرض "شناسایی کدگذاری از نشانههای ترتیب بایت" است (detectEncodingFromByteOrderMarks: true)
- اگر نیاز به کدگذاری سفارشی دارید، لطفاً ویژگی StreamReaderFunc / StreamWriterFunc را تغییر دهید
csharp
// Read
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
StreamReaderFunc = (stream) => new StreamReader(stream,Encoding.GetEncoding("gb2312"))
};
var rows = MiniExcel.Query(path, true,excelType:ExcelType.CSV,configuration: config);// Write var config = new MiniExcelLibs.Csv.CsvConfiguration() { StreamWriterFunc = (stream) => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) }; MiniExcel.SaveAs(path, value,excelType:ExcelType.CSV, configuration: config);
#### خواندن رشته خالی به عنوان nullبه طور پیشفرض، مقادیر خالی به string.Empty نگاشت میشوند. شما میتوانید این رفتار را تغییر دهید
csharp
var config = new MiniExcelLibs.Csv.CsvConfiguration()
{
ReadEmptyStringAsNull = true
};
### DataReader#### 1. GetReader
از نسخه 1.23.0، شما میتوانید GetDataReader را دریافت کنید
csharp
using (var reader = MiniExcel.GetReader(path,true))
{
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
var value = reader.GetValue(i);
}
}
}
غیرهمزمان (Async)
- نسخه v0.17.0 از غیرهمزمان (Async) پشتیبانی میکند (با تشکر از isdaniel ( SHIH,BING-SIOU)](https://github.com/isdaniel))
csharp
public static Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
public static Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null)
public static Taskcsharp public class Dto { public string Name { get; set; } public I49RYZUserType UserType { get; set; } }- پشتیبانی v1.25.0 ازcancellationToken.سایر موارد
#### 1. شمارش (Enum)
اطمینان حاصل کنید که نام اکسل و ویژگی یکسان باشد، سیستم به صورت خودکار نگاشت را انجام میدهد (بدون حساسیت به حروف بزرگ و کوچک)
از نسخه V0.18.0 پشتیبانی از توضیحات Enum اضافه شده است
public enum Type { [Description("General User")] V1, [Description("General Administrator")] V2, [Description("Super Administrator")] V3 }

از نسخه 1.30.0 پشتیبانی از تبدیل توضیحات اکسل به Enum اضافه شده است، با تشکر از @KaneLeung
#### 2. تبدیل CSV به XLSX یا تبدیل XLSX به CSV
csharp
MiniExcel.ConvertXlsxToCsv(xlsxPath, csvPath);
MiniExcel.ConvertXlsxToCsv(xlsxStream, csvStream);
MiniExcel.ConvertCsvToXlsx(csvPath, xlsxPath);
MiniExcel.ConvertCsvToXlsx(csvStream, xlsxStream);
#### 3. فرهنگاطلاعات (CultureInfo) سفارشی`csharp using (var excelStream = new FileStream(path: filePath, FileMode.Open, FileAccess.Read)) using (var csvStream = new MemoryStream()) { MiniExcel.ConvertXlsxToCsv(excelStream, csvStream); }
از نسخه 1.22.0، شما میتوانید CultureInfo را به صورت زیر سفارشیسازی کنید، پیشفرض سیستم CultureInfo.InvariantCulture است.
var config = new CsvConfiguration()
{
Culture = new CultureInfo("fr-FR"),
};
MiniExcel.SaveAs(path, value, configuration: config);// or
MiniExcel.Query(path, configuration: config);
#### 4. اندازه بافر سفارشی public abstract class Configuration : IConfiguration
{
public int BufferSize { get; set; } = 1024 * 512;
}
#### 5. حالت سریعسیستم حافظه را کنترل نخواهد کرد، اما میتوانید سرعت ذخیرهسازی بالاتری داشته باشید.
var config = new OpenXmlConfiguration() { FastMode = true };
MiniExcel.SaveAs(path, reader,configuration:config);
#### 6. افزودن دستهای تصویر (MiniExcel.AddPicture)لطفاً تصاویر را قبل از تولید دستهای دادههای ردیف اضافه کنید، در غیر این صورت سیستم هنگام فراخوانی AddPicture از حافظه زیادی استفاده خواهد کرد.
var images = new[]
{
new MiniExcelPicture
{
ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")),
SheetName = null, // default null is first sheet
CellAddress = "C3", // required
},
new MiniExcelPicture
{
ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")),
PictureType = "image/png", // default PictureType = image/png
SheetName = "Demo",
CellAddress = "C9", // required
WidthPx = 100,
HeightPx = 100,
},
};
MiniExcel.AddPicture(path, images);
#### ۷. دریافت ابعاد شیتها
var dim = MiniExcel.GetSheetDimensions(path);
مثالها:
#### ۱. درج SQL فایل با حجم بزرگ در SQLite و Dapper برای جلوگیری از OOM
توجه: لطفاً پس از Query از متدهای ToList/ToArray استفاده نکنید، این کار باعث بارگذاری تمام دادهها در حافظه میشود.
using (var connection = new SQLiteConnection(connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
using (var stream = File.OpenRead(path))
{
var rows = stream.Query();
foreach (var row in rows)
connection.Execute("insert into T (A,B) values (@A,@B)", new { row.A, row.B }, transaction: transaction);
transaction.Commit();
}
}
عملکرد:

#### 2. دموی API دانلود/آپلود اکسل Xlsx در ASP.NET Core 3.1 یا MVC 5 امتحان کنید
public class ApiController : Controller
{
public IActionResult Index()
{
return new ContentResult
{
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
Content = @"
DownloadExcel
DownloadExcelFromTemplatePath
DownloadExcelFromTemplateBytes
Upload Excel
public IActionResult DownloadExcel()
{
var values = new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
};
var memoryStream = new MemoryStream();
memoryStream.SaveAs(values);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "demo.xlsx"
};
}
public IActionResult DownloadExcelFromTemplatePath()
{
string templatePath = "TestTemplateComplex.xlsx";
Dictionary value = new Dictionary()
{
["title"] = "FooCompany",
["managers"] = new[] {
new {name="Jack",department="HR"},
new {name="Loan",department="IT"}
},
["employees"] = new[] {
new {name="Wade",department="HR"},
new {name="Felix",department="HR"},
new {name="Eric",department="IT"},
new {name="Keaton",department="IT"}
}
};
MemoryStream memoryStream = new MemoryStream();
memoryStream.SaveAsByTemplate(templatePath, value);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "demo.xlsx"
};
}
private static Dictionary TemplateBytesCache = new Dictionary();
static ApiController()
{
string templatePath = "TestTemplateComplex.xlsx";
byte[] bytes = System.IO.File.ReadAllBytes(templatePath);
TemplateBytesCache.Add(templatePath, bytes);
}
public IActionResult DownloadExcelFromTemplateBytes()
{
byte[] bytes = TemplateBytesCache["TestTemplateComplex.xlsx"];
Dictionary value = new Dictionary()
{
["title"] = "FooCompany",
["managers"] = new[] {
new {name="Jack",department="HR"},
new {name="Loan",department="IT"}
},
["employees"] = new[] {
new {name="Wade",department="HR"},
new {name="Felix",department="HR"},
new {name="Eric",department="IT"},
new {name="Keaton",department="IT"}
}
};
MemoryStream memoryStream = new MemoryStream();
memoryStream.SaveAsByTemplate(bytes, value);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "demo.xlsx"
};
}
public IActionResult UploadExcel(IFormFile excel)
{
var stream = new MemoryStream();
excel.CopyTo(stream);
foreach (var item in stream.Query(true))
{
// do your logic etc.
}
return Ok("File uploaded successfully");
}
}
#### 3. پرسوجوی صفحهبندیvoid Main()
{
var rows = MiniExcel.Query(path); Console.WriteLine("==== No.1 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:1));
Console.WriteLine("==== No.50 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:50));
Console.WriteLine("==== No.5000 Page ====");
Console.WriteLine(Page(rows,pageSize:3,page:5000));
}
public static IEnumerable Page(IEnumerable en, int pageSize, int page)
{
return en.Skip(page * pageSize).Take(pageSize);
}

#### 4. خروجی اکسل WebForm با استفاده از memorystream
var fileName = "Demo.xlsx";
var sheetName = "Sheet1";
HttpResponse response = HttpContext.Current.Response;
response.Clear();
response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
response.AddHeader("Content-Disposition", $"attachment;filename=\"{fileName}\"");
var values = new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
};
var memoryStream = new MemoryStream();
memoryStream.SaveAs(values, sheetName: sheetName);
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(Response.OutputStream);
response.End();#### 5. مدیریت پویا i18n چندزبانه و سطح دسترسی نقشها
مانند مثال، یک متد برای مدیریت i18n و سطح دسترسی ایجاد کنید و از yield return برای بازگرداندن IEnumerable استفاده کنید تا پردازش پویا و با مصرف حافظه کم حاصل شود.
void Main()
{
var value = new Order[] {
new Order(){OrderNo = "SO01",CustomerID="C001",ProductID="P001",Qty=100,Amt=500},
new Order(){OrderNo = "SO02",CustomerID="C002",ProductID="P002",Qty=300,Amt=400},
}; Console.WriteLine("en-Us and Sales role");
{
var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx";
var lang = "en-US";
var role = "Sales";
MiniExcel.SaveAs(path, GetOrders(lang, role, value));
MiniExcel.Query(path, true).Dump();
}
Console.WriteLine("zh-CN and PMC role");
{
var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx";
var lang = "zh-CN";
var role = "PMC";
MiniExcel.SaveAs(path, GetOrders(lang, role, value));
MiniExcel.Query(path, true).Dump();
}
}
private IEnumerable> GetOrders(string lang, string role, Order[] orders)
{
foreach (var order in orders)
{
var newOrder = new Dictionary();
if (lang == "zh-CN")
{
newOrder.Add("客户编号", order.CustomerID);
newOrder.Add("订单编号", order.OrderNo);
newOrder.Add("产品编号", order.ProductID);
newOrder.Add("数量", order.Qty);
if (role == "Sales")
newOrder.Add("价格", order.Amt);
yield return newOrder;
}
else if (lang == "en-US")
{
newOrder.Add("Customer ID", order.CustomerID);
newOrder.Add("Order No", order.OrderNo);
newOrder.Add("Product ID", order.ProductID);
newOrder.Add("Quantity", order.Qty);
if (role == "Sales")
newOrder.Add("Amount", order.Amt);
yield return newOrder;
}
else
{
throw new InvalidDataException($"lang {lang} wrong");
}
}
}
public class Order
{
public string OrderNo { get; set; }
public string CustomerID { get; set; }
public decimal Qty { get; set; }
public string ProductID { get; set; }
public decimal Amt { get; set; }
}

پرسشهای متداول (FAQ)
#### سوال: اگر عنوان هدر اکسل با نام خصوصیت کلاس برابر نباشد، چگونه نگاشت انجام دهیم؟
پاسخ: لطفاً از ویژگی ExcelColumnName استفاده کنید

#### سوال: چگونه چندین شیت را کوئری یا خروجی بگیریم؟
پاسخ: از متد GetSheetNames با پارامتر sheetName در Query استفاده کنید.
var sheets = MiniExcel.GetSheetNames(path);
foreach (var sheet in sheets)
{
Console.WriteLine($"sheet name : {sheet} ");
var rows = MiniExcel.Query(path,useHeaderRow:true,sheetName:sheet);
Console.WriteLine(rows);
}

#### سوال: چگونه اطلاعات مربوط به وضعیت نمایش شیتها را استعلام یا صادر کنیم؟
پاسخ: استفاده از متد GetSheetInformations.
var sheets = MiniExcel.GetSheetInformations(path);
foreach (var sheetInfo in sheets)
{
Console.WriteLine($"sheet index : {sheetInfo.Index} "); // next sheet index - numbered from 0
Console.WriteLine($"sheet name : {sheetInfo.Name} "); // sheet name
Console.WriteLine($"sheet state : {sheetInfo.State} "); // sheet visibility state - visible / hidden
}
#### س. آیا استفاده از Count تمام دادهها را به حافظه بارگذاری میکند؟خیر، تست تصویر دارای ۱ میلیون ردیف * ۱۰ ستون داده است، بیشترین استفاده از حافظه کمتر از ۶۰ مگابایت بوده و زمان اجرا ۱۳.۶۵ ثانیه است

#### س. Query چگونه از ایندکس عددی استفاده میکند؟
ایندکس پیشفرض Query، کلید رشتهای است: A,B,C.... اگر میخواهید به ایندکس عددی تغییر دهید، لطفاً متد زیر را برای تبدیل ایجاد کنید
void Main()
{
var path = @"D:\git\MiniExcel\samples\xlsx\TestTypeMapping.xlsx";
var rows = MiniExcel.Query(path,true);
foreach (var r in ConvertToIntIndexRows(rows))
{
Console.Write($"column 0 : {r[0]} ,column 1 : {r[1]}");
Console.WriteLine();
}
}private IEnumerable> ConvertToIntIndexRows(IEnumerable
var dic = new Dictionary();
var index = 0;
foreach (var key in keys)
dic[index++] = r[key];
yield return dic;
}
}
#### پرسش. چرا زمانی که مقدار خالی باشد، یک فایل اکسل بدون عنوان و خالی هنگام خروجی گرفتن تولید میشود؟زیرا MiniExcel از منطقی مشابه JSON.NET برای به دست آوردن نوع بهصورت پویا از مقادیر استفاده میکند تا عملیات API را ساده کند و بدون داده، نوع قابل تشخیص نیست. برای درک بیشتر میتوانید به issue #133 مراجعه کنید.

نوع قوی و DataTable هدرها را تولید میکنند، اما Dictionary همچنان اکسل خالی ایجاد میکند
#### پرسش. چگونه میتوان حلقه foreach را هنگام رسیدن به ردیف خالی متوقف کرد؟
MiniExcel را میتوان با LINQ TakeWhile برای متوقفکردن حلقه foreach استفاده کرد.

#### پرسش. چگونه ردیفهای خالی را حذف کنیم؟

IEnumerable :
public static IEnumerable QueryWithoutEmptyRow(Stream stream, bool useHeaderRow, string sheetName, ExcelType excelType, string startCell, IConfiguration configuration)
{
var rows = stream.Query(useHeaderRow,sheetName,excelType,startCell,configuration);
foreach (IDictionary row in rows)
{
if(row.Keys.Any(key=>row[key]!=null))
yield return row;
}
} جدول داده (DataTable) :
public static DataTable QueryAsDataTableWithoutEmptyRow(Stream stream, bool useHeaderRow, string sheetName, ExcelType excelType, string startCell, IConfiguration configuration)
{
if (sheetName == null && excelType != ExcelType.CSV) /Issue #279/
sheetName = stream.GetSheetNames().First(); var dt = new DataTable(sheetName);
var first = true;
var rows = stream.Query(useHeaderRow,sheetName,excelType,startCell,configuration);
foreach (IDictionary row in rows)
{
if (first)
{
foreach (var key in row.Keys)
{
var column = new DataColumn(key, typeof(object)) { Caption = key };
dt.Columns.Add(column);
}
dt.BeginLoadData();
first = false;
}
var newRow = dt.NewRow();
var isNull=true;
foreach (var key in row.Keys)
{
var _v = row[key];
if(_v!=null)
isNull = false;
newRow[key] = _v;
}
if(!isNull)
dt.Rows.Add(newRow);
}
dt.EndLoadData();
return dt;
}
#### س. چگونه از SaveAs(path,value) برای جایگزینی فایل موجود استفاده کنیم بدون اینکه خطای "The file ...xlsx already exists" ایجاد شود
لطفاً از کلاس Stream برای پیادهسازی منطق سفارشی ایجاد فایل استفاده کنید، به عنوان مثال:
`C#
using (var stream = File.Create("Demo.xlsx"))
MiniExcel.SaveAs(stream,value);
یا، از نسخه V1.25.0، SaveAs پارامتر overwriteFile را برای فعال/غیرفعال کردن بازنویسی فایل موجود پشتیبانی میکند
csharp MiniExcel.SaveAs(path, value, overwriteFile: true); ``
محدودیتها و نکات قابل توجه
- در حال حاضر از فایلهای xls و فایلهای رمزگذاریشده پشتیبانی نمیشود
- xlsm فقط از Query پشتیبانی میکند
منابع
ExcelDataReader / ClosedXML / Dapper / ExcelNumberFormat
تشکر
#### Jetbrains

با تشکر از فراهم کردن یک IDE رایگان All product برای این پروژه (License)
اشتراکگذاری مشارکت و حمایت مالی
لینک https://github.com/orgs/mini-software/discussions/754مشارکتکنندگان
--- Tranlated By Open Ai Tx | Last indexed: 2025-10-09 ---









