تشخیص تفاوت بین IIS سرورهای مختلف
I commonly hear the phrase “The web application worked in the pre-production environment and now is encountering issues in production and the server’s configuration are identical!” when I appear onsite to help assist with the resolution of the issues. Upon further investigation, an IIS module has not been installed on the production server, or the configuration is different for an application pool setting between the pre-production and production environments. This is a very common scenario I encounter in the field and here are some suggestions on how to determine differences between IIS servers in an IIS farm environment or between servers in different environments, such as pre-production and production. Keeping server configuration and content synchronized is always a challenge and I hope these suggestions help out.
SQL Server CTE Basics
در اینجا هم ManagerID int NULL تعریف شده.
سفر به Angular بخش اول
In the eighteen years that I’ve been doing Web development, a lot has changed. We started out creating HTML pages to present static information to our users. We then used classic ASP to get database data and incorporate that into our pages. To use both of these technologies, we had to know a lot about HTML, CSS, and JavaScript. Along came .NET and we started rendering everything on the server-side. We forgot a lot about HTML, CSS, and JavaScript as Web Forms wrapped up a lot of that for us. Web Forms’ architecture closely mimicked the way developers created desktop applications. This was great for helping developers move to the Web, but unfortunately hid a lot of the power of the Web, and also tended to be a little slow.
EF Code First #10
حین کار با ORMهای پیشرفته، ویژگیهای جالب توجهی در اختیار برنامه نویسها قرار میگیرد که در زمان استفاده از کلاسهای متداول SQLHelper از آنها خبری نیست؛ مانند:
الف) Deferred execution
ب) Lazy loading
ج) Eager loading
نحوه بررسی SQL نهایی تولیدی توسط EF
برای توضیح موارد فوق، نیاز به مشاهده خروجی SQL نهایی حاصل از ORM است و همچنین شمارش تعداد بار رفت و برگشت به بانک اطلاعاتی. بهترین ابزاری را که برای این منظور میتوان پیشنهاد داد، برنامه EF Profiler است. برای دریافت آن میتوانید به این آدرس مراجعه کنید: (^) و (^)
پس از وارد کردن نام و آدرس ایمیل، یک مجوز یک ماهه آزمایشی، به آدرس ایمیل شما ارسال خواهد شد.
زمانیکه این فایل را در ابتدای اجرای برنامه به آن معرفی میکنید، محل ذخیره سازی نهایی آن جهت بازبینی بعدی، مسیر MyUserName\Local Settings\Application Data\EntityFramework Profiler خواهد بود.
استفاده از این برنامه هم بسیار ساده است:
الف) در برنامه خود، ارجاعی را به اسمبلی HibernatingRhinos.Profiler.Appender.dll که در پوشه برنامه EFProf موجود است، اضافه کنید.
ب) در نقطه آغاز برنامه، متد زیر را فراخوانی نمائید:
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
نقطه آغاز برنامه میتواند متد Application_Start برنامههای وب، در متد Program.Main برنامههای ویندوزی کنسول و WinForms و در سازنده کلاس App برنامههای WPF باشد.
ج) برنامه EFProf را اجرا کنید.
مزایای استفاده از این برنامه
1) وابسته به بانک اطلاعاتی مورد استفاده نیست. (برخلاف برای مثال برنامه معروف SQL Server Profiler که فقط به همراه SQL Server ارائه میشود)
2) خروجی SQL نمایش داده شده را فرمت کرده و به همراه Syntax highlighting نیز هست.
3) کار این برنامه صرفا به لاگ کردن SQL تولیدی خلاصه نمیشود. یک سری از Best practices را نیز به شما گوشزد میکند. بنابراین اگر نیاز دارید سیستم خود را بر اساس دیدگاه یک متخصص بررسی کنید (یک Code review ارزشمند)، این ابزار میتواند بسیار مفید باشد.
4) میتواند کوئریهای سنگین و سبک را به خوبی تشخیص داده و گزارشات آماری جالبی را به شما ارائه دهد.
5) میتواند دقیقا مشخص کند، کوئری را که مشاهده میکنید از طریق کدام متد در کدام کلاس صادر شده است و دقیقا از چه سطری.
6) امکان گروه بندی خودکار کوئریهای صادر شده را بر اساس DbContext مورد استفاده به همراه دارد.
و ...
استفاده از این برنامه حین کار با EF «الزامی» است! (البته نسخههای NH و سایر ORMهای دیگر آن نیز موجود است و این مباحث در مورد تمام ORMهای پیشرفته صادق است)
مدام باید بررسی کرد که صفحه جاری چه تعداد کوئری را به بانک اطلاعاتی ارسال کرده و به چه نحوی. همچنین آیا میتوان با اعمال اصلاحاتی، این وضع را بهبود بخشید. بنابراین عدم استفاده از این برنامه حین کار با ORMs، همانند راه رفتن در خواب است! ممکن است تصور کنید برنامه دارد به خوبی کار میکند اما ... در پشت صحنه فقط صفحه جاری برنامه، 100 کوئری را به بانک اطلاعاتی ارسال کرده، در حالیکه شما تنها نیاز به یک کوئری داشتهاید.
کلاسهای مدل مثال جاری
کلاسهای مدل مثال جاری از یک دپارتمان که دارای تعدادی کارمند میباشد، تشکیل شده است. ضمنا هر کارمند تنها در یک دپارتمان میتواند مشغول به کار باشد و رابطه many-to-many نیست :
using System.Collections.Generic;
namespace EF_Sample06.Models
{
public class Department
{
public int DepartmentId { get; set; }
public string Name { get; set; }
//Creates Employee navigation property for Lazy Loading (1:many)
public virtual ICollection<Employee> Employees { get; set; }
}
}
namespace EF_Sample06.Models
{
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
//Creates Department navigation property for Lazy Loading
public virtual Department Department { get; set; }
}
}
نگاشت دستی این کلاسها هم ضرورتی ندارد، زیرا قراردادهای توکار EF Code first را رعایت کرده و EF در اینجا به سادگی میتواند primary key و روابط one-to-many را بر اساس navigation properties تعریف شده، تشخیص دهد.
در اینجا کلاس Context برنامه به شرح زیر است:
using System.Data.Entity;
using EF_Sample06.Models;
namespace EF_Sample06.DataLayer
{
public class Sample06Context : DbContext
{
public DbSet<Department> Departments { set; get; }
public DbSet<Employee> Employees { set; get; }
}
}
و تنظیمات ابتدایی نحوه به روز رسانی و آغاز بانک اطلاعاتی نیز مطابق کدهای زیر میباشد:
using System.Collections.Generic;
using System.Data.Entity.Migrations;
using EF_Sample06.Models;
namespace EF_Sample06.DataLayer
{
public class Configuration : DbMigrationsConfiguration<Sample06Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(Sample06Context context)
{
var employee1 = new Employee { FirstName = "f name1", LastName = "l name1" };
var employee2 = new Employee { FirstName = "f name2", LastName = "l name2" };
var employee3 = new Employee { FirstName = "f name3", LastName = "l name3" };
var employee4 = new Employee { FirstName = "f name4", LastName = "l name4" };
var dept1 = new Department { Name = "dept 1", Employees = new List<Employee> { employee1, employee2 } };
var dept2 = new Department { Name = "dept 2", Employees = new List<Employee> { employee3 } };
var dept3 = new Department { Name = "dept 3", Employees = new List<Employee> { employee4 } };
context.Departments.Add(dept1);
context.Departments.Add(dept2);
context.Departments.Add(dept3);
base.Seed(context);
}
}
}
نکته: تهیه خروجی XML از نگاشتهای خودکار تهیه شده
اگر علاقمند باشید که پشت صحنه نگاشتهای خودکار EF Code first را در یک فایل XML جهت بررسی بیشتر ذخیره کنید، میتوان از متد کمکی زیر استفاده کرد:
void ExportMappings(DbContext context, string edmxFile)
{
var settings = new XmlWriterSettings { Indent = true };
using (XmlWriter writer = XmlWriter.Create(edmxFile, settings))
{
System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(context, writer);
}
}
بهتر است پسوند فایل XML تولیدی را edmx قید کنید تا بتوان آنرا با دوبار کلیک بر روی فایل، در ویژوال استودیو نیز مشاهده کرد:
using (var db = new Sample06Context())
{
ExportMappings(db, "mappings.edmx");
}
الف) بررسی Deferred execution یا بارگذاری به تاخیر افتاده
برای توضیح مفهوم Deferred loading/execution بهترین مثالی را که میتوان ارائه داد، صفحات جستجوی ترکیبی در برنامهها است. برای مثال یک صفحه جستجو را طراحی کردهاید که حاوی دو تکست باکس دریافت FirstName و LastName کاربر است. کنار هر کدام از این تکست باکسها نیز یک چکباکس قرار دارد. به عبارتی کاربر میتواند جستجویی ترکیبی را در اینجا انجام دهد. نحوه پیاده سازی صحیح این نوع مثالها در EF Code first به چه نحوی است؟
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample06.DataLayer;
using EF_Sample06.Models;
namespace EF_Sample06
{
class Program
{
static IList<Employee> FindEmployees(string fName, string lName, bool byName, bool byLName)
{
using (var db = new Sample06Context())
{
IQueryable<Employee> query = db.Employees.AsQueryable();
if (byLName)
{
query = query.Where(x => x.LastName == lName);
}
if (byName)
{
query = query.Where(x => x.FirstName == fName);
}
return query.ToList();
}
}
static void Main(string[] args)
{
// note: remove this line if you received : create database is not supported by this provider.
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample06Context, Configuration>());
var list = FindEmployees("f name1", "l name1", true, true);
foreach (var item in list)
{
Console.WriteLine(item.FirstName);
}
}
}
}
نحوه صحیح این نوع پیاده سازی ترکیبی را در متد FindEmployees مشاهده میکنید. نکته مهم آن، استفاده از نوع IQueryable و متد AsQueryable است و امکان ترکیب کوئریها با هم.
به نظر شما با فراخوانی متد FindEmployees به نحو زیر که هر دو شرط آن توسط کاربر انتخاب شده است، چه تعداد کوئری به بانک اطلاعاتی ارسال میشود؟
var list = FindEmployees("f name1", "l name1", true, true);
شاید پاسخ دهید که سه بار : یکبار در متد db.Employees.AsQueryable و دوبار هم در حین ورود به بدنه شرطهای یاد شده و اینجا است که کسانی که قبلا با رویههای ذخیره شده کار کرده باشند، شروع به فریاد و فغان میکنند که ما قبلا این مسایل رو با یک SP در یک رفت و برگشت مدیریت میکردیم!
پاسخ صحیح: «فقط یکبار»! آنهم تنها در زمان فراخوانی متد ToList و نه قبل از آن.
برای اثبات این مدعا نیاز است به خروجی SQL لاگ شده توسط EF Profiler مراجعه کرد:
SELECT [Extent1].[EmployeeId] AS [EmployeeId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM [dbo].[Employees] AS [Extent1]
WHERE ([Extent1].[LastName] = 'l name1' /* @p__linq__0 */)
AND ([Extent1].[FirstName] = 'f name1' /* @p__linq__1 */)
IQueryable قلب LINQ است و تنها بیانگر یک عبارت (expression) از رکوردهایی میباشد که مد نظر شما است و نه بیشتر. برای مثال زمانیکه یک IQueryable را همانند مثال فوق فیلتر میکنید، هنوز چیزی از بانک اطلاعاتی یا منبع دادهای دریافت نشده است. هنوز هیچ اتفاقی رخ نداده است و هنوز رفت و برگشتی به منبع دادهای صورت نگرفته است. به آن باید به شکل یک expression builder نگاه کرد و نه لیستی از اشیاء فیلتر شدهی ما. به این مفهوم، deferred execution (اجرای به تاخیر افتاده) نیز گفته میشود.
کوئری LINQ شما تنها زمانی بر روی بانک اطلاعاتی اجرا میشود که کاری بر روی آن صورت گیرد مانند فراخوانی متد ToList، فراخوانی متد First یا FirstOrDefault و امثال آن. تا پیش از این فقط به شکل یک عبارت در برنامه وجود دارد و نه بیشتر.
اطلاعات بیشتر: «تفاوت بین IQueryable و IEnumerable در حین کار با ORMs»
ب) بررسی Lazy Loading یا واکشی در صورت نیاز
در مطلب جاری اگر به کلاسهای مدل برنامه دقت کنید، تعدادی از خواص به صورت virtual تعریف شدهاند. چرا؟
تعریف یک خاصیت به صورت virtual، پایه و اساس lazy loading است و به کمک آن، تا به اطلاعات شیءایی نیاز نباشد، وهله سازی نخواهد شد. به این ترتیب میتوان به کارآیی بیشتری در حین کار با ORMs رسید. برای مثال در کلاسهای فوق، اگر تنها نیاز به دریافت نام یک دپارتمان هست، نباید حین وهله سازی از شیء دپارتمان، شیء لیست کارمندان مرتبط با آن نیز وهله سازی شده و از بانک اطلاعاتی دریافت شوند. به این وهله سازی با تاخیر، lazy loading گفته میشود.
Lazy loading پیاده سازی سادهای نداشته و مبتنی است بر بکارگیری AOP frameworks یا کتابخانههایی که امکان تشکیل اشیاء Proxy پویا را در پشت صحنه فراهم میکنند. علت virtual تعریف کردن خواص رابط نیز به همین مساله بر میگردد، تا این نوع کتابخانهها بتوانند در نحوه تعریف اینگونه خواص virtual در زمان اجرا، در پشت صحنه دخل و تصرف کنند. البته حین استفاده از EF یا انواع و اقسام ORMs دیگر با این نوع پیچیدگیها روبرو نخواهیم شد و تشکیل اشیاء Proxy در پشت صحنه انجام میشوند.
یک مثال: قصد داریم اولین دپارتمان ثبت شده در حین آغاز برنامه را یافته و سپس لیست کارمندان آنرا نمایش دهیم:
using (var db = new Sample06Context())
{
var dept1 = db.Departments.Find(1);
if (dept1 != null)
{
Console.WriteLine(dept1.Name);
foreach (var item in dept1.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}
رفتار یک ORM جهت تعیین اینکه آیا نیاز است برای دریافت اطلاعات بین جداول Join صورت گیرد یا خیر، واکشی حریصانه و غیرحریصانه را مشخص میسازد.
در حالت واکشی حریصانه به ORM خواهیم گفت که لطفا جهت دریافت اطلاعات فیلدهای جداول مختلف، از همان ابتدای کار در پشت صحنه، Join های لازم را تدارک ببین. در حالت واکشی غیرحریصانه به ORM خواهیم گفت به هیچ عنوان حق نداری Join ایی را تشکیل دهی. هر زمانی که نیاز به اطلاعات فیلدی از جدولی دیگر بود باید به صورت مستقیم به آن مراجعه کرده و آن مقدار را دریافت کنی.
به صورت خلاصه برنامه نویس در حین کار با ORM های پیشرفته نیازی نیست Join بنویسد. تنها باید ORM را طوری تنظیم کند که آیا اینکار را حتما خودش در پشت صحنه انجام دهد (واکشی حریصانه)، یا اینکه خیر، به هیچ عنوان SQL های تولیدی در پشت صحنه نباید حاوی Join باشند (lazy loading).
در مثال فوق به صورت خودکار دو کوئری به بانک اطلاعاتی ارسال میگردد:
SELECT [Limit1].[DepartmentId] AS [DepartmentId],
[Limit1].[Name] AS [Name]
FROM (SELECT TOP (2) [Extent1].[DepartmentId] AS [DepartmentId],
[Extent1].[Name] AS [Name]
FROM [dbo].[Departments] AS [Extent1]
WHERE [Extent1].[DepartmentId] = 1 /* @p0 */) AS [Limit1]
SELECT [Extent1].[EmployeeId] AS [EmployeeId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM [dbo].[Employees] AS [Extent1]
WHERE ([Extent1].[Department_DepartmentId] IS NOT NULL)
AND ([Extent1].[Department_DepartmentId] = 1 /* @EntityKeyValue1 */)
یکبار زمانیکه قرار است اطلاعات دپارتمان یک (db.Departments.Find) دریافت شود. تا این لحظه خبری از جدول Employees نیست. چون lazy loading فعال است و فقط اطلاعاتی را که نیاز داشتهایم فراهم کرده است.
زمانیکه برنامه به حلقه میرسد، نیاز است اطلاعات dept1.Employees را دریافت کند. در اینجا است که کوئری دوم، به بانک اطلاعاتی صادر خواهد شد (بارگذاری در صورت نیاز).
ج) بررسی Eager Loading یا واکشی حریصانه
حالت lazy loading بسیار جذاب به نظر میرسد؛ برای مثال میتوان خواص حجیم یک جدول را به جدول مرتبط دیگری منتقل کرد. مثلا فیلدهای متنی طولانی یا اطلاعات باینری فایلهای ذخیره شده، تصاویر و امثال آن. به این ترتیب تا زمانیکه نیازی به اینگونه اطلاعات نباشد، lazy loading از بارگذاری آنها جلوگیری کرده و سبب افزایش کارآیی برنامه میشود.
اما ... همین lazy loading در صورت استفاده نا آگاهانه میتواند سرور بانک اطلاعاتی را در یک برنامه چندکاربره از پا درآورد! نیازی هم نیست تا شخصی به سایت شما حمله کند. مهاجم اصلی همان برنامه نویس کم اطلاع است!
اینبار مثال زیر را درنظر بگیرید که بجای دریافت اطلاعات یک شخص، مثلا قصد داریم، اطلاعات کلیه دپارتمانها را توسط یک Grid نمایش دهیم (فرقی نمیکند برنامه وب یا ویندوز باشد؛ اصول یکی است):
using (var db = new Sample06Context())
{
foreach (var dept in db.Departments)
{
Console.WriteLine(dept.Name);
foreach (var item in dept.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}
There is already an open DataReader associated with this Command which must be closed first
برای رفع این مشکل نیاز است گزینه MultipleActiveResultSets=True را به کانکشن استرینگ اضافه کرد:
<connectionStrings>
<clear/>
<add
name="Sample06Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true;MultipleActiveResultSets=True;"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
سؤال: به نظر شما در دو حلقه تو در توی فوق چندبار رفت و برگشت به بانک اطلاعاتی صورت میگیرد؟ با توجه به اینکه در متد Seed ذکر شده در ابتدای مطلب، تعداد رکوردها مشخص است.
پاسخ: 7 بار!
و اینجا است که عنوان شد استفاده از EF Profiler در حین توسعه برنامههای مبتنی بر ORM «الزامی» است! اگر از این نکته اطلاعی نداشتید، بهتر است یکبار تمام صفحات گزارشگیری برنامههای خود را که حاوی یک Grid هستند، توسط EF Profiler بررسی کنید. اگر در این برنامه پیغام خطای n+1 select را دریافت کردید، یعنی در حال استفاده ناصحیح از امکانات lazy loading میباشید.
آیا میتوان این وضعیت را بهبود بخشید؟ زمانیکه کار ما گزارشگیری از اطلاعات با تعداد رکوردهای بالا است، استفاده ناصحیح از ویژگی Lazy loading میتواند به شدت کارآیی بانک اطلاعاتی را پایین بیاورد. برای حل این مساله در زمانهای قدیم (!) بین جداول join مینوشتند؛ الان چطور؟
در EF متدی به نام Include جهت Eager loading اطلاعات موجودیتهای مرتبط به هم درنظر گرفته شده است که در پشت صحنه همینکار را انجام میدهد:
using (var db = new Sample06Context())
{
foreach (var dept in db.Departments.Include(x => x.Employees))
{
Console.WriteLine(dept.Name);
foreach (var item in dept.Employees)
{
Console.WriteLine(item.FirstName);
}
}
}
همانطور که ملاحظه میکنید اینبار به کمک متد Include، نسبت به واکشی حریصانه Employees اقدام کردهایم. اکنون اگر برنامه را اجرا کنیم، فقط یک رفت و برگشت به بانک اطلاعاتی انجام خواهد شد و کار Join نویسی به صورت خودکار توسط EF مدیریت میگردد:
SELECT [Project1].[DepartmentId] AS [DepartmentId],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
[Project1].[EmployeeId] AS [EmployeeId],
[Project1].[FirstName] AS [FirstName],
[Project1].[LastName] AS [LastName],
[Project1].[Department_DepartmentId] AS [Department_DepartmentId]
FROM (SELECT [Extent1].[DepartmentId] AS [DepartmentId],
[Extent1].[Name] AS [Name],
[Extent2].[EmployeeId] AS [EmployeeId],
[Extent2].[FirstName] AS [FirstName],
[Extent2].[LastName] AS [LastName],
[Extent2].[Department_DepartmentId] AS [Department_DepartmentId],
CASE
WHEN ([Extent2].[EmployeeId] IS NULL) THEN CAST(NULL AS int)
ELSE 1
END AS [C1]
FROM [dbo].[Departments] AS [Extent1]
LEFT OUTER JOIN [dbo].[Employees] AS [Extent2]
ON [Extent1].[DepartmentId] = [Extent2].[Department_DepartmentId]) AS [Project1]
ORDER BY [Project1].[DepartmentId] ASC,
[Project1].[C1] ASC
متد Include در نگارشهای اخیر EF پیشرفت کرده است و همانند مثال فوق، امکان کار با lambda expressions را جهت تعریف خواص مورد نظر به صورت strongly typed ارائه میدهد. در نگارشهای قبلی این متد، تنها امکان استفاده از رشتهها برای معرفی خواص وجود داشت.
همچنین توسط متد Include امکان eager loading چندین سطح با هم نیز وجود دارد؛ مثلا x.Employees.Kids و همانند آن.
چند نکته در مورد نحوه خاموش کردن Lazy loading
امکان خاموش کردن Lazy loading در تمام کلاسهای برنامه با تنظیم خاصیت Configuration.LazyLoadingEnabled کلاس Context برنامه به نحو زیر میسر است:
public class Sample06Context : DbContext
{
public Sample06Context()
{
this.Configuration.LazyLoadingEnabled = false;
}
یا اگر تنها در مورد یک کلاس نیاز است این خاموش سازی صورت گیرد، کلمه کلیدی virtual را حذف کنید. برای مثال با نوشتن public ICollection<Employee> Employees بجای public virtual ICollection<Employee> Employees در اولین بار وهله سازی کلاس دپارتمان، لیست کارمندان آن به نال تنظیم میشود. البته در این حالت null object pattern را نیز فراموش نکنید (وهله سازی پیش فرض Employees در سازنده کلاس):
public class Department
{
public int DepartmentId { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
public Department()
{
Employees = new HashSet<Employee>();
}
}
به این ترتیب به خطای null reference object بر نخواهیم خورد. همچنین وهله سازی، با مقدار دهی لیست دریافتی از بانک اطلاعاتی متفاوت است. در اینجا نیز باید از متد Include استفاده کرد.
بنابراین در صورت خاموش کردن lazy loading، حتما نیاز است از متد Include استفاده شود. اگرlazy loading فعال است، جهت تبدیل آن به eager loading از متد Include استفاده کنید (اما اجباری نیست).
These are the customer-reported issues addressed in this version:
Visual Studio crashes when you open a solution with a test project.
Visual Studio Freezes in Debug with Chrome.
Failure to install HelpViewer.
UI delay while typing R code.
C# 7.0 Regression in Tuples.
Xamarin - Dynamic Type Platform not supported exception.
Xamarin – Dynamic object is not supported.
Xamarin - Xamarin.iOS: ArgumentNullException for instruction parameter in Mono.Linker's MarkException() method.
نکاتی از SQL که به نظر ممکن نمیرسند
نحوه اضافه کردن چند پروژه در vs code
Visual Studio Code doesn't support solution files, whereas Visual Studio does. In Visual Studio we use solution files to link multiple projects together, and at first I thought that this was going to be a deal breaker for Visual Studio Code - but it's not at all. Many people aren't aware that Visual Studio Code, although lightweight, is a powerful editor that can do most of the things you need.