REDMOND, Wash. —June 4, 2018—Microsoft Corp. on Monday announced it has reached an agreement to acquire GitHub, the world’s leading softwareh development platform where more than 28 million developers learn, share and collaborate to create the future. Together, the two companies will empower developers to achieve more at every stage of the development lifecycle, accelerate enterprise use of GitHub, and bring Microsoft’s developer tools and services to new audiences.
On 28 of May, 2014, the first commit of Immutable.js was pushed in the facebook’s organization on GitHub. Immutable.js is a set of immutable data structures (List, Set, Map, etc.) implemented in JavaScript.
Plain JavaScript array
20 | 10 | 5 | 1 | تعداد watch - تعداد آرایه |
2.58 | 2.573 | 2.56 | 2.517 | 100 |
2.853 | 2.747 | 2.675 | 2.555 | 1000 |
15.68 | 7.736 | 4.025 | 2.861 | 10000 |
Immutable JavaScript list
20 | 10 | 5 | 1 | تعداد watch - تعداد آرایه |
2.569 | 2.562 | 2.507 | 2.696 | 100 |
2.49 | 2.569 | 2.54 | 2.715 | 1000 |
2.708 | 2.599 | 2.538 | 2.832 | 10000 |
بررسی ویجت Kendo UI File Upload
- اگر قرار هست تعدادی read و write از و به بانک اطلاعاتی به صورت یک atomic operation عمل کنند، باید از تراکنشها به صورت صریح استفاده کنید:
using (var transaction = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })) { var database = new DatabaseContext(); // DBC: BEGIN TRANSACTION var userA = database.Users.Find(1); var userB = database.Users.Find(2); userA.Name = "Admin"; database.SaveChanges(); userB.Age = 28; database.SaveChanges(); // DBC: COMMIT TRANSACTION transaction.Complete(); }
«اصول پایگاه داده - تراکنش ها»
«آشنایی با TransactionScope»
«مفهوم READ_COMMITTED_SNAPSHOT در EF 6»
« بررسی Transactions و Locks در SQL Server»
برای شروع ابتدا مدل برنامه رو به صورت زیر تعریف کنید.
public class Category { public int Id { get; set; } public string Title { get; set; } }
public class CategoryMap : EntityTypeConfiguration<Entity.Category> { public CategoryMap() { ToTable( "Category" ); HasKey( _field => _field.Id ); Property( _field => _field.Title ) .IsRequired(); } }
using System.Data.Entity; using System.Data.Entity.Infrastructure; namespace DataAccess { public interface IUnitOfWork { DbSet<TEntity> Set<TEntity>() where TEntity : class; DbEntityEntry<TEntity> Entry<TEntity>() where TEntity : class; void SaveChanges(); void Dispose(); } }
چون کلاس DatabaseContext از اینترفیس IUnitOfWork ارث برده است برای همین از InheritedExport استفاده میکنیم.
[InheritedExport( typeof( IUnitOfWork ) )] public class DatabaseContext : DbContext, IUnitOfWork { private DbTransaction transaction = null; public DatabaseContext() { this.Configuration.AutoDetectChangesEnabled = false; this.Configuration.LazyLoadingEnabled = true; } protected override void OnModelCreating( DbModelBuilder modelBuilder ) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.AddFormAssembly( Assembly.GetAssembly( typeof( Entity.Map.CategoryMap ) ) ); } public DbEntityEntry<TEntity> Entry<TEntity>() where TEntity : class { return this.Entry<TEntity>(); } }
public static class ModelBuilderExtension { public static void AddFormAssembly( this DbModelBuilder modelBuilder, Assembly assembly ) { Array.ForEach<Type>( assembly.GetTypes().Where( type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof( EntityTypeConfiguration<> ) ).ToArray(), delegate( Type type ) { dynamic instance = Activator.CreateInstance( type ); modelBuilder.Configurations.Add( instance ); } ); } }
برای پیاده سازی قسمت BusinessLogic ابتدا کلاس BusiessBase را در آن قرار دهید:
public class BusinessBase<TEntity> where TEntity : class { public BusinessBase( IUnitOfWork unitOfWork ) { this.UnitOfWork = unitOfWork; } [Import] public IUnitOfWork UnitOfWork { get; private set; } public virtual IEnumerable<TEntity> GetAll() { return UnitOfWork.Set<TEntity>().AsNoTracking(); } public virtual void Add( TEntity entity ) { try { UnitOfWork.Set<TEntity>().Add( entity ); UnitOfWork.SaveChanges(); } catch { throw; } finally { UnitOfWork.Dispose(); } } }
تمام متدهای پایه مورد نظر را باید در این کلاس قرار داد که برای مثال من متد Add , GetAll را براتون پیاده سازی کردم. UnitOfWork توسط ImportAttribute مقدار دهی میشود و نیاز به وهله سازی از آن نیست
کلاس Category رو هم باید به صورت زیر اضافه کنید.
public class Category : BusinessBase<Entity.Category> { [ImportingConstructor] public Category( [Import( typeof( IUnitOfWork ) )] IUnitOfWork unitOfWork ) : base( unitOfWork ) { } }
public class Plugin { public void Run() { AggregateCatalog catalog = new AggregateCatalog(); Container = new CompositionContainer( catalog ); CompositionBatch batch = new CompositionBatch(); catalog.Catalogs.Add( new AssemblyCatalog( Assembly.GetExecutingAssembly() ) ); batch.AddPart( this ); Container.Compose( batch ); } public CompositionContainer Container { get; private set; } }
- AssemblyCatalog : در اسمبلی مورد نظر به دنبال تمام Export Attributeها میگردد و آنها را به عنوان ExportedValue در Container اضافه میکند.
- TypeCatalog: فقط یک نوع مشخص را به عنوان ExportAttribute در نظر میگیرد.
- DirectoryCatalog : در یک مسیر مشخص تمام Assembly مورد نظر را از نظر Export Attribute جستجو میکند و آنها را به عنوان ExportedValue در Container اضافه میکند.
- ApplicationCatalog : در اسمبلی و فایلهای (EXE) مورد نظر به دنبال تمام Export Attributeها میگردد و آنها را به عنوان ExportedValue در Container اضافه میکند.
- AggregateCatalog : تمام موارد فوق را Support میکند.
class Program { static void Main( string[] args ) { Plugin plugin = new Plugin(); plugin.Run(); Category category = new Category(plugin.Container.GetExportedValue<IUnitOfWork>()); category.GetAll().ToList().ForEach( _record => Console.Write( _record.Title ) ); } }
مشکل عمل نکردن فونت فارسی
public static class ReportMethod { static FontSelector fontSelector = new FontSelector(); const char RightToLeftEmbedding = (char)PersianDate.RightToLeftEmbedding; const char PopDirectionalFormatting = (char)PersianDate.PopDirectionalFormatting; public static Dictionary<string, string> fontDicBody = new Dictionary<string, string> { { "BMitra", "B Mitra" }, { "tahoma", "tahoma" } }; public static Dictionary<string, string> fontDicHeader1 = new Dictionary<string, string> { { "BMitraBd", "B Mitra Bold" }, { "tahoma", "tahoma" } }; public static Dictionary<string, string> fontDicHeader2 = new Dictionary<string, string> { { "BTitrBd", "B Titr Bold" }, { "tahoma", "tahoma" } }; public static Dictionary<string, string> fontDicFooter = new Dictionary<string, string> { { "BMitra", "B Mitra" }, { "tahoma", "tahoma" } }; public static string FixWeakCharacters(string data) { if (string.IsNullOrWhiteSpace(data)) return string.Empty; var weakCharacters = new[] { @"\", "/", "+", "-", "=", ";", "$" }; foreach (var weakCharacter in weakCharacters) { data = data.Replace(weakCharacter, RightToLeftEmbedding + weakCharacter + PopDirectionalFormatting); } return data; } public static Phrase SetFont(string data, int fontType) { Dictionary<string, string> fontDic; float fontSize = 11; switch (fontType) { case 0: fontDic = fontDicBody; fontSize = 11; break; case 1: fontDic = fontDicHeader1; fontSize = 14; break; case 2: fontDic = fontDicFooter; fontSize = 12; break; case 11: fontDic = fontDicHeader2; fontSize = 18; break; default: fontDic = fontDicBody; fontSize = 11; break; } foreach (var item in fontDic) { FontFactory.Register("c:\\windows\\fonts\\" + item.Key + ".ttf"); Font newfont = FontFactory.GetFont(item.Value, BaseFont.IDENTITY_H, fontSize); if (newfont.Familyname != "unknown") fontSelector.AddFont(newfont); } return fontSelector.Process(FixWeakCharacters(data)); } public static PdfPCell SetCell(string text, int border, int colspan, int Horizontal, int Vertical, bool DirectionRTL, int fontType = 0) { if (DirectionRTL) { var cell = new PdfPCell { RunDirection = PdfWriter.RUN_DIRECTION_RTL }; cell.Border = border; cell.Colspan = colspan; cell.HorizontalAlignment = Horizontal; cell.VerticalAlignment = Vertical; cell.Phrase = new Phrase(ReportMethod.SetFont(text, fontType)); return cell; } else { var cell = new PdfPCell(); cell.Border = border; cell.Colspan = colspan; cell.HorizontalAlignment = Horizontal; cell.VerticalAlignment = Vertical; cell.Phrase = new Phrase(ReportMethod.SetFont(text, fontType)); return cell; } } }
public IPdfReportData CreatePdfReport_SRptTeach(int MemberID, List<sp_Teach_Communicate_Select_ReportTeachResult> Teach_Result, string st, List<sp_Institute_Center_Info_Select_Name_MasterResult> Info) { string fileName = string.Format("SRptTeach-{0}.pdf", Guid.NewGuid().ToString("N")); return new PdfReport() .DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.RightToLeft); doc.Orientation(PageOrientation.Landscape); doc.PageSize(PdfPageSize.A4); doc.DocumentMetadata(new DocumentMetadata { Author = Info[0].InstName, Application = "PdfRpt", Keywords = "گزارش", Subject = "گزارش ویژه", Title = "گزارش کارکرد مدرسید" }); doc.Compression(new CompressionSettings { EnableCompression = true, EnableFullCompression = true }); doc.PrintingPreferences(new PrintingPreferences { ShowPrintDialogAutomatically = false }); }) .DefaultFonts(fonts => { fonts.Path(System.IO.Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "fonts\\" + ReportMethod.fontDicBody.ElementAt(0).Key + ".ttf"), System.IO.Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "fonts\\" + ReportMethod.fontDicBody.ElementAt(1).Key + ".ttf")); fonts.Size(11); fonts.Color(System.Drawing.Color.Black); }) .PagesFooter(footer => { footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.RightToLeft)); }) .PagesHeader(header => { header.CustomHeader(new CustomHeader_SRptTeach(MemberID, st, Info)); }) .MainTableTemplate(template => { //template.BasicTemplate(BasicTemplate.SimpleTemplate); template.CustomTemplate(new TransparentTemplate()); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); table.GroupsPreferences(new GroupsPreferences { GroupType = GroupType.IncludeGroupingColumns, RepeatHeaderRowPerGroup = true, ShowOneGroupPerPage = false, SpacingBeforeAllGroupsSummary = 5f, ShowGroupingPropertiesInAllRows = true }); }) .MainTableDataSource(dataSource => { dataSource.StronglyTypedList<sp_Teach_Communicate_Select_ReportTeachResult>(Teach_Result); }) .MainTableSummarySettings(summarySettings => { summarySettings.OverallSummarySettings("جمع مبالغ"); summarySettings.AllGroupsSummarySettings("جمع کل مبالغ"); }) .MainTableColumns(columns => { columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.RowNo); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(0); column.Width(4); column.HeaderCell("ردیف"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.ParentName); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(1); column.Width(5); column.HeaderCell("مرکز"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.FullName); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(2); column.Width(12); column.HeaderCell("نام و نام خانوادگی"); column.Group(true, (val1, val2) => { return val1.ToString() == val2.ToString(); }); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.TermInfoName); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(3); column.Width(8); column.HeaderCell("ترم"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.Contract_NO); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(4); column.Width(7); column.HeaderCell("شماره قرارداد"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.LessonFullCode); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(5); column.Width(4); column.HeaderCell("کد درس"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.LessonName); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(6); column.Width(10); column.HeaderCell("نام درس"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.Start_Date_Lesson); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(7); column.Width(6); column.HeaderCell("تاریخ شروع"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.End_Date_Lesson); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(8); column.Width(6); column.HeaderCell("تاریخ پایان"); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.TeachAmount); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(9); column.Width(6); column.HeaderCell("مبلغ حق التدریس(ریال)"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.DoTeacherTime); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(10); column.Width(3); column.HeaderCell("ساعت کارکرد"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); column.AggregateFunction(aggregateFunction => { aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum); aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); }); columns.AddColumn(column => { column.PropertyName("CanPay"); column.CalculatedField(true, list => { if (list == null) return string.Empty; var amount = list.GetSafeStringValueOf<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.TeachAmount); var doTime = list.GetSafeStringValueOf<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.DoTeacherTime); var result = float.Parse(amount) * float.Parse(doTime); return Convert.ToDecimal(result); }); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(11); column.Width(7); column.HeaderCell("قابل پرداخت(ریال)"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); column.AggregateFunction(aggregateFunction => { aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum); aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); }); columns.AddColumn(column => { column.PropertyName<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.Personal_Education); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(12); column.Width(6); column.HeaderCell("مدرک تحصیلی"); }); //columns.AddColumn(column => //{ // column.PropertyName("Descriptions"); // column.CalculatedField(true, // list => // { // if (list == null) // return string.Empty; // var Row = list.GetSafeStringValueOf<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.RowNoPerson); // return ""; // }); // column.CellsHorizontalAlignment(HorizontalAlignment.Center); // column.IsVisible(true); // column.Order(13); // column.Width(3); // column.HeaderCell("توضیحات"); //}); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "اطلاعاتی برای نمایش وجود ندارد."); events.DocumentClosing(docClose => { string[] msgField = { "مدیر گروه", Info.Where(sp => sp.ID == MemberID).FirstOrDefault().InstKindName, Info.Where(sp => sp.ID == 0).FirstOrDefault().InstKindName, "امور مالی", "معاون پشتیبانی" }; string[] dataField = { "", Info.Where(sp => sp.ID == MemberID).FirstOrDefault().MasterName, Info.Where(sp => sp.ID == 0).FirstOrDefault().MasterName, "", Info.Where(sp => sp.ID == 1).FirstOrDefault().MasterName }; var infoTable = new PdfGrid(msgField.Length) { RunDirection = PdfWriter.RUN_DIRECTION_RTL, WidthPercentage = 100 }; foreach (var item in msgField) { infoTable.AddCell(ReportMethod.SetCell(item, PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, true)); } foreach (var item in dataField) { infoTable.AddCell(ReportMethod.SetCell(item, PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, true)); } docClose.PdfDoc.Add(infoTable); }); }) .Export(export => { export.ToExcel(); export.ToCsv(); export.ToXml(); export.ToString(); }) .Generate(data => { fileName = HttpUtility.UrlEncode(fileName, Encoding.UTF8); data.FlushInBrowser(fileName, FlushType.Inline); }); //.Generate(data => data.AsPdfFile(string.Format("{0}\\PlansPage\\RptIListSample-{1}.pdf", AppPath.ApplicationPath, Guid.NewGuid().ToString("N")))); }
namespace Academy.Control.Reports { public class CustomHeader_SRptTeach : IPageHeader { public IPdfFont PdfRptFont { set; get; } string st; List<sp_Institute_Center_Info_Select_Name_MasterResult> Info; int MemberID; public CustomHeader_SRptTeach(int MemberID, string st, List<sp_Institute_Center_Info_Select_Name_MasterResult> Info) { this.st = st; this.Info = Info; this.MemberID = MemberID; } public PdfGrid RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> rowdata, IList<SummaryCellData> summaryData) { // return null; var groupFullName = rowdata.GetSafeStringValueOf<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.FullName); var groupPersonalEducation = rowdata.GetSafeStringValueOf<sp_Teach_Communicate_Select_ReportTeachResult>(x => x.Personal_Education); var table = new PdfGrid(2) { WidthPercentage = 100 }; table.AddSimpleRow( (cellData, cellProperties) => { cellData.Value = "نام و نام خانوادگی:"; cellProperties.PdfFont = PdfRptFont; cellProperties.PdfFontStyle = DocumentFontStyle.Bold; cellProperties.HorizontalAlignment = HorizontalAlignment.Left; }, (cellData, cellProperties) => { cellData.Value = groupFullName; cellProperties.PdfFont = PdfRptFont; cellProperties.HorizontalAlignment = HorizontalAlignment.Left; }); table.AddSimpleRow( (cellData, cellProperties) => { cellData.Value = "مدرک تحصیلی :"; cellProperties.PdfFont = PdfRptFont; cellProperties.PdfFontStyle = DocumentFontStyle.Bold; cellProperties.HorizontalAlignment = HorizontalAlignment.Left; }, (cellData, cellProperties) => { cellData.Value = groupPersonalEducation; cellProperties.PdfFont = PdfRptFont; cellProperties.HorizontalAlignment = HorizontalAlignment.Left; }); return table.AddBorderToTable(borderColor: BaseColor.LIGHT_GRAY, spacingBefore: 10f); } public PdfGrid RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData) { var tableMain = new PdfGrid(1) { RunDirection = PdfWriter.RUN_DIRECTION_RTL, WidthPercentage = 100 }; tableMain.DefaultCell.Border = PdfPCell.NO_BORDER; PdfGrid table = new PdfGrid(3); table.DefaultCell.Border = PdfPCell.NO_BORDER; table.AddCell(ReportMethod.SetCell("", PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, false)); table.AddCell(ReportMethod.SetCell("گزارش کارکرد مدرسین ", PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, true,11)); PdfPTable tbRight = new PdfPTable(1) { RunDirection = PdfWriter.RUN_DIRECTION_RTL }; tbRight.DefaultCell.Border = PdfPCell.NO_BORDER; Image _image = Image.GetInstance(System.IO.Path.Combine(AppPath.ApplicationPath, "Content\\Images\\p_jahad2.jpg")); var cellImg = new PdfPCell(_image, false) { Border = PdfPCell.NO_BORDER }; cellImg.HorizontalAlignment = PdfPCell.ALIGN_CENTER; tbRight.AddCell(cellImg); tbRight.AddCell(ReportMethod.SetCell(Info[0].InstName, PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, false,1)); tbRight.AddCell(ReportMethod.SetCell(Info.Where(sp => sp.ID == MemberID).FirstOrDefault().SecondName, PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, false,1)); table.AddCell(tbRight); PdfGrid tbLeft = new PdfGrid(2) { RunDirection = PdfWriter.RUN_DIRECTION_RTL }; tbLeft.DefaultCell.Border = PdfPCell.NO_BORDER; tbLeft.AddCell(ReportMethod.SetCell("تاریخ گزارش : " + System.DateTime.Now.ToPersianDateTime("/", false), PdfPCell.NO_BORDER, 2, PdfPCell.ALIGN_LEFT, PdfPCell.ALIGN_MIDDLE, false)); tbLeft.AddCell(ReportMethod.SetCell("از تاریخ " + st.Split(';')[0], PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_LEFT, PdfPCell.ALIGN_MIDDLE, false)); tbLeft.AddCell(ReportMethod.SetCell("تا تاریخ " + st.Split(';')[1], PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_LEFT, PdfPCell.ALIGN_MIDDLE, false)); table.AddCell(tbLeft); table.AddCell(ReportMethod.SetCell("", PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, false)); table.AddCell(ReportMethod.SetCell("", PdfPCell.NO_BORDER, 1, PdfPCell.ALIGN_CENTER, PdfPCell.ALIGN_MIDDLE, false)); tableMain.AddCell(table); return tableMain; } } }
امکان خروجی اکسل از گزارشات سیستم، یکی از بایدهای بیشتر سیستمهای اطلاعاتی میباشد؛ یکی از چالشهای اصلی در تولید این نوع خروجی، افزایش مصرف حافظه متناسب با افزایش حجم دیتا میباشد. از آنجاییکه بیشتر راهکارهای موجود از جمله ClosedXml یا Epplus کل ساختار را ابتدا تولید کرده و اصطلاحا خروجی مورد نظر را بافر میکنند، برای حجم بالای اطلاعات مناسب نخواهند بود. راهکار برای خروجی CSV به عنوان مثال خیلی سرراست میباشد و میتوان با چند خط کد، به نتیجه دلخواه از طریق مکانیزم Streaming رسید؛ ولی ساختار Excel به سادگی فرمت CSV نیست و برای مثال فرمت Excel Workbook با پسوند xlsx یک بسته Zip شدهای از فایلهای XML میباشد.
معرفی MiniExcel
MiniExcel یک کتابخانه سورس باز با هدف به حداقل رساندن مصرف حافظه در زمان پردازش فایلهای Excel در دات نت میباشد. در مقایسه با Aspose از منظر امکانات شاید حرفی برای گفتن نداشته باشد، ولی از جهت خواندن اطلاعات فایلهای Excel با قابلیت پشتیبانی از LINQ و Deferred Execution در کنار مصرف کم حافظه و جلوگیری از مشکل OOM خیلی خوب عمل میکند. در تصویر زیر مشخص است که برای عمده عملیات پیادهسازی شده، از استریمها بهره برده شده است.
همچنین در زیر مقایسهای روی خروجی ۱ میلیون رکورد با تعداد ۱۰ ستون در هر ردیف انجام شدهاست که قابل توجه میباشد:
Logic : create a total of 10,000,000 "HelloWorld" excel
LibraryMethodMax Memory UsageMean | |||
MiniExcel | 'MiniExcel Create Xlsx' | 15 MB | 11.53181 sec |
Epplus | 'Epplus Create Xlsx' | 1,204 MB | 22.50971 sec |
OpenXmlSdk | 'OpenXmlSdk Create Xlsx' | 2,621 MB | 42.47399 sec |
ClosedXml | 'ClosedXml Create Xlsx' | 7,141 MB | 140.93992 sec |
به شدت API خوش دستی برای استفاده دارد و شاید مطالعه سورس کد آن از جهت طراحی نیز درس آموزی داشته باشد. در ادامه چند مثال از مستندات آن را میتوانید ملاحظه کنید:
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); MiniExcel.SaveAs(path, new[] { new { Column1 = "MiniExcel", Column2 = 1 }, new { Column1 = "Github", Column2 = 2} });
// DataReader export multiple sheets (recommand by Dapper ExecuteReader) using (var cnn = Connection) { cnn.Open(); var sheets = new Dictionary<string,object>(); sheets.Add("sheet1", cnn.ExecuteReader("select 1 id")); sheets.Add("sheet2", cnn.ExecuteReader("select 2 id")); MiniExcel.SaveAs("Demo.xlsx", sheets); }
طراحی یک ActionResult سفارشی برای استفاده از MiniExcel
برای این منظور نیاز است تا Stream مربوط به Response درخواست جاری را در اختیار این کتابخانه قرار دهیم و از سمت دیگر دیتای مورد نیاز را به نحوی که بافر نشود و از طریق مکانیزم Streaming در EF (استفاده از Deferred Execution و Enumerableها) مهیا کنیم. برای امکان تعویض پذیری (این سناریو در پروژه واقعی و باتوجه به جهت وابستگیها میتواند ضروری باشد) از دو واسط زیر استفاده خواهیم کرد:
public interface IExcelDocumentFactory { ILargeExcelDocument CreateLargeDocument(IEnumerable<ExcelColumn> headers, Stream stream); } public interface ILargeExcelDocument : IAsyncDisposable, IDisposable { Task Write<T>( PaginatedEnumerable<T> items, int count, int sizeLimit, CancellationToken cancellationToken = default) where T : notnull; }
متد CreateLargeDocument یک وهله از ILargeExcelDocument را در اختیار مصرف کننده قرار میدهد که قابلیت نوشتن روی آن از طریق متد Write را خواهد داشت. روش واکشی دیتا از طریق Delegate تعریف شده با نام PaginatedEnumerable به مصرف کننده محول شدهاست که در ادامه امضای آن را میتوانید مشاهده کنید:
public delegate IEnumerable<T> PaginatedEnumerable<out T>(int page, int pageSize);
در ادامه پیادهسازی واسط ILargeExcelDocument برای MiniExcel به شکل زیر خواهد بود:
internal sealed class MiniExcelDocument(Stream stream, IEnumerable<ExcelColumn> columns) : ILargeExcelDocument { private const int SheetLimit = 1_048_576; private bool _disposedValue; public async Task Write<T>( PaginatedEnumerable<T> items, int count, int sizeLimit, CancellationToken cancellationToken = default) where T : notnull { ThrowIfDisposed(); // TODO: apply sizeLimit var properties = FastReflection.Instance.GetProperties(typeof(T)) .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); var sheets = new Dictionary<string, object>(); var index = 1; while (count > 0) { cancellationToken.ThrowIfCancellationRequested(); IEnumerable<Dictionary<string, object>> reader = items(index, SheetLimit) .Select(item => { cancellationToken.ThrowIfCancellationRequested(); return columns.ToDictionary(h => h.Title, h => ValueOf(item, h.Name, properties)); }); sheets.Add($"sheet_{index}", reader); count -= SheetLimit; index++; } // This part is forward-only, and we are pretty sure that streaming will happen without buffering. await stream.SaveAsAsync(sheets, cancellationToken: cancellationToken); } private void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects) } // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null _disposedValue = true; } } ~MiniExcelDocument() { Dispose(disposing: false); } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } public async ValueTask DisposeAsync() { Dispose(); await ValueTask.CompletedTask; } private void ThrowIfDisposed() { if (!_disposedValue) return; throw new ObjectDisposedException(nameof(MiniExcelDocument)); } private static object ValueOf<T>(T record, string prop, IDictionary<string, FastPropertyInfo> properties) where T : notnull { var property = properties[prop] ?? throw new InvalidOperationException($"There is no property with given name [{prop}]"); return NormalizeValue(property.GetValue?.Invoke(record)); } private static object NormalizeValue(object? value) { if (value == null) return null!; return value switch { DateTime dateTime => dateTime.ToShortPersianDateTimeString(), TimeSpan time => time.ToString(@"hh\:mm\:ss"), DateOnly dateTime => dateTime.ToShortPersianDateString(false), TimeOnly time => time.ToString(@"hh\:mm\:ss"), bool boolean => boolean ? "بلی" : "خیر", IEnumerable<object> values => string.Join(',', values.Select(NormalizeValue).ToList()), Enum enumField => enumField.GetEnumStringValue(), _ => value }; } }
در بدنه متد Write باتوجه به تعداد کل رکوردها، یک کوئری برای هر شیت از طریق فراخوانی متد منتسب به پارامتر items اجرا خواهد شد؛ توجه کنید که اجرای این کوئری مشخصا به تعویق افتاده و تا زمان اولین MoveNext، اجرایی صورت نخواهد گرفت (مفهوم Deferred Execution). به این ترتیب باقی کارها از جمله فرمت کردن مقادیر در سمت برنامه و از طریق Linq To Object انجام خواهد شد. همچنین پیادهسازی Factory مرتبط با آن به شکل زیر خواهد بود:
internal sealed class ExcelDocumentFactory : IExcelDocumentFactory { public ILargeExcelDocument CreateLargeDocument(IEnumerable<ExcelColumn> columns, Stream stream) { return new MiniExcelDocument(stream, columns); } }
در ادامه ActionResult سفارشی برای گرفتن خروجی اکسل را به شکل زیر می توان پیادهسازی کرد:
public class ExcelExportResult<T>(PaginatedEnumerable<T> items, int count, ExportMetadata metadata) : ActionResult where T : notnull { private const string ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; private const string Extension = ".xlsx"; private const int SizeLimit = int.MaxValue; private readonly IReadOnlyList<FastPropertyInfo> _properties = FastReflection.Instance.GetProperties(typeof(T)); public override async Task ExecuteResultAsync(ActionContext context) { var sp = context.HttpContext.RequestServices; var factory = sp.GetRequiredService<IExcelDocumentFactory>(); var disposition = new ContentDispositionHeaderValue(DispositionTypeNames.Attachment); disposition.SetHttpFileName(MakeFilename()); context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = disposition.ToString(); context.HttpContext.Response.Headers.Append(HeaderNames.ContentType, ContentType); context.HttpContext.Response.StatusCode = StatusCodes.Status200OK; //TODO: deal with exception, because our global exception handling cannot take into account while the response is started. await using var bodyStream = context.HttpContext.Response.BodyWriter.AsStream(); await context.HttpContext.Response.StartAsync(context.HttpContext.RequestAborted); await using (var document = factory.CreateLargeDocument(MakeColumns(), bodyStream)) { await document.Write(items, count, SizeLimit, context.HttpContext.RequestAborted); } await context.HttpContext.Response.CompleteAsync(); } private string MakeFilename() { return $"{metadata.Title} - {DateTime.UtcNow.ToEpochSeconds()}{Extension}"; } private IEnumerable<ExcelColumn> MakeColumns() { var types = _properties.ToDictionary(p => p.Name, p => p.PropertyType, StringComparer.OrdinalIgnoreCase); return metadata.Fields.Select(f => { var type = types[f.Name]; type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum || type == typeof(DateOnly) || type == typeof(TimeOnly) || type == typeof(bool) || type == typeof(TimeSpan) || type == typeof(DateTime)) { type = typeof(string); } return new ExcelColumn(f.Name, f.Title, type); }); } }
در اینجا از طریق ExportMetadata که از سمت کاربر تعیین میشود، مشخص خواهد شد که کدام فیلدها در فایل نهایی حضور داشته باشند. در بدنه متد ExecuteResultAsync یکسری هدر مرتبط با کار با فایلها تنظیم شدهاست و سپس از طریق BodyWriter و متد AsStream به استریم مورد نظر دست یافته و در اختیار متد Write مربوط به document ایجاد شده، قرار دادهایم. یک نمونه استفاده از آن برای موجودیت فرضی مشتری می تواند به شکل زیر باشد:
[ApiController, Route("api/customers")] public class CustomersController(IDbContext dbContext) : ControllerBase { [HttpGet("export")] public async Task<ActionResult> ExportCustomers([FromQuery] ExportMetadata metadata, CancellationToken cancellationToken) { var count = await dbContext.Set<Customer>().CountAsync(cancellationToken); return this.Export( (page, pageSize) => dbContext.Set<Customer>() .OrderBy(c => c.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .AsNoTracking() .AsEnumerable(), // Enable streaming instead of buffering through deferred execution count, metadata); } }
در اینجا از طریق Extension Method مهیا شده روش کوئری کردن برای هر شیت را مشخص کردهایم؛ نکته مهم در ایجاد استفاده از متد AsEnumerable می باشد که در عمل یک Type Casting انجام می دهد که باقی متدهای استفاده شده روی خروجی، از طریق Linq To Object اعمال شود و همچنین نیاز به استفاده از ToList و یا موارد مشابه را نخواهیم داشت. نمونه درخواست GET برای این API می تواند به شکل زیر باشد:
http://localhost:5118/api/customers/export?Title=Test&Fields[0].Name=FirstName&Fields[0].Title=First name&Fields[1].Name=LastName&Fields[1].Title=Last name&Fields[2].Name=BirthDate&Fields[2].Title=BirthDate
سورس کد مثال قابل اجرا از طریق مخزن زیر قابل دسترس می باشد:
https://github.com/rabbal/large-excel-streaming
در این مثال در زمان آغاز برنامه، ۱۰ میلیون رکورد در جدول Customer ثبت خواهد شد که در ادامه می توان از آن خروجی Excel تهیه کرد.
نکته مهم: توجه داشته باشید که استفاده از این روش قابلیت از سرگیری مجدد برای دانلود را نخواهد داشت و شاید بهتر است این فرآیند را از طریق یک Job انجام داده و با استفاده از قابلیتهای Multipart Upload مربوط به یک BlobStroage مانند Minio، خروجی مورد نظر از قبل ذخیره کرده و لینک دانلودی را در اختیار کاربر قرار دهید.
یکی از سؤالاتی که ممکن است در مصاحبهها با آن روبرو شوید، عنوان این مطلب است. در این مقاله قصد داریم تفاوت بین این دو را بررسی کنیم.
در علم کامپیوتر، یک call stack، یک ساختار دادهای پشته میباشد که اطلاعات جزئی را راجع به زیرروالهای فعال یک برنامه، ذخیره میکند. این نوع پشته با اسامی مختلفی از جمله Execution Stack (ES)، Program Stack (PS)، Control Stack، Runtime Stack یا Machine Stack شناخته میشود و یا اینکه به صورت کلی به آن The Stack یا همان پشته هم میگویند. با استفاده از صفحه Call Stack میتوانیم توابع و پروسیجرهایی را که فراخوانی شدهاند، ببینیم. Call Stack به ما میگوید که کدام متدها و توابع، با چه ترتیبی اجرا شدهاند. Call Stack یک راه بسیار خوب، برای فهم درست نحوهی اجرای یک برنامه است. با رهگیری یا Track کردن Call Stack میتوانیم بفهمیم که مکانیزم کار داخلی برنامه چگونه است و برای بهتر رفع کردن مشکلات، از آن استفاده کنیم. در سی شارپ، Stack Trace یک پشتهی اجرایی یا Execution Stack است که تمامی متدهای درحال اجرا را رهگیری (Track) میکند. Stack Trace راهی است که با استفاده از آن میتوانیم Call Stack را بررسی کنیم، تا شماره خط متدی را که Exception درآن رخ داده است، ببینیم. از این به بعد برای سادگی مطلب، Stack Trace را به صورت ST بیان میکنیم. برای دسترسی به ST از فضای نام System.Diagnostics مانند زیر استفاده میکنیم.
System.Diagnostics.StackTrace myTrack = new System.Diagnostics.StackTrace();
و یا برای مشاهدهی آن در کنسول از کد زیر استفاده میکنیم:
static void Main(string[] args) { Console.WriteLine("Stack Trace: {0}", Environment.StackTrace); }
نتیجه:
Stack Trace:at System.Environment.get_StackTrace() at DiffThrowAndThrowException.Program.Main(String[] args) in Program.cs:line 9
استفاده از ST در Try Catch :
static void Main(string[] args) { try { throw new Exception(); } catch (Exception ex) { Console.WriteLine("Stack Trace: {0}", Environment.StackTrace); } }
نتیجه:
Stack Trace:at System.Environment.get_StackTrace() at DiffThrowAndThrowException.Program.Main(String[] args) in Program.cs:line 11
حالا که مفهوم Stack Trace و نحوه کار و نمایش آن را بررسی کردیم، به راحتی میتوان تفاوت بین Throw و Throw Exception را درک کرد. به طور کلی میتوان اینطور گفت، در حالتیکه در داخل بلاک Catch، از Throw استفاده کنیم، این کار باعث میشود استثنائی که در اینجا رخ داده، به ابتدای ST افزوده شده و در واقع سلسله مراتب اجرای برنامه تا جایی که Throw نوشته شده، در ST نگهداری شود. اما در صورتیکه به جای Throw از Throw Exception استفاده کنیم، اتفاقی که رخ میدهد این است که ST تا اینجای کار که throw exception را استفاده میکنیم، نگهداری میشود و اطلاعات متدهایی که بعد از throw exception اجرا شدهاند، از آن حذف میشود. در نهایت در این حالت ST شامل اطلاعات متدهای اجرا شده در فرآیند جاری، از ابتدا تا رسیدن به throw exception میباشد.
در زیر، نمونه کدی را برای استفاده از Throw، میبینید:
class Program { static void Main(string[] args) { FirstExceptionMethod firstException = new FirstExceptionMethod(); try { firstException.Method1(); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } } class FirstExceptionMethod { public void Method1() { try { SecondExceptionMethod secondException = new SecondExceptionMethod(); secondException.Method2(); } catch (Exception ex) { throw; } } } class SecondExceptionMethod { public void Method2() { try { ThrowExMethod(); } catch (Exception ex) { throw; } } public void ThrowExMethod() { throw new Exception(); } }
نتیجه:
at DiffThrowAndThrowException.SecondExceptionMethod.ThrowExMethod() in Program.cs:line 51 at DiffThrowAndThrowException.SecondExceptionMethod.Method2() in Program.cs:line 41 at DiffThrowAndThrowException.FirstExceptionMethod.Method1() in Program.cs:line 27 at DiffThrowAndThrowException.Program.Main(String[] args) in Program.cs:line 12
همانطور که میبینید اطلاعات متدهایی که در این فرآیند اجرا شدهاند، در داخل Stack Trace رهگیری (track) شدهاند.
این دفعه برای نشان دادن تفاوت محتویات ST، کدهای Method1 را به شکل زیر تغییر میدهیم که در بخش catch آن، از throw exception استفاده کردهایم:
public void Method1() { try { SecondExceptionMethod secondException = new SecondExceptionMethod(); secondException.Method2(); } catch (Exception ex) { throw new Exception("Some Text ..."); } }
نتیجه:
at DiffThrowAndThrowException.FirstExceptionMethod.Method1() in Program.cs:line 31 at DiffThrowAndThrowException.Program.Main(String[] args) in Program.cs:line 12
در اینجا اطلاعات متدهای برنامه از شروع فرآیند تا جائیکه از throw exception استفاده کرده، در ST نوشته میشود.
با دیدن خروجیهای بالا میتوان دریافت که استفاده از throw exception بجای throw باعث میشود تا اطلاعات کمتری از فرآیند اجرا شده در ST ذخیره شود و در واقع رهگیری متدهای فرآیند از ابتدا تا جائیکه throw exception استفاده میشود، پیش میرود و بعد از آن اطلاعاتی را ثبت نمیکند.
ویژگی های C# 7.1
#. ویژگیهای نسخه ۵ --> مبانی asyncویژگیهای نسخه ۷٫۱
#. ویژگیهای نسخه ۶
#. ویژگیهای نسخه ۷
#1. فعالسازی کامپایلر
#2. استفاده از async main
#3. مقادیر پیشفرض - default literals
#4. استنتاج نام چندتاییها - tuple name inference
یکی
از ویژگیهای جدید اضافه شده به سی شارپ 9، Attributes on
local functions نام دارد و این توانایی را به ما میدهد تا بر روی متدهای محلی که
درون متدها تعریف میشوند، Attributes قرار دهیم. قابلیت local functions در نسخه 7 سی شارپ اضافه شدهاست و با استفاده از این قابلیت میتوانیم درون یک متد، تابع دیگری را تعریف کنیم و در همان متد، از آن تابع درونی
استفاده کنیم. در واقع تابع درونی، لوکال متد بیرونی است و در خارج از متد بیرونی،
قابل دسترسی نیست. مانند مثال زیر:
// Main method public static void Main() { // Local Function void AddValue(int a, int b) { Console.WriteLine("Value of a is: " + a); Console.WriteLine("Value of b is: " + b); Console.WriteLine("Sum of a and b is: {0}", a + b); Console.WriteLine(); } // Calling Local function AddValue(20, 40); AddValue(40, 60); }
برای بررسی این ویژگی جدید سی شارپ 9.0، از یک مثال استفاده میکنیم. فرض کنید یک برنامهی کنسول را داریم و میخواهیم یک قطعه کد فقط در حالتی در خروجی نوشته شود که برنامه در حالت دیباگ اجرا شده باشد و اگر در حالت ریلیز باشد، در خروجی مشاهده نشود. قبل از نسخهی 9.0 سی شارپ، مجبور هستیم از directive های کامپایلر زبان استفاده کنیم و از طریق آن به کامپایلر بفهمانیم که چه زمانی این قطعه کد را کامپایل کند. مانند مثال زیر:
static void Main(string[] args) { static void DoAction() { // DoAction Console.WriteLine("DoAction..."); } #if DEBUG DoAction(); #endif }
اما با استفاده قابلیت اضافه شدهی در این نسخه از سی شارپ، میتوان روی متدهای محلی هم Attributes اضافه کرد. پس میتوانیم از ConditionalAttribute استفاده کنیم و آن را در بالای متد محلی قرار دهیم و از کامپایلر بخواهیم در حالت دیباگ اجرا شود. مانند کد زیر
static void Main(string[] args) { [Conditional("DEBUG")] static void DoAction() { // Do Action Here Console.WriteLine("Do Action..."); } DoAction(); }
اگر بر روی متدهای محلی C# 8.0 از Attribute استفاده کنیم، با خطای زیر روبرو میشویم:
ErrorCS8400Feature 'local function attributes' is not available in C# 8.0. Please use language version 9.0 or greater.