<link href="/Content/myCSS?v=LBa4VrARIJBApHOXLQoCWVPKu_c6QBb4D7npv-dq5TA1" rel="stylesheet"/> <script src="/Scripts/js?v=rk-l3czPS8KW6usEOpK8aIglVqPOVEKWoSXV4N-5N2Q1"></script>
مدتی پیش نیاز پیدا کردم تا فراخوانی متدهایی را Serialize کرده و در مواقعی خاص، آن متدها را فراخوانی کنم که نتیجهی آن را در زیر با هم میبینیم.
در نظر بگیرید متدی داریم به شکل زیر:
public class EmailSender { public void Send(string emailAddress) { Console.WriteLine($"an email was sent to {emailAddress}"); } }
و میخواهیم نحوه فراخوانی این متد را Serialize کرده
new EmailSender().Send("eng.younos1986@gmail.com")
یعنی با چنین کدی، متد مورد نظر را Serialize کرده، Type ، Method.Name و Argument های آن را بدست آورده و در دیتابیس ذخیره کنیم:
Serialize(() => new EmailSender().Send("eng.younos1986@gmail.com"));
public void Serialize(Expression<Action> methodCall) { var callExpression = methodCall.Body as MethodCallExpression; var rawArguments = new object[callExpression.Arguments.Count]; var i = 0; foreach (var argg in callExpression.Arguments) { rawArguments[i++] = Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke(); } string typeName = string.Empty; var serializedArgumentsObject = rawArguments.ObjectToByteArray(); //todo: save this to db as method parameters [Binary field] if (callExpression.Object != null) //todo: save this to db as class name to be instanciated later {nvarchar field} typeName = callExpression.Object.Type.ToString(); // instance methods else typeName = callExpression.Method.ReflectedType.ToString(); // static methods var methodname = callExpression.Method.Name; //todo: save this to db as method name to be called via reflection [nvarchar field] var deserializedArgumentsObject = serializedArgumentsObject.ByteArrayToObject(); //todo: retrieve serializedObject fro db and deserialize var objInstance = GetInstance(typeName); //todo: retrieve typeName fro db and deserialize if (objInstance != null) { objInstance.GetType().GetMethod(methodname).Invoke(objInstance, (object[])deserializedArgumentsObject); } }
برای اینکه بتوانیم نحوه فراخوانی متد را به صورت Lambda Expressions به متد Serialize بفرستیم، باید نوع پارارمتر آن از جنس <Expression<Action باشد.
بعد با Cast کردن methodCall.Body به MethodCallExpression میتوانیم آرگیومنتها، نام متد و Type آن را بدست بیاوریم:
در خطوط 5 تا 10 کد بالا، آرایهای به طول تعداد Argument ها ساخته و هر Argument را به نوع خودش تبدیل کرده و درون آرایه میریزیم.
var rawArguments = new object[callExpression.Arguments.Count];
var i = 0; foreach (var argg in callExpression.Arguments) { rawArguments[i++] = Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke(); }
و با این کد
Expression.Lambda(Expression.Convert(argg, argg.Type)).Compile().DynamicInvoke();
هر آرگیومنت را به نوع خودش تبدیل کرده سپس Lambdaی آن را ساخته و کامپایل کرده تا Expression Tree به delegate تبدیل شده ( به کد معادل IL تبدیل شده)، سپس آن را با متد DynamicInvoke اجرا کرده تا دقیقا معادل آرگیومنت ارسالی را بدست آورده و در آرایه ذخیره میکنیم. در آخر آرایه بدست آمده را به بایت تبدیل کرده و میتوان در یک منبع داده دائمی ذخیره کرد.
با کد زیر، Typeی که متد، درون آن قرار دارد را بدست میآوریم:
if (callExpression.Object != null) typeName = callExpression.Object.Type.ToString(); else typeName = callExpression.Method.ReflectedType.ToString();
و همچنین نام متد:
var methodname = callExpression.Method.Name;
تا به اینجای کار Type، نام متد و آرگیومنتهای Serialize شده آن بدست آمدند که میتوان آنها را در دیتابیس ذخیره کرد. سپس استخراج، DeSerialize و فراخوانی کرد:
نحوه فراخوانی:
var methodname = callExpression.Method.Name; var deserializedArgumentsObject = serializedArgumentsObject.ByteArrayToObject(); var objInstance = GetInstance(typeName); if (objInstance != null) { objInstance.GetType().GetMethod(methodname).Invoke(objInstance, (object[])deserializedArgumentsObject); }
و برای instance ساختن از Type مورد نظر از کد زیر استفاده میکنیم:
public object GetInstance(string strFullyQualifiedName) { Type type = Type.GetType(strFullyQualifiedName); if (type != null) return Activator.CreateInstance(type); foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { type = asm.GetType(strFullyQualifiedName); if (type != null) return Activator.CreateInstance(type); } return null; }
موارد استفاده: نوشتن نرم افزارهایی برای مدیریت یک سری Job
کد کامل MethodCallSerialization.rar
متدی برای بررسی صحت کد ملی وارد شده
function checkCodeMeli(code) { var L=code.length; if(L<11 || parseInt(code,10)==0) return false; if(parseInt(code.substr(3,6),10)==0) return false; var c=parseInt(code.substr(10,1),10); var d=parseInt(code.substr(9,1),10)+2; var z=new Array(29,27,23,19,17); var s=0; for(var i=0;i<10;i++) s+=(d+parseInt(code.substr(i,1),10))*z[i%5]; s=s%11;if(s==10) s=0; return (c==s); }
قابلیتی که در ادامه از آن برای «قالب پذیر ساختن گرید» استفاده خواهیم کرد، همان نکتهی «امکان تعویض پویای قالبهای یک دربرگیرنده» است که در مطلب «امکان تعریف قالبها در Angular با دایرکتیو ng-template» به آن پرداختیم.
تعریف قالبهای نمایش و ویرایش اطلاعات یک ردیف در گرید طراحی شده
پس از آشنایی با دایرکتیوهای تعریف و کار با قالبها در Angular، اکنون تبدیل بدنهی ثابت جدول، به دو قالب نمایش و ویرایش، سادهاست.
در قسمت دوم این سری، کار رندر بدنهی اصلی گرید توسط همین چند سطر، در قالب آن مدیریت میشود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> </tr> </tbody> </table>
در ادامه قسمت داخلی ngFor را تبدیل به یک ng-container میکنیم تا قالب پذیر شود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <ng-container [ngTemplateOutlet]="loadTemplate(item)" [ngOutletContext]="{ $implicit: item, idx: i }"></ng-container> </tr> </tbody>
در اینجا ngTemplateOutlet این امکان را میدهد تا بتوان توسط کدهای برنامه، قالب هر ردیف را مشخص کرد. متد loadTemplate در کدهای کامپوننت متناظر فراخوانی شده و بر اساس وضعیت هر ردیف، یکی از دو قالب ذیل را بازگشت میدهد:
الف) قالب نمایش معمولی و فقط خواندنی رکوردها
<!--The Html Template for Read-Only Rows--> <ng-template #readOnlyTemplate let-item let-i="idx"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> <td> <input type="button" value="Edit" class="btn btn-default btn-xs" (click)="editItem(item)" /> </td> <td> <input type="button" value="Delete" (click)="deleteItem(item)" class="btn btn-danger btn-xs" /> </td> </ng-template>
این قالب در کدهای کامپوننت آن به صورت ذیل قابل دسترسی و انتخاب شدهاست:
@ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>;
ب) قالب ویرایش اطلاعات هر ردیف که از آن برای افزودن یک ردیف جدید هم میتوان استفاده کرد
شبیه به همان کاری را که برای نمایش ردیفهای فقط خواندنی انجام دادیم، در مورد قالب ویرایش هر ردیف نیز تکرار میکنیم. در اینجا فقط امکان ویرایش نام محصول، قیمت آن و موجود بودن آنرا توسط یکسری input box مهیا کردهایم:
<!--The Html Template for Editable Rows--> <ng-template #editTemplate let-item let-i="idx"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center"> <input type="text" [(ngModel)]="selectedItem.productName" class="form-control" /> </td> <td class="text-center"> <input type="text" [(ngModel)]="selectedItem.price" class="form-control" /> </td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" [(ngModel)]="selectedItem.isAvailable" /> </td> <td> <input type="button" value="Save" (click)="saveItem()" class="btn btn-success btn-xs" /> </td> <td> <input type="button" value="Cancel" (click)="cancel()" class="btn btn-warning btn-xs" /> </td> </ng-template>
@ViewChild("editTemplate") editTemplate: TemplateRef<any>;
تا اینجا کار تعریف قالبهای این گرید به پایان میرسد. در ادامه کدهای افزودن، ثبت، ویرایش، حذف و لغو را پیاده سازی خواهیم کرد:
خواص عمومی مورد نیاز جهت کار با قالبها و ویرایشهای درون ردیفی
@ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>; @ViewChild("editTemplate") editTemplate: TemplateRef<any>; selectedItem: AppProduct; isNewRecord: boolean;
به علاوه اگر به قالب editTemplate دقت کنید، مقدار ویرایش شده به [(ngModel)]="selectedItem.productName" انتساب داده میشود. به همین جهت شیء selectedItem نیز تعریف شدهاست.
همچنین نیاز است بدانیم اکنون در حال ویرایش یک ردیف هستیم یا این ردیف، کاملا ردیف جدیدی است. به همین جهت پرچم isNewRecord نیز تعریف شدهاست.
فعالسازی قالب ویرایش هر ردیف
در انتهای هر ردیف، دکمهی ویرایش نیز قرار دارد که به (click) آن، رخداد editItem متصل است:
editItem(item: AppProduct) { this.selectedItem = item; }
loadTemplate(item: AppProduct) { if (this.selectedItem && this.selectedItem.productId === item.productId) { return this.editTemplate; } else { return this.readOnlyTemplate; } }
مدیریت افزودن یک ردیف جدید
دکمهی افزودن یک ردیف جدید به صورت ذیل به قالب اضافه شدهاست:
<div class="panel"> <input type="button" value="Add new product" class="btn btn-primary" (click)="addItem()" /> </div>
addItem() { this.selectedItem = new AppProduct(0, "", 0, false); this.isNewRecord = true; this.queryResult.items.push(this.selectedItem); this.queryResult.totalItems++; }
مدیریت لغو ویرایش هر ردیف
برای اینکه ویرایش هر ردیف را لغو کنیم و قالب آنرا به حالت فقط خواندنی بازگشت دهیم، فقط کافی است selectedItem را به نال تنظیم کنیم:
cancel() { this.selectedItem = null; }
مدیریت حذف هر ردیف
در اینجا با پیاده سازی متد رخدادگردان deleteItem و ارسال id هر ردیف به سرور، کار حذف هر ردیف را انجام خواهیم داد:
deleteItem(item: AppProduct) { this.productsService .deleteAppProduct(item.productId) .subscribe((resp: Response) => { this.getPagedProductsList(); }); }
مدیریت ثبت و یا به روز رسانی هر ردیف
آخرین عملیاتی که باید مدیریت شود، بررسی پرچم isNewRecord است. اگر true بود، کار افزودن یک ردیف جدید صورت گرفته و سپس این پرچم false میشود. اگر false بود، به معنای درخواست به روز رسانی ردیفی مشخص است. در پایان هر دو عملیات selectedItem را نیز true میکنیم و این پایان عملیات باید داخل قسمت دریافت پاسخ از سرور مدیریت شود و نه پس از فراخوانی این متدها؛ چون متدهای subscribe غیرهمزمان بوده و ردیفهای پس از آنها بلافاصله اجرا میشوند.
saveItem() { if (this.isNewRecord) { this.productsService .addAppProduct(this.selectedItem) .subscribe((resp: AppProduct) => { this.selectedItem.productId = resp.productId; this.isNewRecord = false; this.selectedItem = null; }); } else { this.productsService .updateAppProduct(this.selectedItem.productId, this.selectedItem) .subscribe((resp: AppProduct) => { this.selectedItem = null; }); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
همانطور که در قسمت اول گفته شد، اجزای رابط کاربری (تگهای HTML) در کتابخانهی React به عنوان کامپوننتها (مؤلفههای جزء) شناخته میشوند. React تگها را به عنوان اجزایی مستقل و با وضعیتی مشخص در حافظه میشناسد. دلایل ارزشمند بودن این روش در ادامه بررسی میشود.
خوانایی بهتر (Readability)
React میتواند تگهای یگانه یا مخلوطی از تگهای به هم مرتبط را در پس زمینه ساخته و با یک نام واحد (کامپوننت) به HTML DOM ارسال کند. یعنی اگر جایی یک کامپوننت صدا زده شود، تگ یا تگهای مرتبط به آن کامپوننت را به عنوان خروجی خواهیم داشت. همانطور که میشود تگهای مختلف را به صورت تو در تو استفاده کرد، کامپوننتها را هم میشود به همین روش فراخوانی کرد. در مثال زیر روش صدا زدن چند کامپوننت و تگهایی را که ارائه میدهد، داریم.
// Components in a JavaScript file. <clickableImage href="http://google.com" src="google.png" /> <LinksContainer> <LinksList> <clickableImage href="http://yahoo.com" src="yahoo.png" /> </LinksList> </LinksContainer> <!--Output in HTML DOM--> <a href="http://google.com"> <img src="google.png" /> </a> <div> <div> <ul> <li> <a href="http://google.com"> <img src="google.png" /> </a> </li> </ul> </div> </div>
در قسمت کامپوننتها میبینیم که چطور کامپوننتها یکبار به صورت تکی و یک بار به صورت تو در تو اجرا میشوند. خروجی در قسمت Output واضح است که با نام کامپوننتها هماهنگی دارد. با این مثال چند مورد مشخص میشود.
- به هر کامپوننت قبلا گفته شده چه تگهایی را باید ایجاد کند. در نتیجه با هر بار فراخوانی در هر مکان، تگ یا تگهایی که به آن معرفی شده را میسازد.
- هر کامپوننت میتواند مقادیری را به عنوان ورودی دریافت کند و آنها را به تگها در خروجی اعمال کند. در مثال بالا href و src در فراخوانیهای مختلف، مقادیر متفاوتی را به خروجی میفرستند.
- با انتخاب نام مناسب برای کامپوننتها، بدون آنکه بدانیم چطور ساخته شدهاند میتوانیم حدس بزنیم چه تگهایی را خواهند ساخت و این دلیلی است که خوانایی برنامه افزایش میابد.
- دلیل دیگر که باعث خوانایی برنامه میشود، این است که هر یک از این کامپوننتها میتوانند تگهای زیادی را یک جا بسازند که این کار منجر به کم شدن مقدار کد برنامه میشود. برنامه هر چه کم کدتر، با خوانایی بیشتر!
قابلیت استفاده مجدد
در ادامه وقتی با روش ساخت کامپوننتها آشنا شدیم، متوجه میشویم که کامپوننتها چیزی بیشتر از یک تابع نیستند. وقتی نام یک کامپوننت را فراخوانی کنیم در واقع یک تابع را اجرا میکنیم، به آن پارامتر ورودی را میدهیم و از آن خروجی میگیریم. میدانیم که توابع را میشود یکبار ساخت و چندبار استفاده کرد. بخصوص اگر این توابع به متغیرهای سراسری و سایر توابع وابسته نباشند و به صورت مستقل عمل کنند، میشود آنها را به برنامههای دیگر هم انتقال داد.
نحوه ساخت یک کامپوننت در React
در React به سه روش میشود کامپوننتها را ایجاد کرد. در روش اول توضیحات زیاد خواهند بود، اما در دو روش بعدی فقط نکات کلیدی گفته خواهد شد.
Stateless function components
میخواهیم یک منو از نوشیدنیها را با استفاده از کامپوننتها نمایش دهیم. در یک فایل جاوااسکریپت کدهای زیر را وارد کنید. در ادامه هر بخش توضیح داده خواهد شد.
var hotDrinks = [ { item: "Tea", price: "7000" }, { item: "Espresso", price: "10000" }, { item: "Hot Chocolate", price: "12000" } ]; var MenuItem = function (props) { return ( <li className="list-group-item"> <span className="badge">{props.price}</span> <p>{props.item}</p> </li> ) }; var Menu = function (props) { return ( <div className="row"> <div className="col-md-4"> <ul className="list-group"> {props.data.map(item => <MenuItem {...item} />)} </ul> </div> </div> ) }; ReactDOM.render( <Menu data={hotDrinks} />, document.getElementById("reactTestContainer") )
- فرض میکنیم که لیست نوشیدنیها و قیمت آنها را به فرمتی که میبینید از سرور دریافت کردهایم. (hotDrinks)
- شیء MenuItem یک تابع بدون نام را اجرا میکند. از دیدگاه React این تابع یک کامپوننت است. کامپوننت با هر بار فراخوانی مقادیری را برای یک نوشیدنی و قیمت آن، دریافت میکند.کامپوننت به عنوان خروجی یک تگ <li>، پر شده با مقادیر ورودی را بازگشت میدهد.
- شیء Menu یک تابع بدون نام را اجرا میکند. از دید React این تابع یک کامپوننت است. کامپوننت با هر بار فراخوانی، مجموعهای از نوشیدنیها و قیمت آنها را دریافت میکند. متد map به کمک یک Arrow Function آرایهای از کامپوننت MenuItem ایجاد میکند که به ازای هر عضو ایجاد شده، یکبار MenuItem اجرا میشود. هر عضو (item) دارای یک نام نوشیدنی و قیمت آن است. سه نقطه در {…item} برای پر کردن جای خالی نیست! این عبارت یعنی اینکه مقادیر نام و قیمت را به صورت جداگانه (یعنی دو پارامتر مجزا) به کامپوننت MenuItem ارسال میکند. کامپوننت، به عنوان خروجی یک تگ <ul>، پر شده با آرایهای از کامپوننت MenuItem را بازگشت میدهد.
- متد render از شیء ReactDOM وظیفه ساخت تگهای JSX واقع در کامپوننتها را در HTML DOM به عهده دارد. پارامتر اول render، کامپوننت Menu است با ورودی دادههای گرفته شده از سرور. همانطور که شرح داده شد، کامپوننت Menu با فراخوانی و به کمک دادههای ورودی، کامپوننت MenuItem را پیادهسازی خواهد کرد. پارامتر دوم render، محلی است که تگها باید در آن ساخته شوند. مثلا یک تگ <div>
- در هر کدام از کامپوننتها و در قسمت ReactDOM.render میشود از کامپوننتهای دیگر به صورت تو در تو استفاده کرد.
React.createClass
React یک API درونی برای ایجاد کامپوننتها، به نام createClass دارد. این تابع باید یک شیء پیکربندی درون خود داشته باشد که در آن و بین دو آکولاد {} خواص و متدها تعریف میشوند. تابع createClass برای کار حداقل باید یک متد به نام render داشته باشد که در آن تگهای JSX را قرار میدهیم. کامپوننت MenuItem را که به صورت Stateless ساختیم، دوباره با createClass ایجاد میکنیم.
var MenuItem = React.createClass({ render: function () { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ) } });
برای خواندن مقادیر ورودی در این روش باید از this استفاده کنیم. بر اساس قواعد شیء گراییِ، MenuItem و Menu کلاس هستند و هر بار در ReactDOM.render کامپوننت Menu را به HTML DOM ارسال میکنیم. یک نمونه از این کلاس ساخته میشود و کلاس Menu، نمونههایی از کلاس MenuItem را میسازد. this به نمونهی ساخته شده از یک کلاس اشاره دارد.
React.Component
در روش آخر با استفاده از extend، از کلاس React.Component ارث بری میکنیم و کامپوننت را میسازیم. مفاهیم کلاس و ارث بری در جاوااسکریپ را میشود از اینجا یاد گرفت. مجددا MenuItem را با این روش ایجاد میکنیم.
class MenuItem extends React.Component { render() { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ); } }
همانطور که میبینید بین دو روش React.Component و React.createClass تفاوتی جز در syntax آنها نیست. در اینجا از سایر امکانات کلاس در جاوااسکریپت مثل سازنده کلاس میشود استفاده کرد. کامپوننتها در React میتوانند کاری بیشتر از ساخت تگها در HTML DOM را انجام دهند. در قسمت بعد به قابلیت مهم حفظ و دنبال کردن تغییرات در وضعیت کامپوننتها میپردازیم.
services.AddServerSideBlazor () //............ endpoints.MapBlzorHub()
<script script src= _framework/blazor.server.js></script>
script src= admin/_framework/blazor.server.js
شروع به کار با NBench
برای شروع به کار با NBench، ابتدا نیاز است دو بستهی نیوگت ذیل را نصب کرد:
PM> Install-Package NBench PM> Install-Package NBench.Runner
[PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void AddMemoryMeasurement() { const int numberOfAdds = 1000000; var dictionary = new Dictionary<int, int>(); for (var i = 0; i < numberOfAdds; i++) { dictionary.Add(i, i); } } [PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [GcMeasurement(GcMetric.TotalCollections, GcGeneration.AllGc)] public void MeasureGarbageCollections() { var dataCache = new List<int[]>(); for (var i = 0; i < 500; i++) { for (var j = 0; j < 10000; j++) { var data = new int[100]; dataCache.Add(data.ToArray()); } dataCache.Clear(); } }
سپس هر متد تست به ویژگی PerfBenchmark مزین میشود. در اینجا RunMode.Iterations به این معنا است که خودمان قصد داریم در طی یک حلقه، تعداد بار انجام را مشخص کنیم.
ویژگی MemoryMeasurement برای اندازه گیری حافظهی مصرفی یک قطعه کد و GcMeasurement برای اندازه گیری فشار بر روی Garbage collector بکار میرود.
اجرای آزمونهای NBench
پس از تهیهی دو متد فوق، به پوشهی packages\NBench.Runner.0.3.4\lib\net45 مراجعه کنید. یک فایل exe در آن موجود است که کار یافتن و اجرای آزمونهای NBench را انجام میدهد. به عنوان پارامتر آن تنها کافی است مسیر اسمبلی برنامه (فایل exe و یا dll) را به آن ارسال کنیم:
D:\Prog\NBenchSample\packages\NBench.Runner.0.3.4\lib\net45\NBench.Runner.exe "D:\Prog\NBenchSample\NBenchSample\bin\Release\NBenchSample.exe"
--------------- RESULTS: NBenchSample.Program+AddMemoryMeasurement --------------- TotalBytesAllocated: Max: 47,842,944.00 bytes, Average: 42,002,757.60 bytes, Min: 41,353,848.00 bytes, StdDev: 2,052,032.33 bytes TotalBytesAllocated: Max / s: 359,074,078.19 bytes, Average / s: 311,474,786.96 bytes, Min / s: 300,926,928.79 bytes, StdDev / s: 16,869,581.62 bytes --------------- RESULTS: NBenchSample.Program+MeasureGarbageCollections --------------- TotalCollections [Gen0]: Max: 708.00 collections, Average: 702.80 collections, Min: 697.00 collections, StdDev: 3.65 collections TotalCollections [Gen0]: Max / s: 111.55 collections, Average / s: 109.87 collections, Min / s: 107.88 collections, StdDev / s: 1.28 collections TotalCollections [Gen1]: Max: 338.00 collections, Average: 334.60 collections, Min: 330.00 collections, StdDev: 2.41 collections TotalCollections [Gen1]: Max / s: 53.61 collections, Average / s: 52.31 collections, Min / s: 51.10 collections, StdDev / s: 0.70 collections TotalCollections [Gen2]: Max: 32.00 collections, Average: 24.80 collections, Min: 18.00 collections, StdDev: 4.73 collections TotalCollections [Gen2]: Max / s: 4.91 collections, Average / s: 3.87 collections, Min / s: 2.86 collections, StdDev / s: 0.72 collections
نکتهای در مورد اندازه گیری فشار حافظه
حافظه توسط سیستم عامل، به صورت صفحات تخصیص داده میشود. برای مثال اگر شما به 12 بایت نیاز داشته باشید، سیستم عامل ممکن است 8 کیلوبایت را جهت کاهش تعداد بار تخصیصهای حافظه و بالا بردن سرعت کار، در اختیار برنامه قرار دهد. بنابراین جهت رسیدن به بهترین نتیجه، در اینجا بهتر است تعداد زیادی شیء را مورد آزمایش قرار داد. برای مثال در آزمایش فوق بجای افزودن یک آیتم به دیکشنری، افزودن میلیونها شیء، نویز استراتژی تخصیص حافظهی توسط سیستم عامل را به حداقل میرساند.
شبیه به همین استراتژی، در پیاده سازی Dictionary نیز بکارگرفته شدهاست:
[PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Measurement)] [MemoryMeasurement(MemoryMetric.TotalBytesAllocated)] public void AddMemoryMeasurement_With_initial_Size() { const int numberOfAdds = 1000000; var dictionary = new Dictionary<int, int>(numberOfAdds); for (var i = 0; i < numberOfAdds; i++) { dictionary.Add(i, i); } }
--------------- RESULTS: NBenchSample.Program+AddMemoryMeasurement_With_initial_Size --------------- TotalBytesAllocated: Max: 23,245,912.00 bytes, Average: 23,245,912.00 bytes, Min: 23,245,912.00 bytes, StdDev: 0.00 bytes TotalBytesAllocated: Max / s: 394,032,435.34 bytes, Average / s: 389,108,363.43 bytes, Min / s: 378,502,981.34 bytes, StdDev / s: 5,575,519.09 bytes
علت اینجا است که دیکشنری در پشت صحنه، از یک متد ReSize استفاده میکند که شبیه به سیستم عامل، بیشتر از مقدار مورد نیاز جهت ذخیرهی اشیاء، برای کاهش تعداد بار تخصیصهای حافظه، حافظه به خود اختصاص میدهد. به همین جهت زمانیکه اندازهی اولیه را مشخص کردهایم، کار تخصیص حافظهی بیش از اندازهی این شیء، به شدت کاهش یافتهاست.
بررسی متد MeasureGarbageCollections
در متد MeasureGarbageCollections، مقدار زیادی شیء بر روی heap ایجاد شده و GC را وادار به عکس العمل شدید میکند.
حلقهی داخلی ایجاد شده نیز تعداد زیادی شیء را در جهت پاکسازی GC تخصیص میدهد. این پاکسازی در مرحلهای به نام generation 0 صورت میگیرد.
اشیاء اضافه شدهی به لیست، طول عمر بیشتری دارند (تا پایان حلقه). بنابراین از garbage collection at generation 0 جان سالم به در خواهند برد و در garbage collection at generation 1 به عمر آنها پایان داده خواهد شد. هرچند ممکن است تعدادی از آنها پاکسازی نشده و تا پایان full garbage collection (generation 2) باقی بمانند.
در آزمایش انجام شده، با ذکر GcGeneration.AllGc، هر سه مورد Gen0 تا Gen2 اندازه گیری خواهند شد. عموما اندازه گیری Gen0 و Gen1 مهم نیستند و اینها خیلی زود به پایان خواهند رسید. اگر تعداد بار رخدادن Gen2 زیاد بود (یا اصلا وجود داشت)، میتواند سبب بروز مشکلات کارآیی شدیدی گردد.
بنابراین میتوان بجای تنظیم GcGeneration.AllGc، صرفا از GcGeneration.Gen2 استفاده کرد.
اندازهگیری Throughput یا تعداد بار اجرای یک متد در ثانیه
روش دیگر کار با فریم ورک NBench، ایجاد یک کلاس مخصوص و سپس افزودن متدهای Setup مزین به PerfSetup، متد Cleanup مزین به PerfCleanup و سپس تعدادی متد اندازه گیری کارآیی توسط ویژگی PerfBenchmark است. در اینجا برای اندازهگیری سرعت اجرای متدها، از ویژگی CounterThroughputAssertion استفاده خواهد شد که پارامتر اول آن نام یک شمارشگر است. این شمارشگر در متد Setup ایجاد میشود (با یک نام دلخواه).
public class DictionaryThroughputTests { private readonly Dictionary<int, int> _dictionary = new Dictionary<int, int>(); private const string AddCounterName = "AddCounter"; private Counter _addCounter; private int _key; private const int AverageOperationsPerSecond = 20000000; [PerfSetup] public void Setup(BenchmarkContext context) { _addCounter = context.GetCounter(AddCounterName); _key = 0; } [PerfBenchmark(RunMode = RunMode.Throughput, TestMode = TestMode.Test)] [CounterThroughputAssertion(AddCounterName, MustBe.GreaterThan, AverageOperationsPerSecond)] public void AddThroughput_ThroughputMode(BenchmarkContext context) { _dictionary.Add(_key++, _key); _addCounter.Increment(); } [PerfBenchmark(RunMode = RunMode.Iterations, TestMode = TestMode.Test)] [CounterThroughputAssertion(AddCounterName, MustBe.GreaterThan, AverageOperationsPerSecond)] public void AddThroughput_IterationsMode(BenchmarkContext context) { for (var i = 0; i < AverageOperationsPerSecond; i++) { _dictionary.Add(i, i); _addCounter.Increment(); } } [PerfCleanup] public void Cleanup(BenchmarkContext context) { _dictionary.Clear(); } }
--------------- RESULTS: NBenchSample.DictionaryThroughputTests+AddThroughput_ThroughputMode --------------- [Counter] AddCounter: Max: 575,654.00 operations, Average: 575,654.00 operations, Min: 575,654.00 operations, StdDev: 0.00 operations [Counter] AddCounter: Max / s: 7,205,997.59 operations, Average / s: 7,163,894.30 operations, Min / s: 7,075,316.79 operations, StdDev / s: 42,518.20 operations --------------- RESULTS: NBenchSample.DictionaryThroughputTests+AddThroughput_IterationsMode --------------- [Counter] AddCounter: Max: 20,000,000.00 operations, Average: 20,000,000.00 operations, Min: 20,000,000.00 operations, StdDev: 0.00 operations [Counter] AddCounter: Max / s: 7,409,380.61 operations, Average / s: 7,250,991.24 operations, Min / s: 6,880,938.73 operations, StdDev / s: 148,085.19 operations
در اینجا برای گزارش دادن، عددهای Average و Average / s باید مورد استفاده قرار گیرند.
یک تجربهی مهم: نگارش 11 آن را با SQL Server X64 تست کردم کار نکرد. اما نگارش 9 کار میکند.
مستندات کامل
پس از نصب ابتدایی آن، مراحل ذیل را برای فعال سازی آن باید طی کرد:
1) تنظیم مسیر پوشه bin نصب فیلتر (مهم!)
Start > Control Panel > System > Advanced Environment Variables -> System Variables -> find PATH
C:\Program Files\Adobe\Adobe PDF iFilter 9 for 64-bit platforms\bin\
سپس کل سیستم را ری استارت کنید.
2) ثبت آن در وهلهی جاری SQL Server
برای این منظور ابتدا دستورات ذیل را جرا کنید:
exec sys.sp_fulltext_service 'load_os_resources', 1; EXEC sp_fulltext_service 'verify_signature', 0 EXEC sp_fulltext_service 'update_languages'; -- update language list EXEC sp_fulltext_service 'restart_all_fdhosts'; -- restart daemon reconfigure with override
سپس در management studio یکبار بر روی وهلهی جاری کلیک راست کرده و گزینهی Restart را انتخاب کنید (مهم).
3) پس از ریاستارت SQL Server، اطمینان حاصل کنید که این فیلتر جدید نصب شدهاست:
exec sys.sp_help_fulltext_system_components 'filter';
و یا کوئری ذیل نیز برای این منظور مفید است:
SELECT document_type, path from sys.fulltext_document_types where document_type = '.pdf'
4) در ادامه برای استفاده از آن آزمایش ذیل را ترتیب خواهیم داد.
ایجاد یک جدول جدید که فایلهای باینری PDF را در خود ذخیره میکند:
CREATE TABLE PdfDocuments ( id INT IDENTITY(1,1) NOT NULL, doctype NCHAR(4) NOT NULL, doccontent VARBINARY(MAX) NOT NULL, CONSTRAINT PK_PdfDocuments PRIMARY KEY CLUSTERED(id) );
سپس چند رکورد را در آن ثبت میکنیم. برای نمونه دو مقالهی خروجی PDF سایت جاری را در این جدول ثبت خواهیم کرد:
INSERT INTO PdfDocuments(doctype, doccontent) SELECT N'PDF', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Downloads\1732-DotNetTips.pdf', SINGLE_BLOB) AS doc; INSERT INTO PdfDocuments(doctype, doccontent) SELECT N'PDF', bulkcolumn FROM OPENROWSET (BULK 'C:\Users\Vahid\Downloads\1733-DotNetTips.pdf', SINGLE_BLOB) AS doc;
http://msdn.microsoft.com/en-us/library/ms692560%28v=vs.85%29.aspx
-- Search property list CREATE SEARCH PROPERTY LIST PdfSearchPropertyList; GO ALTER SEARCH PROPERTY LIST PdfSearchPropertyList ADD 'Author' WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9', PROPERTY_INT_ID = 4, PROPERTY_DESCRIPTION = 'Author - author of a given item.'); GO
در اینجا اگر علاقمند بودید، stop list معرفی شده در قسمتهای قبل را نیز میتوان افزود.
CREATE FULLTEXT STOPLIST SQLStopList; GO -- Add a stopwords ALTER FULLTEXT STOPLIST SQLStopList ADD N'به' LANGUAGE 'English'; ALTER FULLTEXT STOPLIST SQLStopList ADD N'با' LANGUAGE 'English'; --.....
سپس یک کاتالوگ FTS و ایندکس Full-Text ایی را بر روی این جدول ایجاد میکنیم:
-- Full-text catalog CREATE FULLTEXT CATALOG PdfDocumentsFtCatalog; GO -- Full-text index CREATE FULLTEXT INDEX ON PdfDocuments ( [doccontent] TYPE COLUMN [doctype] Language 1033 STATISTICAL_SEMANTICS ) KEY INDEX PK_PdfDocuments ON PdfDocumentsFtCatalog WITH STOPLIST = SQLStopList, SEARCH PROPERTY LIST = PdfSearchPropertyList, CHANGE_TRACKING AUTO; GO
آیا کار میکند ؟ چیزی ایندکس شدهاست؟
SELECT I.document_id, I.display_term, I.occurrence_count FROM sys.dm_fts_index_keywords_by_document(DB_ID(DB_NAME()), OBJECT_ID(N'dbo.PdfDocuments')) AS I INNER JOIN dbo.PdfDocuments D ON D.id = I.document_id;
انجام دو کوئری بر روی آن. یکی برای یافتن متنی ساده و دیگری برای یافتن خواص
SELECT * FROM PdfDocuments WHERE CONTAINS(doccontent, N'است') SELECT I.document_id, I.display_term, I.property_id FROM sys.dm_fts_index_keywords_by_property(DB_ID(DB_NAME()), OBJECT_ID(N'dbo.PdfDocuments')) AS I INNER JOIN dbo.PdfDocuments D ON D.id = I.document_id;
دریافت ایندکس ردیف جاری جدول هر صفحه
int GetRowIndexPerPage(int currentRowIndex, int numberOfRowsPerPage) { return (currentRowIndex % numberOfRowsPerPage) + 1; } var myResult = someTable.Select((r, i) => new { Row = r, Index = GetRowIndexPerPage(i, 4) });
کتابخانه At.js
- Support IE 7+ for textarea.
- Supports HTML5 contentEditable elements (NOT including IE 8)
- Can listen to any character and not just '@'. Can set up multiple listeners for different characters with different behavior and data
- Listener events can be bound to multiple inputors.
- Format returned data using templates
- Keyboard controls in addition to mouse
-
Tab
orEnter
keys select the value -
Up
andDown
navigate between values (andCtrl-P
andCtrl-N
also) -
Right
andleft
will re-search the keyword.
-
- Custom data handlers and template renderers using a group of configurable callbacks
- Supports AMD