C# Application From Start to Finish: Tournament Tracker Course - YouTube
28 videos, 192,192 views, Last updated on Jan 13, 2019
Follow along in this free course as I show you how to create an application in C# from idea through the finished product. Everything is shown on screen and in great detail. Learn how to use SQL databases, CSV text files, custom events, Linq, Lambda expressions, emailing, and more. Everything you learn will be in context of a real application.
var query = breeze.EntityQuery .from("Customers") .where("CompanyName", "startsWith", "A") .orderBy("CompanyName");
var promise = manager.executeQuery(query) .then(querySucceeded) .fail(queryFailed);
if (manager.hasChanges()) { manager.saveChanges().then(saveSucceeded).fail(saveFailed); } // listen for any change to a customer customer.entityAspect.propertyChanged.subscribe(somethingHappened);
<!-- Angular template --> <li data-ng-repeat="emp in employees"> <label>{{emp.FirstName}}</label> <label>{{emp.LastName}}</label> </li> // bound to employees from query manager.executeQuery(breeze.EntityQuery.from("Employees")) .then(function(data) { $scope.employees = data.results; });
/* Query with related entities using expand */ // query for orders of customers whose name begins "Alfreds" // include their customers & child details & their detail products breeze.EntityQuery.from("Orders") .where("Customer.CompanyName", "startsWith", "Alfreds") .expand("Customer, OrderDetails.Product") .using(manager) .execute().then(querySucceeded).fail(queryFailed);
// execute query asynchronously on the server manager.executeQuery(query).then(querySuccess).fail(queryFail); // execute query synchronously on local cache var customers = manager.executeQueryLocally(query)
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>http://www.example.com/</loc> <lastmod>2005-01-01</lastmod> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> </urlset>
که یک فایل XML متشکل از یک تگ urlset است و این تگ نیز حاوی یک یا چند تگ url میباشد. با توجه به تعاریف بالا به یک چنین کلاسی خواهیم رسید:
public enum ChangeFreq { Always, Hourly, Daily, Weekly, Monthly, Yearly, Never } [XmlElement("loc")] public string Url { get; set; } [XmlElement("lastmod")] public DateTime? LastModified { get; set; } public bool ShouldSerializeLastModified() { return LastModified.HasValue; } [XmlElement("changefreq")] public ChangeFreq? ChangeFrequency { get; set; } public bool ShouldSerializeChangeFrequency() { return ChangeFrequency.HasValue; } [XmlElement("priority")] public float? Priority { get; set; } public bool ShouldSerializePriority() { return Priority.HasValue; } }
دقت داشته باشید که چون پروپرتیهای LastModified ، ChangeFrequency و Priority از نوع Nullable تعریف شدهاند، پس باید کاری کنیم در صورتیکه این پروپرتیها نال بودند سریالیز نشوند. بدین منظور از تابع ShouldSerialize[MemberName] استفاده میشود. این تابع جزئی از دات نت است. کافی است بعد از ShouldSerialize نام پروپرتی را ذکر کنید. حال به کلاس دیگری نیاز داریم تا لیستی از کلاس فوق را دربر داشته باشد.
[XmlRoot("urlset",Namespace = "http://www.sitemaps.org/schemas/sitemap/0.9")] public class SiteMp { private readonly List<Location> _locations; public SiteMp() { _locations = new List<Location>(); } [XmlElement("url")] public List<Location> Locations { get { return _locations; } set { foreach (var location in value) { Add(location); } } } public void Add(Location location) { _locations.Add(location); } }
حال برای پردازش کلاس بالا لازم است ActionResultی را طراحی کنیم تا خروجی Response را به فرمت XML پردازش کند:
public class XmlResult : ActionResult { private readonly object _objectToSerialize; public XmlResult(object objectToSerialize) { _objectToSerialize = objectToSerialize; } public override void ExecuteResult(ControllerContext context) { if (_objectToSerialize == null) return; context.HttpContext.Response.Clear(); var xmlSerializer = new XmlSerializer(_objectToSerialize.GetType()); context.HttpContext.Response.ContentType = "text/xml"; xmlSerializer.Serialize(context.HttpContext.Response.Output, _objectToSerialize); } }
و در آخر یک کنترلر ساخته و به صورت زیر از آن استفاده میکنیم:
public class SiteMapController : Controller { // GET: SiteMap public ActionResult Index() { SiteMp siteMap = new SiteMp(); siteMap.Add(new Location { Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/Home/Index" }); siteMap.Add(new Location { Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/Home/NewRequest", ChangeFrequency = Location.ChangeFreq.Always, LastModified = DateTime.UtcNow, Priority = 0.5f }); siteMap.Add(new Location { Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/Home/FindRequest", ChangeFrequency = Location.ChangeFreq.Always, LastModified = DateTime.UtcNow, Priority = 0.5f }); siteMap.Add(new Location { Url = Request.Url.GetLeftPart(UriPartial.Authority) + "/ContactUs/Index", ChangeFrequency = Location.ChangeFreq.Daily, LastModified = DateTime.UtcNow, Priority = 0.5f }); return new XmlResult(siteMap); }
اگر دقت کنید لینکهای ثابت باید به صورت دستی اضافه شوند. سناریویی را تصور کنید که لینکها زیاد باشند(جدای از لینک هایی که از دیتابیس لود میشوند) این کار کمی ناجور به نظر میرسد. در اینجا میخواهیم از طریق امکانات ،Reflection عمل اضافه کردن لینک به صورت خودکار انجام شود.
public class ControllerScanner { public static List<string> ScanAllControllers(HttpRequestBase requestBase) { Assembly asm = Assembly.GetAssembly(typeof(MvcApplication)); var controllerActionlist = asm.GetTypes() .Where(type => typeof (Controller).IsAssignableFrom(type)) .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public)) .Where((returnType => returnType.ReturnType == (typeof(ViewResult)) || returnType.ReturnType==(typeof(ActionResult)))) .Select( x => new { Controller = x.DeclaringType.Name, Action = x.Name, ReturnType = x.ReturnType.Name }) .OrderBy(x => x.Controller).ThenBy(x => x.Action).Distinct().ToList(); if (requestBase.Url == null) return null; var url = requestBase.Url.GetLeftPart(UriPartial.Authority); return controllerActionlist.Select(controller => $"{url}/{controller.Controller}/{controller.Action}").ToList(); } }
حال از کلاس بالا در کنترلر SiteMap به صورت زیر استفاده میکنیم :
public class SiteMapController : Controller { // GET: SiteMap public ActionResult Index() { var siteMap = new SiteMap(); var controllers = ControllerScanner.ScanAllControllers(Request); foreach (var controller in controllers) { siteMap.Add(new Location { Url = controller, ChangeFrequency = Location.ChangeFreq.Always, LastModified = DateTime.UtcNow, Priority = 0.5f }); } return new XmlResult(siteMap); } }
در آخر نیز سطر زیر را به سیستم مسیریابی اضافه نمایید تا در صورت درخواست فایل sitemap.xml اکشن Index از کنترلر SiteMap فراخوانی شود.
routes.MapRoute( "SiteMap", // Route name "sitemap.xml", // URL with parameters new { controller = "Sitemap", action = "Index", name = UrlParameter.Optional, area = "" } );
کلاس EfExceptionsInterceptor
در اینجا نمونهای از یک پیاده سازی اینترفیس IDbCommandInterceptor را مشاهده میکنید. همچنین طراحی یک متد عمومی که میتواند به جزئیات SQL نهایی و پارامترهای آن دسترسی داشته باشد، در اینترفیس IEfExceptionsLogger ذکر شدهاست.
public interface IEfExceptionsLogger { void LogException<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext); } using System.Data.Common; using System.Data.Entity.Infrastructure.Interception; namespace ElmahEFLogger { public class EfExceptionsInterceptor : IDbCommandInterceptor { private readonly IEfExceptionsLogger _efExceptionsLogger; public EfExceptionsInterceptor(IEfExceptionsLogger efExceptionsLogger) { _efExceptionsLogger = efExceptionsLogger; } public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { _efExceptionsLogger.LogException(command, interceptionContext); } public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { _efExceptionsLogger.LogException(command, interceptionContext); } public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { _efExceptionsLogger.LogException(command, interceptionContext); } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { _efExceptionsLogger.LogException(command, interceptionContext); } public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { _efExceptionsLogger.LogException(command, interceptionContext); } public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { _efExceptionsLogger.LogException(command, interceptionContext); } } }
تهیه یک پیاده سازی سفارشی از IEfExceptionsLogger توسط ELMAH
اکنون که ساختار کلی IDbCommandInterceptor سفارشی برنامه مشخص شد، میتوان پیاده سازی خاصی از آنرا جهت استفاده از ELMAH به نحو ذیل ارائه داد:
using System; using System.Data.Common; using System.Data.Entity.Infrastructure.Interception; using Elmah; namespace ElmahEFLogger.CustomElmahLogger { public class ElmahEfExceptionsLogger : IEfExceptionsLogger { /// <summary> /// Manually log errors using ELMAH /// </summary> public void LogException<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext) { var ex = interceptionContext.OriginalException; if (ex == null) return; var sqlData = CommandDumper.LogSqlAndParameters(command, interceptionContext); var contextualMessage = string.Format("{0}{1}OriginalException:{1}{2} {1}", sqlData, Environment.NewLine, ex); if (!string.IsNullOrWhiteSpace(contextualMessage)) { ex = new Exception(contextualMessage, new ElmahEfInterceptorException(ex.Message)); } try { ErrorSignal.FromCurrentContext().Raise(ex); } catch { ErrorLog.GetDefault(null).Log(new Error(ex)); } } } }
استفاده از ElmahEfExceptionsLogger جهت طراحی یک Interceptor عمومی
public class ElmahEfInterceptor : EfExceptionsInterceptor { public ElmahEfInterceptor() : base(new ElmahEfExceptionsLogger()) { } }
DbInterception.Add(new ElmahEfInterceptor());
پس از آن جزئیات کلیه استثناهای EF در لاگهای نهایی ELMAH به نحو ذیل ظاهر خواهند شد:
کدهای کامل این پروژه را از اینجا میتوانید دریافت کنید:
ElmahEFLogger
where T:clas where T:struc ...
همانطور که مطلع هستید هر عنصری جنریکی را که تعریف میکنید حداقل دارای یک پارامتر نوع هست و در زمان بکارگیری آن جنریک باید نوع آن را مشخص نمایید. برای نمونه مثال زیر را در نظر بگیرید :
public class MyCollection<T> { private List<T> collections = new List<T>(); public void Add(T value) { collections.Add(value); } }
MyCollection<int> myintObj = new MyCollection<int>(); myintObj.Add(12); myintObj.Add(33); myintObj.Add(33.3);// ERROR z
ایجاد قیدها یا محدودیتها بر روی پارامترهای جنریکها شامل پنج حالت میباشد:
حالت اول : Where T:struct
در این حالت T باید یک ساختار باشد .
حالت دوم : where T:class
T باید یک نوع ارجاعی باشد. اگر در مثال فوق این قید را به آن اضافه کنیم، در هنگام ساخت نمونهای از کلاس فوق، اگر یک نوع value type را به T نسبت دهیم، در هنگام وارد کردن یک نوع value type با خطا مواجه خواهیم شد. مثال:
public class MyCollection<T> where T:class { private List<T> collections = new List<T>(); public void Add(T value) { collections.Add(value); } }
MyCollection<int> myintObj = new MyCollection<int>(); // ERROR , int is value type
حالت سوم : ()Where T:new
نوعی که به T نسبت داده میشود باید یک سازندهی پیش فرض داشته باشد.
داخل پرانتز : سازندهی پیش فرض: زمانی که شما یک کلاس مینویسید اگر آن کلاس دارای هیچ سازندهای نباشد، کامپایلر یک سازندهی بدون پارامتر را به کلاس فوق اضافه میکند که کار آن مقدار دهی به فیلدهای کلاس است. در اینجا از مقادیر پیش فرض استفاده میشود. مثلا برای int مقدار صفر و برای string مقدار "" و به همین ترتیب.
اگر از مقدار دهی پیش فرض توسط کامپایلر خرسند نیستید، میتوانید سازنده پیش فرض را تغییر داده و مطابق میل خود فیلدها را مقدار دهی اولیه کنید .
حالت چهارم : where T:NameOfBaseClass
نوعی که به T نسبت داده میشود باید از کلاس NameOfBaseClass ارث بری کرده باشد.
حالت پنجم : where T:NameOfInterface
همانند حالت چهارم میباشد؛ با این تفاوت: نوعی که به T نسبت داده میشود باید واسط NameOfInterface را پیاده سازی کرده باشد.
پنج حالت فوق نمونههایی از ایجاد محدودیت بر روی پرامتر نوع اعضای جنریک بودند و اما در ادامه قصد داریم نکاتی را در این باب، بیان کنیم:
نکته اول : میتوانید محدودیتهای فوق را با هم ترکیب کنید برای اینکار آنها را با کاما از هم جدا کنید :
public class MyCollection<T> where T:class,IDisposable,new() { //content }
- باید از نوع ارجاعی باشد.
- باید واسط IDisposable را پیاده سازی کرده باشد.
- باید یک سازندهی پیش فرض داشته باشد.
نکته دوم : زمانیکه از چندین محدودیت استفاده میکنید مثل مثال فوق، باید محدودیت ()new در آخرین جایگاه محدودیتها قرار گیرد؛ در غیر اینصورت با خطای زمان ترجمه روبه رو خواهید شد .
نکته سوم : میتوان محدودیتهای فوق را علاوه بر کلاس، بر روی متدهای جنریک نیز اعمال کنید:
public void Swap<T>(ref T val1,ref T val2) where T:struct { //content }
public class MyCollection<T,K> where T:class where K:IDisposable,new() { //content }
در این مقاله میخواهیم نحوهٔ ساخت اشیایی با خصوصیات Enumerable را بررسی کنیم. بررسی ویژگی این اشیاء دارای اهمیت است حداقل به این دلیل که پایهٔ یکی از قابلیت مهم زبانی سیشارپ یعنی LINQ هستند. برای یافتن پیشزمینهای در این موضوع خواندن این مقالههای بسیار خوب (۱ و ۲) نیز توصیه میشود.
Enumerableها
اشیاء Enumerable یا بهعبارت دیگر اشیائی که اینترفیس IEnumerable را پیادهسازی میکنند، دامنهٔ گستردهای از Collectionهای CLI را شامل میشوند. همانطور که در نمودار زیر نیز میتوانید مشاهده کنید IEnumerable (از نوع غیر Generic آن) در بالای سلسله مراتب اینترفیسهای Collectionهای CLI قرار دارد:
درخت اینترفیسهای Collectionها در CLI منبع
IEnumerableها همچنین دارای اهمیت دیگری نیز هستند؛ قابلیتهای LINQ که از داتنت ۳.۵ به داتنت اضافه شدند بهعنوان Extensionهای این اینترفیس تعریف شدهاند و پیادهسازی Linq to Objects را میتوانید در کلاس استاتیک System.Linq.Enumerable در System.Core مشاهده کنید. (میتوانید برای دیدن آن را با ILDasm یا Reflector باز کنید یا پیادهسازی آزاد آن در پروژهٔ Mono را اینجا مشاهده کنید که برای شناخت بیشتر LINQ واقعاً مفید است.)
همچنین این Enumerableها هستند که foreach را امکانپذیر میکنند. به عبارتی دیگر هر شئای که قرار باشد در foreach (var x in object) قرار بگیرد و بدین طریق اشیاء درونیاش را برای پیمایش یا عملی خاص قرار دهد باید Enumerable باشد.
همانطور که قبلاً هم اشاره شد IEnumerable از نوع غیر Generic در بالای نمودار Collectionها قرار دارد و حتی IEnumerable از نوع Generic نیز باید آن را پشتیبانی کند. این موضوع به احتمال به این دلیل در طراحی لحاظ شد که مهاجرت به .NET 2.0 که قابلیتهای Generic را افزوده بود سادهتر کند. IEnumerable همچنین قابلیت covariance که از قابلیتهای جدید C# 4.0 هست را دارا است (در اصل IEnumerable دارای Generic از نوع out است).
Enumerableها همانطور که از اسم اینترفیس IEnumerable انتظار میرود اشیایی هستند که میتوانند یک شئ Enumerator که IEnumerator را پیادهسازی کردهاست را از خود ارائه دهند. پس طبیعی است برای فهم و درک دلیل وجودی Enumerable باید Enumerator را بررسی کنیم.
Enumeratorها
Enumerator شئ است که در یک پیمایش یا بهعبارت دیگر گذر از روی تکتک عضوها ایجاد میشود که با حفظ موقعیت فعلی و پیمایش امکان ادامهٔ پیمایش را برای ما فراهم میآورد. اگر بخواهید آن را در حقیقت بازسازی کنید شئ Enumerator بهمانند کاغذ یا جسمی است که بین صفحات یک کتاب قرار میدهید که مکانی که در آن قرار دارید را گم نکنید؛ در این مثال، Enumerable همان کتاب است که قابلیت این را دارد که برای پیمایش به وسیلهٔ قرار دادن یک جسم در وسط آن را دارد.
حال برای اینکه دید بهتری از رابطهٔ بین Enumerable و Enumerator از نظر برنامهنویسی به این موضوع پیدا کنیم یک کد نمونهٔ عملی را بررسی میکنیم.
در اینجا نمونهٔ ساده و خوانایی از استفاده از یک List برای پیشمایش تمامی اعداد قرار دارد:
List<int> list = new List<int>(); list.Add(1); list.Add(2); list.Add(3); foreach (int i in list) { Console.WriteLine(i); }
همانطور که قبلاً اشاره foreach نیاز به یک Enumerable دارد و List هم با پیادهسازی IList که گسترشی از IEnumerable هست نیز یک نوع Enumerable هست. اگر این کد را Compile کنیم و IL آن را بررسی کنیم متوجه میشویم که CLI در اصل چنین کدی را برای اجرا میبینید:
List<int> list = new List<int>(); list.Add(1); list.Add(2); list.Add(3); IEnumerator<int> listIterator = list.GetEnumerator(); while (listIterator.MoveNext()) { Console.WriteLine(listIterator.Current); } listIterator.Dispose();
(میتوان از using استفاده نمود که Dispose را خود انجام دهد که اینجا برای سادگی استفاده نشدهاست.)
همانطور که میبینیم یک Enumerator برای Enumerable ما (یعنی List) ایجاد شد و پس از آن با پرسش این موضوع که آیا این پیمایش امکان ادامه دارد، کل اعضا پیمودهشده و عمل مورد نظر ما بر آنها انجام شدهاست.
خب، تا اینجای کار با خصوصیات و اهمیت Enumeratorها و Enumerableها آشنا شدیم، حال نوبت به آن میرسد که بررسی کنیم آنها را چگونه میسازند و بعد از آن با کاربردهای فراتری از آنها نسبت به پیمایش یک List آشنا شویم.
ساخت Enumeratorها و Enumerableها
همانطور که اشاره شد ایجاد اشیاء Enumerable به اشیاء Enumerator مربوط است، پس ما در یک قطعه کد که پیمایش از روی یک آرایه را فراهم میآورد ایجاد هر دوی آنها و رابطهٔ بینشان را بررسی میکنیم.
public class ArrayEnumerable<T> : IEnumerable<T> { private T[] _array; public ArrayEnumerable(T[] array) { _array = array; } public IEnumerator<T> GetEnumerator() { return new ArrayEnumerator<T>(_array); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } public class ArrayEnumerator<T> : IEnumerator<T> { private T[] _array; public ArrayEnumerator(T[] array) { _array = array; } public int index = -1; public T Current { get { return _array[index]; } } object System.Collections.IEnumerator.Current { get { return this.Current; } } public bool MoveNext() { index++; return index < _array.Length; } public void Reset() { index = 0; } public void Dispose() { } }
SELECT DEPARTMENTNAME, GENDER, COUNT(1) AS COUNT FROM DBO.DIMEMPLOYEE GROUP BY DEPARTMENTNAME,GENDER ORDER BY DEPARTMENTNAME,GENDER
SELECT EMPLOYEEKEY,FIRSTNAME,LASTNAME, MIDDLENAME,TITLE,HIREDATE, BIRTHDATE,EMAILADDRESS,PHONE,GENDER FROM DBO.DIMEMPLOYEE WHERE DEPARTMENTNAME=@DEPARTMENTNAME AND GENDER=@GENDER
قسمتی از یک پروژه به همراه کلاس SqlHelper آن در کامنتهای مطلب «اهمیت Code review» توسط یکی از خوانندگان بلاگ جهت Code review مطرح شده که بهتر است در یک مطلب جدید و مجزا به آن پرداخته شود. قسمت مهم آن کلاس SqlHelper است و مابقی در اینجا ندید گرفته میشوند:
//It's only for code review purpose!
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;
public sealed class SqlHelper
{
private SqlHelper() { }
// Send Connection String
//---------------------------------------------------------------------------------------
public static string GetCntString()
{
return WebConfigurationManager.ConnectionStrings["db_ConnectionString"].ConnectionString;
}
// Connect to Data Base SqlServer
//---------------------------------------------------------------------------------------
public static SqlConnection Connect2Db(ref SqlConnection sqlCnt, string cntString)
{
try
{
if (sqlCnt == null) sqlCnt = new SqlConnection();
sqlCnt.ConnectionString = cntString;
if (sqlCnt.State != ConnectionState.Open) sqlCnt.Open();
return sqlCnt;
}
catch (SqlException)
{
return null;
}
}
// Run ExecuteScalar Command
//---------------------------------------------------------------------------------------
public static string RunExecuteScalarCmd(ref SqlConnection sqlCnt, string strCmd, bool blnClose)
{
Connect2Db(ref sqlCnt, GetCntString());
using (sqlCnt)
{
using(SqlCommand sqlCmd = sqlCnt.CreateCommand())
{
sqlCmd.CommandText = strCmd;
object objResult = sqlCmd.ExecuteScalar();
if (blnClose) CloseCnt(ref sqlCnt, true);
return (objResult == null) ? string.Empty : objResult.ToString();
}
}
}
// Close SqlServer Connection
//---------------------------------------------------------------------------------------
public static bool CloseCnt(ref SqlConnection sqlCnt, bool nullSqlCnt)
{
try
{
if (sqlCnt == null) return true;
if (sqlCnt.State == ConnectionState.Open)
{
sqlCnt.Close();
sqlCnt.Dispose();
}
if (nullSqlCnt) sqlCnt = null;
return true;
}
catch (SqlException)
{
return false;
}
}
}
مثالی از نحوه استفاده ارائه شده:
protected void BtnTest_Click(object sender, EventArgs e)
{
SqlConnection sqlCnt = new SqlConnection();
string strQuery = "SELECT COUNT(UnitPrice) AS PriceCount FROM [Order Details]";
// در این مرحله پارامتر سوم یعنی کانکشن باز نگه داشته شود
string strResult = SqlHelper.RunExecuteScalarCmd(ref sqlCnt, strQuery, false);
strQuery = "SELECT LastName + N'-' + FirstName AS FullName FROM Employees WHERE (EmployeeID = 9)";
// در این مرحله پارامتر سوم یعنی کانکشن بسته شود
strResult = SqlHelper.RunExecuteScalarCmd(ref sqlCnt, strQuery, true);
}
مروری بر این کد:
1) نحوه کامنت نوشتن
بین سی شارپ و زبان سی++ تفاوت وجود دارد. این نحوه کامنت نویسی بیشتر در سی++ متداول است. اگر از ویژوال استودیو استفاده میکنید، مکان نما را به سطر قبل از یک متد منتقل کرده و سه بار پشت سر هم forward slash را تایپ کنید. به صورت خودکار ساختار خالی زیر تشکیل خواهد شد:
/// <summary>
///
/// </summary>
/// <param name="sqlCnt"></param>
/// <param name="cntString"></param>
/// <returns></returns>
public static SqlConnection Connect2Db(ref SqlConnection sqlCnt, string cntString)
این روش مرسوم کامنت نویسی کدهای سی شارپ است. خصوصا اینکه ابزارهایی وجود دارند که به صورت خودکار از این نوع کامنتها، فایل CHM درست میکنند.
2) وجود سازنده private
احتمالا هدف این بوده که نه شخصی و نه حتی کامپایلر، وهلهای از این کلاس را ایجاد نکند. بنابراین بهتر است کلاسی را که تمام متدهای آن static است (که به این هم خواهیم رسید!) ، راسا static معرفی کنید. به این ترتیب نیازی به سازنده private نخواهد بود.
3) وجود try/catch
یک اصل کلی وجود دارد: اگر در حال طراحی یک کتابخانه پایهای هستید، try/catch را در هیچ متدی از آن لحاظ نکنید. بله؛ درست خوندید! لطفا try/catch ننویسید! کرش کردن برنامه خوب است! لایههای بالاتر برنامه که در حال استفاده از کدهای شما هستند متوجه خواهند شد که مشکلی رخ داده و این مشکل توسط کتابخانه مورد استفاده «خفه» نشده. برای مثال اگر هم اکنون SQL Server در دسترس نیست، لایههای بالاتر برنامه باید این مشکل را متوجه شوند. Exception اصلا چیز بدی نیست! کرش برنامه اصلا بد نیست!
فرض کنید که دچار بیماری شدهاید. اگر مثلا تبی رخ ندهد، از کجا باید متوجه شد که نیاز به مراقبت پزشکی وجود دارد؟ اگر هیچ علامتی بروز داده نشود که تا الان نسل بشر منقرض شده بود!
4) وجود ref و out
دوستان گرامی! این ref و out فقط جهت سازگاری با زبان C در سی شارپ وجود دارد. لطفا تا حد ممکن از آن استفاده نکنید! مثلا استفاده از توابع API ویندوز که با C نوشته شدهاند.
یکی از مهمترین کاربردهای pointers در زبان سی، دریافت بیش از یک خروجی از یک تابع است. برای مثال یک متد API ویندوز را فراخوانی میکنید؛ خروجی آن یک ساختار است که به کمک pointers به عنوان یکی از پارامترهای همان متد معرفی شده. این روش به وفور در طراحی ویندوز بکار رفته. ولی خوب در سی شارپ که از این نوع مشکلات وجود ندارد. یک کلاس ساده را طراحی کنید که چندین خاصیت دارد. هر کدام از این خاصیتها میتوانند نمایانگر یک خروجی باشند. خروجی متد را از نوع این کلاس تعریف کنید. یا برای مثال در دات نت 4، امکان دیگری به نام Tuples معرفی شده برای کسانی که سریع میخواهند چند خروجی از یک تابع دریافت کنند و نمیخواهند برای اینکار یک کلاس بنویسند.
ضمن اینکه برای مثال در متد Connect2Db، هم کانکشن یکبار به صورت ref معرفی شده و یکبار به صورت خروجی متد. اصلا نیازی به استفاده از ref در اینجا نبوده. حتی نیازی به خروجی کانکشن هم در این متد وجود نداشته. کلیه تغییرات شما در شیء کانکشنی که به عنوان پارامتر ارسال شده، در خارج از آن متد هم منعکس میشود (شبیه به همان بحث pointers در زبان سی). بنابراین وجود ref غیرضروری است؛ وجود خروجی متد هم به همین صورت.
5) استفاده از using در متد RunExecuteScalarCmd
استفاده از using خیلی خوب است؛ همیشه اینکار را انجام دهید!
اما اگر اینکار را انجام دادید، بدانید که شیء sqlCnt در پایان بدنه using ، توسط GC نابوده شده است. بنابراین اینجا bool blnClose دیگر چه کاربردی دارد؟! تصمیم شما دیگر اهمیتی نخواهد داشت؛ چون کار تخریبی پیشتر انجام شده.
6) متد CloseCnt
این متد زاید است؛ به دلیلی که در قسمت (5) عنوان شد. using های استفاده شده، کار را تمام کردهاند. بنابراین بستن اشیاء dispose شده معنا نخواهد داشت.
7) در مورد نحوه استفاده
اگر SqlHelper را در اینجا مثلا یک DAL ساده فرض کنیم (data access layer)، جای قسمت BLL (business logic layer) در اینجا خالی است. عموما هم چون توضیحات این موارد را خیلی بد ارائه دادهاند، افراد از شنیدن اسم آنها هم وحشت میکنند. BLL یعنی کمی دست به Refactoring بزنید و این پیاده سازی منطق تجاری ارائه شده در متد BtnTest_Click را به یک کلاس مجزا خارج از code behind پروژه منتقل کنید. Code behind فقط محل استفاده نهایی از آن باشد. همین! فعلا با همین مختصر شروع کنید.
مورد دیگری که در اینجا باز هم مشهود است، عدم استفاده از پارامتر در کوئریها است. چون از پارامتر استفاده نکردهاید، SQL Server مجبور است برای حالت EmployeeID = 9 یکبار execution plan را محاسبه کند، برای کوئری بعدی مثلا EmployeeID = 19، اینکار را تکرار کند و الی آخر. این یعنی مصرف حافظه بالا و همچنین سرعت پایین انجام کوئریها. بنابراین اینقدر در قید و بند باز نگه داشتن یک کانکشن نباشید؛ مشکل اصلی جای دیگری است!
8) برنامه وب و اطلاعات استاتیک!
این پروژه، یک پروژه ASP.NET است. دیدن تعاریف استاتیک در این نوع پروژهها یک علامت خطر است! در این مورد قبلا مطلب نوشتم:
متغیرهای استاتیک و برنامههای ASP.NET
یک درخواست عمومی!
لطف کنید در پروژهای «جدید» خودتون این نوع کلاسهای SqlHelper رو «دور بریزید». یاد گرفتن کار با یک ORM جدید اصلا سخت نیست. مثلا طراحی Entity framework مایکروسافت به حدی ساده است که هر شخصی با داشتن بهره هوشی در حد یک عنکبوت آبی یا حتی جلبک دریایی هم میتونه با اون کار کنه! فقط NHibernate هست که کمی مرد افکن است و گرنه مابقی به عمد ساده طراحی شدهاند.
مزایای کار کردن با ORM ها این است:
- کوئریهای حاصل از آنها «پارامتری» است؛ که این دو مزیت عمده را به همراه دارد:
امنیت: مقاومت در برابر SQL Injection
سرعت و همچنین مصرف حافظه کمتر: با کوئریهای پارامتری در SQL Server همانند رویههای ذخیره شده رفتار میشود.
- عدم نیاز به نوشتن DAL شخصی پر از باگ. چون ORM یعنی همان DAL که توسط یک سری حرفهای طراحی شده.
- یک دست شدن کدها در یک تیم. چون همه بر اساس یک اینترفیس مشخص کار خواهند کرد.
- امکان استفاده از امکانات جدید زبانهای دات نتی مانند LINQ و نوشتن کوئریهای strongly typed تحت کنترل کامپایلر.
- پایین آوردن هزینههای آموزشی افراد در یک تیم. مثلا EF را میشود به عنوان یک پیشنیاز در نظر گرفت؛ عمومی است و همه گیر. کسی هم از شنیدن نام آن تعجب نخواهد کرد. کتاب(های) آموزشی هم در مورد آن زیاد هست.
و ...
در این قسمت بر روی تابع Filter در MDX Query ها تمرکز خواهیم کرد. برای آشنایی با این تابع یک سری از کوئریها را اجرا کرده و به بررسی آنها میپردازیم.
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, [Product].[Product Categories].[Category] on rows From [Adventure Works]
دقت کنید که در واکشی، مقدار فروش اینترنتی Component برابر Null میباشد.
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], [Measures].[Internet Sales Amount] > 0 ) on rows From [Adventure Works]
با توجه به شرطی که اعمال شده است، فقط دسته بندی محصولاتی در خروجی می آید که فروش اینترنتی آنها بیشتر از صفر یا برابر Null نباشند.
استفاده از کلید واژه ی Having در هر محور کاری شبیه به انجام عمل فیلترینگ می باشد .
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, [Product].[Product Categories].[Category] having [Measures].[Internet Sales Amount] > 0 on rows From [Adventure Works]
اگر بخواهیم میزان فروش اینترنتی و میزان فروش نمایندگان فروش را برای محصولاتی واکشی کنیم که میزان فروش اینترنتی آنها بیش از 500000 دلار میباشد ، کوئری زیر را خواهیم داشت :
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], [Measures].[Internet Sales Amount] > 500000 ) on rows From [Adventure Works]
و برای ایجاد شرط ترکیبی بر روی شاخص، به صورت زیر عمل خواهیم کرد :
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], [Measures].[Internet Sales Amount] > 500000 and [Measures].[Internet Sales Amount] < 750000 ) on rows From [Adventure Works]
در مثال بالا، دسته بندی محصولاتی در خروجی واکشی شده اند که میزان فروش اینترنتی آنها بیش از 500 هزار و کمتر از 750 هزار میباشد.
استفاده از And , Or در شروط ترکیبی مجاز میباشد
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], [Measures].[Internet Sales Amount] > 750000 or [Measures].[Internet Sales Amount] < 500000 ) on rows From [Adventure Works]
در زیر با استفاده از And، شرط برروی میزان فروش نمایندگان فروش نیز قرارداده شده است.
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], ( [Measures].[Internet Sales Amount] > 750000 or [Measures].[Internet Sales Amount] < 500000 ) and [Measures].[Reseller Sales Amount] < 15000000 ) on rows From [Adventure Works]
در هنگام ایجاد شروط ترکیبی حتما از () استفاده کنید .
حال میخواهیم دو شاخص در یک ردیف، با یکدیگر مقایسه شوند و در صورت صحت شرط، آن ردیف در خروجی قرار گیرد
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], [Measures].[Internet Sales Amount] > [Measures].[Reseller Sales Amount] ) on rows From [Adventure Works]
ایجاد فیلترینگ با استفاده از currentmember و عملگر Is .
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], [Product].[Product Categories].currentmember is [Product].[Product Categories].[Category].[Bikes] ) on rows From [Adventure Works]
البته در مثال بالا می توانیم به جای استفاده از Is از = استفاده کنیم. تا اینجا عمل Filtering برروی شاخصها انجام شده است. اما امکان اعمال Filter روی Dimension ها نیز وجود دارد.
کوئری زیر را بررسی کنید :
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], [Product].[Product Categories].currentmember is [Product].[Product Categories].[Category].[Bikes] or [Product].[Product Categories].currentmember is [Product].[Product Categories].[Category].[Accessories] ) on rows From [Adventure Works]
در کوئری بالا میزان فروش نمایندگان فروش و فروش اینترنتی برای دسته بندیهای Bike و Accessories واکشی شده است.
امکان ایجاد شرایط ترکیبی از شاخصها و بعدها در یک Filter نیز وجود دارد.
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], ( [Product].[Product Categories].currentmember is [Product].[Product Categories].[Category].[Bikes] and [Measures].[Reseller Sales Amount] > 1000000 ) or ( [Product].[Product Categories].currentmember is [Product].[Product Categories].[Category].[Accessories] ) and [Measures].[Reseller Sales Amount] > 750000 )on rows From [Adventure Works]
همچنین میتوان از Not درون شرط Filter استفاده کرد
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, filter( [Product].[Product Categories].[Category], not ( [Product].[Product Categories].currentmember is [Product].[Product Categories].[Category].[Clothing] ) ) on rows From [Adventure Works]
در زیر میخواهیم به بررسی تابع non Empty بپردازیم . برای این منظور در ابتدا کوئری زیر را اجرا کنید :
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, [Product].[Product Categories].[Subcategory] on rows From [Adventure Works]
با استفاده از تابع Non Empty، ردیف ها یا ستون هایی حذف می گردند که تمامی مقادیر آنها ، در آن ردیف یا در آن ستون برابر Null باشند.
Select { [Measures].[Internet Sales Amount], [Measures].[Reseller Sales Amount] } on columns, non empty [Product].[Product Categories].[Subcategory] on rows From [Adventure Works]