در ضمن این نکته رو فراموش نکنید که طول رشتهی ارسالی نباید بیشتر از 1000 کاراکتر باشه.
من در این مورد به پشتیبانی سایت تیکت فرستادم اگر جواب دادند،به اشتراک میزارم.
سرعت واکشی اطلاعات در List و Dictionary
الف) تبدیلگر HTML به XAML نوشته شده توسط خود مایکروسافت
ب) پروژه فوق العاده جالب HTML Renderer
به عبارتی اگر وب سرور IIS بود و View State هم حجمی را نمایش داد، سایت 100 درصد بر مبنای ASP.NET Web forms است.
بررسی علت CPU Usage بالای برنامه در حال اجرا
از کجا میشه درصد استفاده از CPU رو متوجه شد؟ وقتی CPU Usage ما به 100 درصد میرسه نه میشه به سرور کانکت شد و نه اینکه وقتی کانکتیم میذاره برنامه ای رو اجرا کنیم. بخاطر همین مجبوریم وقنی درصد از حدی بالاتر رفت بلافاصله یه سری اطلاعات را ثبت کنیم.
آموزش TypeScript #5
export interface ILogger { log(message: string): void; }
حال نیاز به کلاسی داریم که این اینترفیس را پیاده سازی کند. این پیاده سازی به صورت زیر انجام میگیرد:
export class Logger implements ILogger { }
export class AnnoyingLogger implements ILogger { log(message: string): void{ alert(message); } }
export class MyClass implements IFirstInterface, ISecondInterface { }
IPerson { firstName: string; lastName: string; } class Person implements IPerson { constructor(public firstName: string, public lastName: string) { } } varpersonA: IPerson = newPerson('Masoud', 'Pakdel'); //expilict varpersonB: IPerson = { firstName: 'Ashkan', lastName: 'Shahram'}; // duck typing
پیاده سازی چند اینترفیس به صورت همزمان
همانند دات نت که یک کلاس فقط میتواند از یک کلاس ارث ببرد ولی میتواند n تا اینترفیس را پیاده سازی کند در TypeScript نیز چنین قوانینی وجود دارد. یعنی یک اینترفیس میتواند چندین اینترفیس دیگر را توسعه دهد(extend) و کلاسی که این اینترفیس را پیاده سازی میکند باید تمام توابع اینترفیسها را پیاده سازی کند. مثال:
interface IMover { move() : void; } interface IShaker { shake() : void; } interface IMoverShaker extends IMover, IShaker { } class MoverShaker implements IMoverShaker { move() { } shake() { } }
instanceof
از instanceof زمانی استفاده میکنیم که قصد داشته باشیم که یک instance را با یک نوع مشخص مقایسه کنیم. اگر instance مربوطه از نوع مشخص باشد یا از این نوع ارث برده باشد مقدار true برگشت داده میشود در غیر این صورت مقدار false خواهد بود.
یک مثال:
var isLogger = logger instanceof Utilities.Logger; var isLogger = logger instanceof Utilities.AnnoyingLogger; var isLogger = logger instanceof Utilities.Formatter;
در TypeScript میتوان مانند زبانهای شی گرای دیگر Method overriding را پیاده سازی کرد. یعنی میتوان متدهای کلاس پایه را در کلاس مشتق شده تحریف کرد. با یک مثال به شرح این مورد خواهم پرداخت.
فرض کنید یک کلاس پایه به صورت زیر داریم:
class BaseEmployee { constructor (public fname: string,public lname: string) { } sayInfo() { alert('this is base class method'); } }
class Employee extends BaseEmployee { sayInfo() { alert('this is derived class method'); } } window.onload = () => { var first: BaseEmployee= new Employee(); first.sayInfo(); var second: BaseEmployee = new BaseEmployee(); second.sayInfo(); }
*اگر در کلاس مشتق شده قصد داشته باشیم که به توابع و فیلدهای کلاس پایه اشاره کنیم باید از کلمه کلیدی super استفاده کنیم.(معادل base در #C).
مثال:
class Animal { constructor (public name: string) { } } class Dog extends Animal { constructor (public name: string, public age:number) { super(name); } sayHello() {
alert(super.name);
} }
public class Dog : Animal { public Dog (string name, int age):base(name) { } }
*دقت کنید که مباحث مربوط به interface و private modifier و Type safety که پیشتر در مورد آنها بحث شد، فقط در فایلهای TypeScript و در هنگام کد نویسی و طراحی معنی دار هستند، زیرا بعد از کامپایل فایلهای ts این مفاهیم در Javascript پشتیبانی نمیشوند در نتیجه هیچ مورد استفاده هم نخواهد داشت.
ادامه دارد...
فرض کنید جدولی دارید با چند ستون محدود که نتیجهی نهایی گزارش آن مثلا 100 صفحه است. جهت صرفه جویی در کاغذ مصرفی شاید بهتر باشد که این جدول را به صورت چند ستونی مثلا 5 ستون در یک صفحه نمایش داد؛ چیزی شبیه به شکل زیر:
روش انجام اینکار به کمک iTextSharp به صورت زیر است:
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
var table1 = new PdfPTable(1);
table1.WidthPercentage = 100f;
table1.HeaderRows = 2;
table1.FooterRows = 1;
//header row
var headerCell = new PdfPCell(new Phrase("header"));
table1.AddCell(headerCell);
//footer row
var footerCell = new PdfPCell(new Phrase(" "));
table1.AddCell(footerCell);
//adding some rows
for (int i = 0; i < 400; i++)
{
var rowCell = new PdfPCell(new Phrase(i.ToString()));
table1.AddCell(rowCell);
}
// wrapping table1 in multiple columns
ColumnText ct = new ColumnText(pdfWriter.DirectContent);
ct.RunDirection = PdfWriter.RUN_DIRECTION_RTL;
ct.AddElement(table1);
int status = 0;
int count = 0;
int l = 0;
int columnsWidth = 100;
int columnsMargin = 7;
int columnsPerPage = 4;
int r = columnsWidth;
bool isRtl = true;
// render the column as long as it has content
while (ColumnText.HasMoreText(status))
{
if (isRtl)
{
ct.SetSimpleColumn(
pdfDoc.Right - l, pdfDoc.Bottom,
pdfDoc.Right - r, pdfDoc.Top
);
}
else
{
ct.SetSimpleColumn(
pdfDoc.Left + l, pdfDoc.Bottom,
pdfDoc.Left + r, pdfDoc.Top
);
}
var delta = columnsWidth + columnsMargin;
l += delta;
r += delta;
// render as much content as possible
status = ct.Go();
// go to a new page if you've reached the last column
if (++count > columnsPerPage)
{
count = 0;
l = 0;
r = columnsWidth;
pdfDoc.NewPage();
}
}
}
//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
توضیحات:
تا قسمت تعریف جدول و اضافه کردن سطرها و ستونهای مورد نظر، همان بحث «تکرار خودکار سرستونهای یک جدول در صفحات مختلف، توسط iTextSharp» میباشد.
اصل مطلب از قسمت ColumnText شروع میشود. با استفاده از شیء ColumnText میتوان محتوای خاصی را در طی چند ستون در صفحه نمایش داد. عرض این ستونها هم توسط متد SetSimpleColumn مشخص میشود و همچنین محل دقیق قرارگیری آنها در صفحه. در اینجا دو حالت راست به چپ و چپ به راست در نظر گرفته شده است.
اگر حالت راست به چپ را در نظر بگیریم، محل قرارگیری اولین ستون از سمت راست صفحه (pdfDoc.Right) باید تعیین شود. سپس هربار به اندازهی عرضی که مد نظر است باید محل شروع ستون را مشخص کرد (pdfDoc.Right - l). هر زمانیکه ct.Go فراخوانی میشود، تاجایی که میسر باشد، اطلاعات جدول 1 در یک ستون درج میشود. سپس بررسی میشود که تا این لحظه چند ستون در صفحه نمایش داده شده است. اگر تعداد مورد نظر ما (columnsPerPage) تامین شده باشد، کار را در صفحهی بعد ادامه خواهیم داد (pdfDoc.NewPage)، در غیراینصورت مجددا مکان یک ستون دیگر در همان صفحه تعیین شده و کار افزودن اطلاعات به آن آغاز خواهد شد و این حلقه تا جایی که تمام محتوای جدول 1 را درج کند، ادامه خواهد یافت.
در ادامهی مطلب قبلی، نکاتی دیگر را جهت افزایش کارآیی سیستمهای مبتنی بر EF اشاره خواهیم کرد:
عدم استفاده از کوئریهای کلی
فرض کنید در یک فرم جستجو، 4 تکست باکس FirstName, LastName, City و PostalZipCode برای عملیات جستجو در نظر گرفته شده است و کاربر میتواند بر اساس آنها جستجو را انجام دهد.
var searchModel = new Pupil { FirstName = "Ben", LastName = null, City = null, PostalZipCode = null }; List<Pupil> pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.City == null || p.LastName == searchModel.City) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList()
USE [EFSchoolSystem] DECLARE @p__linq__0 NVarChar(4000) SET @p__linq__0 = 'Ben' DECLARE @p__linq__1 NVarChar(4000) SET @p__linq__1 = 'Ben' DECLARE @p__linq__2 NVarChar(4000) SET @p__linq__2 = '' DECLARE @p__linq__3 NVarChar(4000) SET @p__linq__3 = '' DECLARE @p__linq__4 NVarChar(4000) SET @p__linq__4 = '' DECLARE @p__linq__5 NVarChar(4000) SET @p__linq__5 = '' DECLARE @p__linq__6 NVarChar(4000) SET @p__linq__6 = '' DECLARE @p__linq__7 NVarChar(4000) SET @p__linq__7 = '' -- Executed query SELECT TOP (100) [Extent1].[PupilId] AS [PupilId] , [Extent1].[FirstName] AS [FirstName] , [Extent1].[LastName] AS [LastName] , [Extent1].[Address1] AS [Address1] , [Extent1].[Adderss2] AS [Adderss2] , [Extent1].[PostalZipCode] AS [PostalZipCode] , [Extent1].[City] AS [City] , [Extent1].[PhoneNumber] AS [PhoneNumber] , [Extent1].[SchoolId] AS [SchoolId] , [Extent1].[Picture] AS [Picture] FROM [dbo].[Pupils] AS [Extent1] WHERE (@p__linq__0 IS NULL OR [Extent1].[FirstName] = @p__linq__1) AND (@p__linq__2 IS NULL OR [Extent1].[LastName] = @p__linq__3) AND (@p__linq__4 IS NULL OR [Extent1].[LastName] = @p__linq__5) AND (@p__linq__6 IS NULL OR [Extent1].[PostalZipCode] = @p__linq__7)
مشکلی که در این دسته از کوئریهای عمومی ایجاد میگردد آن است که ممکن است پلنی که برای یک گروه از پارامترهای ورودی مناسب باشد (جستجو بر اساس نام) برای سایر پارامترهای ورودی نامناسب باشد. تصور کنید تمام دانش آموزان، در شهر نیویورک یا بوستون زندگی میکنند. بنابراین این ستون از تنوع انتخاب کمتری برخوردار است در مقایسه با ستون نام خانوادگی و فرض کنید پلن، براساس پارامتر شهر ایجاد شده است. بنابراین ایجاد این پلن برای سایر پارامترها از کارآیی کافی برخوردار نیست. این مشکل با نام Bad Parameter Sniffing شناخته میشود و دربارهی Parameter Sniffing در اینجا به تفصیل اشاره شده است.
این مشکل زمانی بیشتر مشکل ساز خواهد شد که 99 درصد دانش آموزان در شهر نیویورک و فقط 1 درصد آنها در شهر بوستون زندگی میکنند و پلن ایجاد شده بر اساس پارامتر شهر بوستون باشد.
راه حل اول:
برای حل این مشکل تنها یک راه حل خاص وجود ندارد و باید براساس شرایط برنامه، کوئری از حالت عمومی خارج گردد؛ مانند زیر:
if (searchModel.City == null) { pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList(); } else { pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.City == null || p.LastName == searchModel.City) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList(); }
راه حل دوم:
کامپایل مجدد پلن در اجرای هر کوئری، اما این راه حل سرباری را تحمیل میکند. بدین منظور مترجم زیر را ایجاد کنید:
public class RecompileDbCommandInterceptor : IDbCommandInterceptor { public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if(!command.CommandText.EndsWith(" option(recompile)")) { command.CommandText += " option(recompile)"; } } //and implement other interface members }
var interceptor = new RecompileDbCommandInterceptor(); DbInterception.Add(interceptor); var pupils = db.Pupils.Where(p => p.City = city).ToList(); DbInterception.Remove(interceptor);
راه حل سوم:
استفاده از اجرای به تعویق افتاده به شکل زیر است:
var result = db.Pupils.AsQueryable(); if(searchModel.FirstName != null ) result = result.Where(p => p.FirstName == searchModel.FirstName); if(searchModel.LastName != null ) result = result.Where(p => p.LastName == searchModel.LastName); if(searchModel.PostalZipCode != null ) result = result.Where(p => p.PostalZipCode == searchModel.PostalZipCode); if(searchModel.City != null ) result = result.Where(p => p.City == searchModel.City);
افزونگی کشِ پلن
استفادهی مجدد از پلن بدلیل عدم ایجاد مجدد آن در زمان اجرای هر کوئری، بسیار خوب است. برای استفادهی مجدد از پلن، باید دستورات ارسالی یکسان باشند؛ مانند کوئریهای پارامتریک. در EF هنگامیکه از متغیرها استفاده کنید، کوئریها پارامتریک تولید میکند؛ اما یک استثناء وجود دارد: ()Skip و ()Take
2 متد فوق بیشتر جهت صفحه بندی استفاده میشوند:
var schools = db.Schools .OrderBy(s => s.PostalZipCode) .Skip(model.Page * model.ResultsPerPage) .Take(model.ResultsPerPage) .ToList();
حال اگر قصد دریافت اطلاعات صفحهی 500 را داشته باشید، مقادیر کوئری بعدی بترتیب 100 و 50000 خواهد بود و بجای مقادیر تصویر بالا 100 و 50000 قرار داده میشوند و کوئری متفاوتی با پلن متفاوتی ایجاد خواهد شد و اس کیو ال پلن کوئری قبلی را مورد استفاده قرار نخواهد داد و با اجرای کوئری دوم، پلن متفاوتی ایجاد خواهد کرد که این باعث ایجاد افزونگی پلنها خواهد شد و همانگونه که قبلا اشاره شد ایجاد پلن جدید هزینه بر است.
نکته: جهت مشاهده پلنهای کش شده در اس کیو ال، دستور زیر اجرا کنید:
SELECT text, query_plan FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_query_plan(plan_handle) CROSS APPLY sys.dm_exec_sql_text(plan_handle)
- صدمه به کارآیی؛ هربار EF یک کوئری و اس کیو ال یک پلن جدید را ایجاد میکنند.
- افزایش اشغال حافظه؛ کش شدن کوئریها توسط EF سمت کلاینت و کش شدن پلنها در اس کیو ال سرور (کش بی رویهی پلنها باعث حذف سایر پلنهای مورد استفاده بدلیل محدودیت حافظه میشود که امکان بروز اختلال در کارآیی را بههمراه خواهد داشت.)
علت بروز مشکل:
هنگامیکه یک مقدار int، به متدهای ()Skip و ()Take ارسال میشود، EF نمیتواند تشخیص دهد این مقدار ارسالی ثابت (absolute) مانند (100)Take است یا توسط یک متغیر مانند (متغیر)Take تولید شده است. به همین خاطر EF مقدار ارسال شده را پارامتریک در نظر نمیگیرد.
راه حل:
در EF6 پیاده سازی دیگری برای متدهای ()Skip و ()Take ارائه شده است که برای حل مشکل فوق میتوان به کار گرفت، این پیاده سازی امکان دریافت lambada بجای int را دارد که باعث ایجاد کوئریهای پارامتریک خواهد شد.
int resultsToSkip = model.Page * model.ResultsPerPage; var schools = db.Schools .OrderBy(s => s.PostalZipCode) .Skip(() => resultsToSkip) //must pre-calculate this value .Take(() => model.ResultsPerPage) .ToList();
همانطور که مشاهده میکنید این بار EF کوئری پارامتریک ایجاد و ارسال کرده است.
ایجاد ساختار ابتدایی پروژه
برای ساخت پروژه، به خط فرمان مراجعه کرده و با دستور زیر، یک پروژهی react از نوع typescript را ایجاد میکنیم.
npx create-react-app todo-mobx --template typescript cd todo-mobx
برای توسعهی این مثال، از محیط توسعهی VSCode استفاده میکنیم. اگر VSCode بر روی سیستم شما نصب باشد، در همان مسیری که خط فرمان باز است، دستور زیر را اجرا کنید؛ پروژهی شما در VSCode باز میشود:
code
سپس در محیط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest npm install mobx mobx-react-lite --save
در ادامه برای استایل بندی بهتر برنامه از کتابخانههای bootstrap و font-awesome استفاده میکنیم:
npm install bootstrap --save npm install font-awesome --save
سپس فایل index.tsx را باز کرده و دو خط زیر را به آن اضافه میکنیم:
import "bootstrap/dist/css/bootstrap.css"; import "font-awesome/css/font-awesome.css";
کتابخانهی MobX، از تزئین کنندهها یا decorators استفاده میکند. بنابراین نیاز است به tsconfig پروژه مراجعه کرده و خط زیر را به آن اضافه کنیم:
"compilerOptions": { .... , "experimentalDecorators": true }
ایجاد مخازن حالت MobX
در ادامه نیاز است storeهای MobX را ایجاد کنیم و بعد آنها را به react اتصال دهیم. بدین منظور یک پوشهی جدید را در مسیر src، به نام stores ایجاد میکنیم و سپس فایل جدیدی را به نام todo-item.ts در آن با محتوای زیر ایجاد میکنیم:
import { observable, action } from "mobx"; export default class TodoItem { id = Date.now(); @observable text: string = ''; @observable isDone: boolean = false; constructor(text: string) { this.text = text; } @action toggleIsDone = () => { this.isDone = !this.isDone } @action updateText = (text: string) => { this.text = text; } }
در همان مسیر stores، فایل دیگری را نیز به نام todo-list.ts، با محتوای زیر ایجاد میکنیم:
import { observable, computed, action } from "mobx"; import TodoItem from "./todo-item"; export class TodoList { @observable.shallow list: TodoItem[] = []; constructor(todos: string[]) { todos.forEach(this.addTodo); } @action addTodo = (text: string) => { this.list.push(new TodoItem(text)); } @action removeTodo = (todo: TodoItem) => { this.list.splice(this.list.indexOf(todo), 1); }; @computed get finishedTodos(): TodoItem[] { return this.list.filter(todo => todo.isDone); } @computed get openTodos(): TodoItem[] { return this.list.filter(todo => !todo.isDone); } }
توضیحات:
مفهوم observable@: کل شیء state را به صورت یک شیء قابل ردیابی JavaScript ای ارائه میکند.
مفهوم computed@: این نوع خواص، مقدار خود را زمانیکه observableهای وابستهی به آنها تغییر کنند، به روز رسانی میکنند.
مفهوم action@: جهت به روز رسانی state و سپس نمایش تغییرات یا نمایش نمونهی دیگری در DOM میباشند.
import { createContext, useContext } from "react"; import { TodoList } from "../stores/todo-list"; export const StoreContext = createContext<TodoList>({} as TodoList); export const StoreProvider = StoreContext.Provider; export const useStore = (): TodoList => useContext(StoreContext);
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import "bootstrap/dist/css/bootstrap.css"; import "font-awesome/css/font-awesome.css"; import { TodoList } from './stores/todo-list'; import { StoreProvider } from './providers/store-provider'; const todoList = new TodoList([ 'Read Book', 'Do exercise', 'Watch Walking dead series' ]); ReactDOM.render( <StoreProvider value={todoList}> <App /> </StoreProvider> , document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
import React, { useState } from 'react'; import { useStore } from '../providers/store-provider'; export const TodoNew = () => { const [newTodo, setTodo] = useState(''); const todoList = useStore(); const addTodo = () => { todoList.addTodo(newTodo); setTodo(''); }; return ( <div className="input-group mb-3"> <input type="text" className="form-control" placeholder="Add To do" value={newTodo} onChange={(e) => setTodo(e.target.value)} /> <div className="input-group-append"> <button className="btn btn-success" type="submit" onClick={addTodo}>Add Todo</button> </div> </div> ) };
import React from 'react'; import { TodoItem } from "./TodoItem"; import { useObserver } from "mobx-react-lite"; import { useStore } from '../providers/store-provider'; export const TodoList = () => { const todoList = useStore(); return useObserver(() => ( <div> <h1>Open Todos</h1> <table className="table"> <thead className="thead-dark"> <tr> <th>Name</th> <th className="text-left">Do It?</th> <th>Actions</th> </tr> </thead> <tbody> { todoList.openTodos.map(todo => <tr key={`${todo.id}-${todo.text}`}> <TodoItem todo={todo} /> </tr>) } </tbody> </table> <h1>Finished Todos</h1> <table className="table"> <thead className="thead-light"> <tr> <th>Name</th> <th className="text-left">Do It?</th> <th>Actions</th> </tr> </thead> <tbody> { todoList.finishedTodos.map(todo => <tr key={`${todo.id}-${todo.text}`}> <TodoItem todo={todo} /> </tr>) } </tbody> </table> </div> )); };
import React, { useState } from 'react'; import TodoItemClass from "../stores/todo-item"; import { useStore } from '../providers/store-provider'; interface Props { todo: TodoItemClass; } export const TodoItem = ({ todo }: Props) => { const todoList = useStore(); const [newText, setText] = useState(''); const [isEditing, setEdit] = useState(false); const saveText = () => { todo.updateText(newText); setEdit(false); setText(''); }; return ( <React.Fragment> { isEditing ? <React.Fragment> <td> <input className="form-control" placeholder={todo.text} type="text" onChange={(e) => setText(e.target.value)} /> </td> <td></td> <td> <button className="btn btn-xs btn-success " onClick={saveText}>Save</button> </td> </React.Fragment> : <React.Fragment> <td> {todo.text} </td> <td className="text-left"> <input className="form-check-input" type="checkbox" onChange={todo.toggleIsDone} defaultChecked={todo.isDone}></input> </td> <td> <button className="btn btn-xs btn-warning " onClick={() => setEdit(true)}> <i className="fa fa-edit"></i> </button> <button className="btn btn-xs btn-danger ml-2" onClick={() => todoList.removeTodo(todo)}> <i className="fa fa-remove"></i> </button> </td> </React.Fragment> } </React.Fragment> ) };