محدودیتهای کار با اشیاء COM در NET Core 2x.
پیاده سازی پشتیبانی از اشیاء COM در NET Core 2x. به همراه اینترفیس IDispatch نیست. به این معنا که از مفهوم «late binding» پشتیبانی نمیکند. حدود 10 سال قبل در زمان ارائهی C# 4.0، واژهی کلیدی dynamic نیز ارائه شد که یکی از مهمترین اهداف آن، ساده سازی کار با اشیاء COM و پشتیبانی از Late binding بود:
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application", true)); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
System.__ComObject does not contain a definition for 'Visible'
یک نکته: NET Core 3x. از Late binding پشتیبانی میکند.
روش کار با اشیاء COM در NET Core 2x.
چون NET Core 2x. از late binding اشیاء COM پشتیبانی نمیکند، میتوان در اینجا از روش قدیمیتر کار با اشیاء COM که استفادهی از «Interop assemblies» نام دارد، استفاده کرد. Interop assemblies در حقیقت محصور کنندههای اشیاء COM هستند که امکان کار مستقیم با آنها را از طریق early binding میسر میکنند. در یک چنین حالتی، کدهای فوق برای دسترسی به اشیاء COM کار با اکسل، به صورت زیر که early binding نام دارد، تغییر میکند:
using Excel = Microsoft.Office.Interop.Excel; // ... var excel = new Excel.Application(); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
روش تولید Interop assemblies
هنوز خود NET Core. روشی را برای تولید Interop assemblies ارائه ندادهاست و تولید آنها یکی از معدود مواردی است که نیاز به نصب Visual Studio را دارد. برای این منظور یک پروژهی خالی (از هر نوعی) را که بر اساس NET Framework 4x. تهیه میشود، در VS آغاز کنید و سپس در solution explorer بر روی پروژهی ایجاد شده کلیک راست کرده و گزینهی Add > Reference را انتخاب کنید. در صفحهی باز شده، گزینهی COM آنرا باید انتخاب کنید. در اینجا است که میتوانید با انتخاب یکی از موارد، ارجاعی را به آن شیء COM اضافه کنید.
پس از اینکار:
- ابتدا این ارجاع اضافه شده را در solution explorer انتخاب کرده و در پایین صفحه، در قسمت برگهی خواص آن، گزینهی «Embed Interop Types» آنرا به false تنظیم کنید.
- سپس یکبار پروژه را نیز کامپایل کنید.
این مراحل سبب تولید یک فایل dll خواهند شد که Interop assembly نام دارد و هم در برنامههای NET. و هم NET Core.، قابل استفادهاست.
روش استفاده از Interop assemblies در برنامههای NET Core.
اکنون که یک فایل dll را از شیء COM انتخابی، در یک پروژهی مجزای مبتنی بر NET 4x. تولید کردیم، روش استفادهی از آن در یک برنامهی دیگر مبتنی بر NET Core. به صورت زیر است:
<ItemGroup> <Reference Include="Interop.WIA"> <HintPath>..\DNTScanner.Core.TypeLibrary\bin\Debug\Interop.WIA.dll</HintPath> <EmbedInteropTypes>True</EmbedInteropTypes> </Reference> </ItemGroup>
یک نکته: اگر EmbedInteropTypes را به true تنظیم کردید، نیاز به بستهی Microsoft.CSharp را نیز خواهید داشت:
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' "> <Reference Include="Microsoft.CSharp" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <PackageReference Include="Microsoft.CSharp" Version="4.5.0" /> </ItemGroup>
روش دیگر استفاده از Interop assemblies در برنامههای NET Core.
روش فوق، جهت کار با فایلهای dll ای است که خودمان تولید کردهایم. برای سایر حالاتی که این موارد در سیستم نصب شدهاند (مانند Office Primary Interop Assemblies (PIA))، پس از افزودن ارجاعی به COM reference مدنظر، فایل csproj همان پروژهی NET 4x. را باز کرده و قسمت COMReference آنرا در اینجا (در فایل csproj پروژهی NET Core.) کپی کنید:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <!-- The following 'COMReference' items were copied from a .NET Framework project. They were added by using the Visual Studio COM References window. See https://docs.microsoft.com/en-us/visualstudio/ide/managing-references-in-a-project?view=vs-2017. Observe the 'EmbedInteropTypes' tag value. See https://docs.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items?view=vs-2017#comreference --> <ItemGroup> <COMReference Include="Microsoft.Office.Core"> <Guid>{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}</Guid> <VersionMajor>2</VersionMajor> <VersionMinor>8</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="Microsoft.Office.Interop.Excel"> <Guid>{00020813-0000-0000-C000-000000000046}</Guid> <VersionMajor>1</VersionMajor> <VersionMinor>9</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="VBIDE"> <Guid>{0002E157-0000-0000-C000-000000000046}</Guid> <VersionMajor>5</VersionMajor> <VersionMinor>3</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> </ItemGroup> </Project>
سپس یک نمونه از MS Office automation را توسط اشیاء COM آن به صورت زیر میتوان پیاده سازی کرد:
using System; using System.Reflection; using Excel = Microsoft.Office.Interop.Excel; namespace ExcelDemo { class Program { public static void Main(string[] args) { Excel.Application excel; Excel.Workbook workbook; Excel.Worksheet sheet; Excel.Range range; try { // Start Excel and get Application object. excel = new Excel.Application(); excel.Visible = true; // Get a new workbook. workbook = excel.Workbooks.Add(Missing.Value); sheet = (Excel.Worksheet)workbook.ActiveSheet; // Add table headers going cell by cell. sheet.Cells[1, 1] = "First Name"; sheet.Cells[1, 2] = "Last Name"; sheet.Cells[1, 3] = "Full Name"; sheet.Cells[1, 4] = "Salary"; // Format A1:D1 as bold, vertical alignment = center. sheet.get_Range("A1", "D1").Font.Bold = true; sheet.get_Range("A1", "D1").VerticalAlignment = Excel.XlVAlign.xlVAlignCenter; // Create an array to multiple values at once. string[,] saNames = new string[5, 2]; saNames[0, 0] = "John"; saNames[0, 1] = "Smith"; saNames[1, 0] = "Tom"; saNames[1, 1] = "Brown"; saNames[2, 0] = "Sue"; saNames[2, 1] = "Thomas"; saNames[3, 0] = "Jane"; saNames[3, 1] = "Jones"; saNames[4, 0] = "Adam"; saNames[4, 1] = "Johnson"; // Fill A2:B6 with an array of values (First and Last Names). sheet.get_Range("A2", "B6").Value2 = saNames; // Fill C2:C6 with a relative formula (=A2 & " " & B2). range = sheet.get_Range("C2", "C6"); range.Formula = "=A2 & \" \" & B2"; // Fill D2:D6 with a formula(=RAND()*100000) and apply format. range = sheet.get_Range("D2", "D6"); range.Formula = "=RAND()*100000"; range.NumberFormat = "$0.00"; // AutoFit columns A:D. range = sheet.get_Range("A1", "D1"); range.EntireColumn.AutoFit(); // Make sure Excel is visible and give the user control // of Microsoft Excel's lifetime. excel.Visible = true; excel.UserControl = true; } catch (Exception e) { Console.WriteLine($"Error: {e.Message} Line: {e.Source}"); } } } }
How to automate Microsoft Excel from Microsoft Visual C#.NET
کنفرانس dotNetConf 2015 مایکروسافت
تبدیلگر ایران سیستم به یونیکد
با این کدها:
// from http://www.microsoft.com/en-us/download/details.aspx?id=14839 var connectionString = "Provider=VFPOLEDB.1;Data Source=" + @"D:\path\JVJ100.DBF" + ";Password=;Collating Sequence=MACHINE"; using (var dbConnection = new OleDbConnection(connectionString)) { using (var dataAdapter = new OleDbDataAdapter("select FAMILY from JVJ100.DBF", dbConnection)) { using (var dataset = new DataSet()) { dataAdapter.Fill(dataset, "table1"); foreach (DataRow dataRow in dataset.Tables[0].Rows) { var familyIranSystem = dataRow[0] as string; var familyIranUnicode = ConvertTo.Unicode(familyIranSystem, 1256); if (!string.IsNullOrWhiteSpace(familyIranUnicode)) { } } } } }
دو نکته در اینجا مهم است:
الف) استفاده از درایور فاکس پرو
ب) code page استفاده شده 1256 است که باید در IranSystemConvertor تنظیم شود.
روشهای مختلف اطلاع رسانی به سیستم ردیابی تغییرات
متد DbSet.Add کار اطلاع رسانی تبدیل وهلههای ثبت شده را به کوئریهای Insert رکوردهای جدید، انجام میدهد:
using (var db = new BloggingContext()) { var blog = new Blog { Url = "http://sample.com" }; db.Blogs.Add(blog); db.SaveChanges(); }
سیستم ردیابی اطلاعات، اگر تغییراتی را در خواص اشیاء تحت نظر خود مشاهده کند، سبب تولید کوئریهای Update میگردد. یک چنین اشیایی تحت نظر Context هستند:
الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شدهاند.
ب) اشیایی که در طول عمر Context به آن اضافه شدهاند (حالت قبل).
using (var db = new BloggingContext()) { var blog = db.Blogs.First(); blog.Url = "http://sample.com/blog"; db.SaveChanges(); }
و متد DbSet.Remove کار اطلاع رسانی تبدیل وهلههای حذف شده را به کوئریهای Delete معادل، انجام میدهد:
using (var db = new BloggingContext()) { var blog = db.Blogs.First(); db.Blogs.Remove(blog); db.SaveChanges(); }
به علاوه امکان ترکیب متدهای Add، Remove و همچنین به روز رسانی اشیاء در طی یک Context و با فراخوانی یک SaveChanges در انتهای کار نیز وجود دارد. از این جهت که یک Context، الگوی واحد کار را پیاده سازی میکند و بیانگر یک تراکنش است. در این حالت ترکیبی، یا کل تراکنش با موفقیت به پایان میرسد و یا در صورت بروز مشکلی، هیچکدام از تغییرات درخواستی، اعمال نخواهند شد.
عملیات ردیابی، بر روی هر نوع Projections صورت نمیگیرد
اگر توسط LINQ Projections، نتیجهی نهایی کوئری را تغییر دادید، فقط در زمانی سیستم ردیابی بر روی آن فعال خواهد بود که projection نهایی حاوی اصل موجودیت مدنظر باشد. برای مثال در کوئری ذیل چون در Projection صورت گرفتهی در متد Select، هنوز در خاصیت Blog، به اصل موجودیت Blog اشاره میشود، نتیجهی این کوئری نیز تحت نظر سیستم ردیابی خواهد بود:
using (var context = new BloggingContext()) { var blog = context.Blogs .Select(b => new { Blog = b, Posts = b.Posts.Count() }); }
using (var context = new BloggingContext()) { var blog = context.Blogs .Select(b => new { Id = b.BlogId, Url = b.Url }); }
لغو سیستم ردیابی تغییرات، در زمانیکه به آن نیازی نیست
سیستم ردیابی تغییرات بر اساس مفاهیم AOP و تولید پروکسیهای آن کار میکند. این پروکسیها، اشیایی شفاف هستند که اشیاء شما را احاطه میکنند و هر تغییری را که اعمال میکنید، ابتدا از این غشاء رد شده و در سیستم ردیابی EF ثبت میشوند. سپس به وهلهی اصلی شیء موجود اعمال خواهند شد.
بدیهی است تولید این پروکسیها، دارای سربار است و اگر هدف شما صرفا کوئری گرفتن از اطلاعات، جهت نمایش آنها است، نیازی به تولید خودکار این پروکسیها را ندارید و این مساله سبب کاهش مصرف حافظهی برنامه و بالا رفتن سرعت آن میشود.
در قسمت قبل عنوان شد که «یک چنین اشیایی تحت نظر Context هستند: الف) اشیایی که در طول عمر Context از دیتابیس کوئری گرفته شدهاند.»
اگر میخواهید این حالت پیش فرض را لغو کنید، از متد AsNoTracking استفاده نمائید:
using (var context = new BloggingContext()) { var blogs = context.Blogs.AsNoTracking().ToList(); }
اگر میخواهید متد AsNoTracking را به صورت خودکار به تمام کوئریهای یک context خاص اعمال کنید، روش کار و تنظیم آن به صورت زیر است:
using (var context = new BloggingContext()) { context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
نکات به روز رسانی ارجاعات موجودیتها
دو حالت زیر را درنظر بگیرید که در اولی، blog از بانک اطلاعاتی واکشی شدهاست و post به صورت مستقیم وهله سازی شدهاست:
using (var context = new BloggingContext()) { var blog = context.Blogs.First(); var post = new Post { Title = "Intro to EF Core" }; blog.Posts.Add(post); context.SaveChanges(); }
using (var context = new BloggingContext()) { var blog = new Blog { Url = "http://blogs.msdn.com/visualstudio" }; var post = context.Posts.First(); blog.Posts.Add(post); context.SaveChanges(); }
در حالت دوم، ابتدا blog در بانک اطلاعاتی ثبت میشود (چون برخلاف حالت اول، تحت نظر context نیست) و سپس این post (که تحت نظر context است) به مجموعه مطالب آن اضافه میشود (بلاگ جدیدی اضافه شده و ارجاع مطلب موجودی به آن اضافه میشود).
وارد کردن یک موجودیت به سیستم ردیابی اطلاعات
در مثال قبل مشاهده کردیم که اگر موجودیتی تحت نظر context نباشد (برای مثال توسط یک کوئری به context وارد نشده باشد)، در حین ذخیره سازی ارجاعات، با آن به صورت یک وهلهی جدید رفتار شده و حتما در بانک اطلاعاتی به صورت یک رکورد جدید ذخیره میشود؛ حتی اگر Id آنرا دستی تنظیم کرده باشید که ندید گرفته خواهد شد.
اگر Id و سایر اطلاعات شیءایی را دارید، نیازی نیست تا حتما توسط یک کوئری ابتدا آنرا از بانک اطلاعاتی دریافت و سپس به صورت خودکار وارد سیستم ردیابی کنید؛ متد Attach نیز یک چنین کاری را انجام میدهد:
var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" }; context.Blog.Attach(blog); context.SaveChanges();
علاوه بر متد Attach، متد AttachRange نیز برای افزودن لیستی از موجودیتها در حالت EntityState.Unchanged، پیش بینی شدهاست.
روش دیگر انجام اینکار به صورت ذیل است:
در اینجا ابتدا یک وهلهی جدید از Blog ایجاد شدهاست و سپس توسط متد Entry به Context وارد شده و همچنین حالت آن به صورت صریح، به تغییر یافته، مشخص گردیدهاست:
var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" }; context.Entry(blog).State = EntityState.Modified ; context.SaveChanges();
var blog = new Blog { Id = 2, Url = "https://www.dntips.ir" }; context.Update(blog); context.SaveChanges();
به علاوه متد UpdateRange نیز برای افزودن لیستی از موجودیتها در حالت EntityState.Modified، پیش بینی شدهاست.
یک نکته: متدهای Attach و Update، هم بر روی یک DbSet و هم بر روی Context، قابل اجرا هستند. اگر بر روی Context اجرا شدند، نوع موجودیت دریافتی به نوع DbSet متناظر به صورت خودکار نگاشت شده و استفاده میشود (context.Set<T>().Attach(entity)). یعنی در حقیقت بین این دو حالت تفاوتی نیست و امکان فراخوانی این متدها بر روی Context، صرفا جهت سهولت کار درنظر گرفته شدهاست.
تفاوت رفتار context.Entry در EF Core با EF 6.x
متد context.Entry در EF 6.x هم وجود دارد. اما در EF core سبب تغییر وضعیت گراف متصل به یک شیء نمیشود و ضعیت روابط آنرا به روز رسانی نمیکند (برخلاف EF 6.x). اگر در EF Core نیاز به یک چنین به روز رسانی گراف مانندی را داشتید، باید از متد جدید context.ChangeTracker.TrackGraph به نحو ذیل استفاده نمائید:
context.ChangeTracker.TrackGraph(blog, e => e.Entry.State = EntityState.Added);
کوئری گرفتن از سیستم ردیابی اطلاعات
این سناریوها را درنظر بگیرید:
- میخواهم سیستمی شبیه به تریگرهای اس کیوال سرور را با EF داشته باشم.
- میخواهم اطلاعات تمام رکوردهای ثبت شده، حذف شده و به روز رسانی شده را لاگ کنم.
- میخواهم پس از ثبت رکوردی در هر جای برنامه، شبیه به مباحث SQL Server Service Broker و SqlDependency بلافاصله مطلع شده و توسط SignalR اطلاع رسانی کنم.
و در حالت کلی میخواهم پیش و یا پس از ثبت اطلاعات، بتوانم به تغییرات صورت گرفته دسترسی داشته باشم و عملیاتی را بر روی آنها انجام دهم. تمام این موارد و سناریوها را با کوئری گرفتن از سیستم ردیابی اطلاعات EF میتوان پیاده سازی کرد.
برای نمونه در مطلب قبل و قسمت «طراحی یک کلاس پایه، بدون تنظیمات ارث بری روابط»، یک کلاس پایه را که مقادیر پیش فرض خود را از SQL Server دریافت میکند، طراحی کردیم. در اینجا میخواهیم با استفاده از سیستم ردیابی EF، طراحی این کلاس پایه را عمومی کرده و سازگار با تمام بانکهای اطلاعاتی موجود کنیم.
جهت یادآوری، کلاس پایه موجودیتها، یک چنین شکلی را داشته:
public class BaseEntity { public int Id { set; get; } public DateTime? DateAdded { set; get; } public DateTime? DateUpdated { set; get; } }
public class Person : BaseEntity { public string FirstName { get; set; } public string LastName { get; set; } }
public class ApplicationDbContext : DbContext { // same as before public override int SaveChanges() { this.ChangeTracker.DetectChanges(); var modifiedEntries = this.ChangeTracker .Entries<BaseEntity>() .Where(x => x.State == EntityState.Modified); foreach (var modifiedEntry in modifiedEntries) { modifiedEntry.Entity.DateUpdated = DateTime.UtcNow; } var addedEntries = this.ChangeTracker .Entries<BaseEntity>() .Where(x => x.State == EntityState.Added); foreach (var addedEntry in addedEntries) { addedEntry.Entity.DateAdded = DateTime.UtcNow; } return base.SaveChanges(); } }
در اینجا کار با کوئری گرفتن از خاصیت ChangeTracker شروع میشود. سپس باید مشخص کنیم چه نوع موجودیتهایی را مدنظر داریم. چون تمام موجودیتهای ما از کلاس پایهی BaseEntity مشتق میشوند، بنابراین کوئری گرفتن بر روی این نوع، به معنای دسترسی به تمام موجودیتهای برنامه نیز هست. سپس در اینجا اگر حالتی EntityState.Modified بود، فقط مقدار خاصیت DateUpdated را به صورت خودکار مقدار دهی میکنیم و اگر حالتی EntityState.Added بود، تنها مقدار خاصیت DateAdded را به روز رسانی خواهیم کرد.
در یک چنین حالتی دیگر نیازی نیست تا مقادیر این خواص را در حین ثبت اطلاعات برنامه به صورت دستی مشخص کنیم.
یک نکته: اگر به ابتدای متد بازنویسی شده دقت کنید، فراخوانی متد this.ChangeTracker.DetectChanges در آن انجام شدهاست. علت اینجا است که این فراخوانی به صورت خودکار توسط متد base.SaveChanges انجام میشود، اما چون این مرحله را تا انتهای متد بازنویسی شده، به تاخیر انداختهایم، نیاز است خودمان به صورت دستی سبب محاسبهی مجدد تغییرات صورت گرفته شویم.
نکتهای در مورد بهبود کیفیت کدهای متد SaveChanges: استفادهی Change Tracker به این صورت با بازنویسی متد SaveChanges بسیار مرسوم است. اما پس از مدتی به متد SaveChanges ایی خواهید رسید که کنترل آن از دست خارج میشود. به همین جهت برای EF 6.x پروژههایی مانند EFHooks طراحی شدهاند تا کپسوله سازی بهتری را بتوان ارائه داد. انتقال کدهای آن به EF Core کار مشکلی نیست و اصل آن، بازنویسی HookedDbContext آن است که نحوهی مدیریت شکیلتر کوئری گرفتن از ChangeTracker را بیان میکند.
خواص سایهای یا Shadow properties
EF Core به همراه مفهوم کاملا جدیدی است به نام خواص سایهای. این نوع خواص در سمت کدهای ما و در کلاسهای موجودیتهای برنامه وجود خارجی نداشته، اما در سمت جداول بانک اطلاعاتی وجود دارند و اکنون امکان کوئری گرفتن و کار کردن با آنها در EF Core میسر شدهاست.
برای تعریف آنها، بجای افزودن خاصیتی به کلاسهای برنامه، کار از متد OnModelCreating به نحو ذیل شروع میشود:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>().Property<DateTime>("DateAdded");
سپس برای کار کردن و کوئری گرفتن از آن میتوان از متد جدید EF.Property، به نحو ذیل استفاده کرد:
var blogs = context.Blogs.OrderBy(b => EF.Property<DateTime>(b, "DateAdded"));
context.Entry(myBlog).Property("DateAdded").CurrentValue = DateTime.Now;
foreach (var addedEntry in addedEntries) { addedEntry.Property("DateAdded").CurrentValue = DateTime.UtcNow; }
درخواست مستندات
<Report> <DocumentPreferences RunDirection="RTL" PageOrientation="Portrait" PageSize="A4"> <Watermark RunDirection="" Text="پس زمینه نمونه" Font="bardia.ttf" FillOpacity="0.7" StrokeOpacity="0.5" /> </DocumentPreferences> <DefaultFonts PrimaryFont="irsans.ttf" SecondaryFont="farnaz.ttf" Size="12px" Color="#CCFFBB" /> <DefaultFooter Message="پا صفحه نمونه" /> <DefaultHeader Message="سرصفحه نمونه" MessageFontColor="#ََََAA0012" ImagePath="logo.png" /> <MainTableTemplate TemplateName="AppleOrchardTemplate" /> <MainTablePreferences ColumnsWidthsType="Relative" NumberOfDataRowsPerPage="0" > <GroupsPreferences GroupType="HideGroupingColumns" ShowOneGroupPerPage="true" /> </MainTablePreferences> <MainTableSummarySettings OverallSummarySettings="جمع کل" PreviousPageSummarySettings="نقل از صفحه قبل" PageSummarySettings="جمع کل صفحه" AllGroupsSummarySettings="جمع کل گروه ها" /> <MainTableDataSource ProviderName="System.Data.SQLُServerCE" ConnectionString="Data Source=MyData.sdf;Persist Security Info=False;" SqlStatement="SELECT [url], [name], [NumberOfPosts], [AddDate] FROM [tblBlogs] WHERE [NumberOfPosts]>=@p1” > <Parameters> <Param Type="int" Name="@p1"/> </Parameters> </MainTableDataSource> <MainTableColumns> <Columns> <Column CellHorizontalAlignment="Right" ColumnItemsTemplate="TextBlock" HeaderCell="عنوان ستون 1" PropertyName="Title" Order="0" Width="1" /> <Column CellHorizontalAlignment="Right" Group="true" ColumnItemsTemplate="TextBlock" HeaderCell="عنوان ستون 2" PropertyName="Category" Order="1" Width="2" /> <Column CellHorizontalAlignment="Right" ColumnItemsTemplate="Image" HeaderCell="عنوان ستون 3" PropertyName="Image" Order="2" Width="2" /> </Columns> </MainTableColumns> <MainTableEvents> <DataSourceIsEmpty Message="داده ای برای نمایش وجود ندارد" /> </MainTableEvents> <Export ToExcel="True" ToCsv="False" ToXml="True" /> <Generate Type="AsPdfFile" FileName="Report.pdf" /> </Report>
- ضمنا نیازی نیست اطلاعات select را در سمت سرور تولید کنید. امکان دریافت JSON از سرور و تبدیل آن به فرمت مورد نظر در سمت کلاینت هم پیش بینی شدهاست:
editoptions: { dataUrl: '...url to get json....', buildSelect: function (response) { var data = typeof response === "string" ? $.parseJSON(response.responseText) : response, var s = "<select>"; s += '<option value="0">--No Manager--</option>'; $.each(data, function () { s += '<option value="' + this.EmployeeId + '">' + this.EmployeeName + '</option>'; }); return s + "</select>"; } }
گزارش درصد پیشرفت عملیات در اعمال غیرهمزمان
- اینترفیس جنریک IProgress واقع در فضای نام System
- کلاس جنریک Progress واقع در فضای نام System
در اینجا وهلهی از پیاده سازی اینترفیس IProgress به Task ارسال میشود. در این بین، عملیات در حال انجام با فراخوانی متد Report آن میتواند در هر زمانیکه نیاز باشد، درصد پیشرفت کار را گزارش کند.
namespace System { public interface IProgress<in T> { void Report( T value ); } }
namespace System { public class Progress<T> : IProgress<T> { public Progress(); public Progress( Action<T> handler ); protected virtual void OnReport( T value ); } }
یک مثال از گزارش درصد پیشرفت عملیات به همراه پشتیبانی از لغو آن
using System; using System.Threading; using System.Threading.Tasks; namespace Async09 { public class TestProgress { public async Task DoProcessingReportProgress() { var progress = new Progress<int>(percent => { Console.WriteLine(percent + "%"); }); var cts = new CancellationTokenSource(); // call some where cts.Cancel(); try { await doProcessing(progress, cts.Token); } catch (OperationCanceledException ex) { //todo: handle cancellations Console.WriteLine(ex); } Console.WriteLine("Done!"); } private static async Task doProcessing(IProgress<int> progress, CancellationToken ct) { await Task.Run(async () => { for (var i = 0; i != 100; ++i) { await Task.Delay(100, ct); if (progress != null) progress.Report(i); ct.ThrowIfCancellationRequested(); } }, ct); } } }
برای تدارک این وهله، از کلاس توکار Progress دات نت در متد public async Task DoProcessingReportProgress استفاده شدهاست.
این متد جنریک بوده و برای مثال نوع آن در اینجا int تعریف شدهاست. سازندهی آن میتواند یک callback را قبول کند. هر زمانیکه متد Report در متد doProcessing فراخوانی گردد، این callback در سمت کدهای استفاده کننده، فراخوانی خواهد شد. مثلا توسط مقدار آن میتوان یک Progress bar را نمایش داد.
به علاوه روش دیگری را در مورد لغو یک عملیات در اینجا ملاحظه میکنید. متد ThrowIfCancellationRequested نیز سبب خاتمهی عملیات میگردد؛ البته اگر در کدهای برنامه در جایی متد Cancel توکن، فراخوانی گردد. برای مثال یک دکمهی لغو عملیات در صفحه قرارگیرد و کار آن صرفا فراخوانی cts.Cancel باشد.
LocalDb دیتابیس توصیه شده برای ویژوال استودیو است و برای انواع پروژهها مانند اپلیکیشنهای وب میتواند استفاده شود. هنگام استفاده از این دیتابیس در IIS Express یا Cassini همه چیز طبق انتظار کار میکند. اما به محض آنکه بخواهید از آن در Full IIS استفاده کنید با خطاهایی مواجه میشوید. مقصود از Full IIS همان نسخه ای است که بهمراه ویندوز عرضه میشود و در قالب یک Windows Service اجرا میگردد.
هنگام استفاده از Full IIS دو خاصیت از LocalDb باعث بروز مشکل میشوند:
- LocalDb نیاز دارد پروفایل کاربر بارگذاری شده باشد
- بصورت پیش فرض، وهله LocalDb متعلق به یک کاربر بوده و خصوصی است
در ادامه این مقاله روی بارگذاری پروفایل کاربر تمرکز میکنیم، و در قسمت بعدی به مالکیت وهله LocalDb میپردازیم.
بارگذاری پروفایل کاربر
بگذارید دوباره به خطای موجود نگاهی بیاندازیم:
System.Data.SqlClient.SqlException: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 0 - [x89C50120])
این پیغام خطا زیاد مفید نیست، اما LocalDb اطلاعات بیشتری در Event Log ویندوز ذخیره میکند. اگر Windows Logs را باز کنید و به قسمت Application بروید پیغام زیر را مشاهده خواهید کرد.Reported at line: 400
بعلاوه این پیام خطا:
Cannot get a local application data path. Most probably a user profile is not loaded. If LocalDB is executed under IIS, make sure that profile loading is enabled for the current user.
به احتمال زیاد تعداد بیشتری از این دو خطا در تاریخچه وقایع وجود خواهد داشت، چرا که منطق کانکشن ADO.NET چند بار سعی میکند در بازههای مختلف به دیتابیس وصل شود.
پیغام خطای دوم واضح است، نیاز است پروفایل کاربر را بارگذاری کنیم. انجام اینکار زیاد مشکل نیست، هر Application Pool در IIS تنظیماتی برای بارگذاری پروفایل کاربر دارد که از قسمت Advanced Settings قابل دسترسی است. متاسفانه پس از انتشار سرویس پک 1 برای Windows 7 مسائل کمی پیچیدهتر شد. در حال حاظر فعال کردن loadUserProfile برای بارگذاری کامل پروفایل کاربر به تنهایی کافی نیست، و باید setProfileEnvironment را هم فعال کنیم. برای اطلاعات بیشتر در این باره به مستندات KB 2547655 مراجعه کنید. بدین منظور باید فایل applicationHost.config را ویرایش کنید. فایل مذکور در مسیر C:\Windows\System32\inetsrv\config قرار دارد. همانطور که در مستندات KB 2547655 توضیح داده شده، باید پرچم هر دو تنظیمات را برای ASP.NET 4.0 فعال کنیم:
<add name="ASP.NET v4.0" autoStart="true" managedRuntimeVersion="v4.0" managedPipelineMode="Integrated"> <processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" setProfileEnvironment="true" /> </add>
جای هیچ نگرانی نیست، چرا که این پیغام خطا انتظار میرود. همانطور که در ابتدا گفته شد، دو خاصیت LocalDb باعث بروز این خطاها میشوند و ما هنوز به خاصیت دوم نپرداخته ایم. بصورت پیش فرض وهلههای LocalDb خصوصی (private) هستند و در Windows account جاری اجرا میشوند. بنابراین ApplicationPoolIdentity در IIS به وهلههای دیتابیس دسترسی نخواهد داشت. در قسمت دوم این مقاله، راههای مختلفی را برای رفع این مشکل بررسی میکنیم.
10) Practical .NET: Powerful JavaScript With Upshot and Knockout
The Microsoft JavaScript Upshot library provides a simplified API for
retrieving data from the server and caching it at the client for reuse. Coupled
with Knockout, the two JavaScript libraries form the pillars of the Microsoft
client-side programming model.
9) On VB: Database Synchronization with the Microsoft Sync
Framework
The Microsoft Sync Framework is a highly flexible framework
for synchronizing files and data between a client and a master data store. With
great flexibility often comes complexity and confusion, however.
8) C# Corner: Performance Tips for Asynchronous Development in
C#
Visual Studio Async is a powerful development framework, but it's
important to understand how it works to avoid performance hits.
7) 2 Great JavaScript Data-Binding Libraries
JavaScript
libraries help you build powerful, data-driven HTML5 apps.
6) On VB: Entity Framework Code-First Migrations
Code First
Migrations allow for database changes to be implemented all through code.
Through the use of Package Manager Console (PMC), commands can be used to
scaffold database changes.
5) C# Corner: The New Read-Only Collections in .NET 4.5
Some
practical uses for the long-awaited interfaces, IReadOnlyList and
IReadOnlyDictionary, in .NET Framework 4.5.
4) C# Corner: Building a Windows 8 RSS Reader
Eric Vogel
walks through a soup-to-nuts demo for building a Metro-style RSS reader.
3) C# Corner: The Build Pattern in .NET
How to separate
complex object construction from its representation using the Builder design
pattern in C#.
2) Inside Visual Studio 11: A Guided Tour
Visual Studio 2012
(code-named Visual Studio 11 then) is packed with new features to help you be a
more efficient, productive developer. Here's your guided tour.
1) HTML5 for ASP.NET Developers
The technologies bundled as
HTML5 finally support what developers have been trying to get HTML to do for
decades.