پروژه تک صفحه ای WebApi و Angular 2
ایجاد ساختار ابتدایی پروژه
برای ساخت پروژه، به خط فرمان مراجعه کرده و با دستور زیر، یک پروژهی 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> ) };
SQL Antipattern #1
بخش اول : Jaywalking
در این بخش در حال توسعه ویژگی نرم افزاری هستیم که در آن هرکاربر به عنوان یک کاربر اصلی برای یک محصول تخصیص داده میشود. در طراحی اصلی ما فقط اجازه میدهیم یک کاربر متعلق به هر محصول باشد، اما امکان چنین تقاضایی وجود دارد که چند کاربر نیز به یک محصول اختصاص داده شوند.
در این صورت، اگر پایگاه داده را به نحوی تغییر دهیم که شناسهی حساب کاربران را در لیستی که با کاما از یکدیگر جدا شدهاند ذخیره نماییم، خیلی سادهتر به نظر میرسد نسبت به اینکه بصورت جداگانه آنها را ثبت نماییم.
برنامه نویسان معمولا برای جلوگیری از ایجاد جدول واسطی [1] که رابطههای چند به چند زیادی دارد از یک لیست که با کاما دادههایش از هم جدا شدهاند، استفاده میکنند. بدین جهت اسم این بخش jaywalking ,antipatten میباشد، زیرا jaywalking نیز عملیاتی است که از تقاطع جلوگیری میکند.
1.1 هدف: ذخیره کردن چندین صفت
طراحی کردن جدولی که ستون آن فقط یک مقدار دارد، بسیار ساده و آسان میباشد. شما میتوانید نوع دادهایی که متعلق به ستون میباشد را انتخاب نمایید. مثلا از نوع (int,date…)
چگونه میتوانیم مجموعهایی از مقادیری که به یکدیگر مرتبط هستند را در یک ستون ذخیره نماییم ؟
در مثال ردیابی خطای پایگاه داده، ما یک محصول را به یک کاربر، با استفاده از ستونی که نوع آن integer است، مرتبط مینماییم. هر کاربر ممکن است محصولاتی داشته باشد و هر محصول به یک contact اشاره کند. بنابراین یک ارتباط چند به یک بین محصولات و کاربر برقرار است. برعکس این موضوع نیز صادق است؛ یعنی امکان دارد هر محصول متعلق به چندین کاربر باشد و یک ارتباط یک به چند ایجاد شود. در زیر جدول محصولات را به صورت عادی آورده شده است:
CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, product_name VARCHAR(1000), account_id BIGINT UNSIGNED, -- . . . FOREIGN KEY (account_id) REFERENCES Accounts(account_id) ); INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder' , 12);
CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, product_name VARCHAR(1000), account_id VARCHAR(100), -- comma-separated list -- . . . ); INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder' , '12,34' );
اکنون مشکلات کارایی و جامعیت دادهها را در این راه حل پیشنهادی بررسی مینماییم .
بدست آوردن محصولاتی برای یک کاربر خاص
بدلیل اینکه تمامی شناسهی کاربران که بصورت کلید خارجی جدول Products میباشند به صورت رشته در یک فیلد ذخیره شدهاند و حالت ایندکس بودن آنها از دست رفته است، بدست آوردن محصولاتی برای یک کاربر خاص سخت میباشد. به عنوان مثال بدست آوردن محصولاتی که کاربری با شناسهی 12 خریداری نموده بهصورت زیر میباشد:
SELECT * FROM Products WHERE account_id REGEXP '[[:<:]]12[[:>:]]' ;
SELECT * FROM Products AS p JOIN Accounts AS a ON p.account_id REGEXP '[[:<:]]' || a.account_id || '[[:>:]]' WHERE p.product_id = 123;
SELECT product_id, LENGTH(account_id) - LENGTH(REPLACE(account_id, ',' , '' )) + 1 AS contacts_per_product FROM Products;
UPDATE Products SET account_id = account_id || ',' || 56 WHERE product_id = 123;
<?php $stmt = $pdo->query( "SELECT account_id FROM Products WHERE product_id = 123"); $row = $stmt->fetch(); $contact_list = $row['account_id' ]; // change list in PHP code $value_to_remove = "34"; $contact_list = split(",", $contact_list); $key_to_remove = array_search($value_to_remove, $contact_list); unset($contact_list[$key_to_remove]); $contact_list = join(",", $contact_list); $stmt = $pdo->prepare( "UPDATE Products SET account_id = ? WHERE product_id = 123"); $stmt->execute(array($contact_list));
INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder' , '12,34,banana' );
محدودیت طول لیست
UPDATE Products SET account_id = '10,14,18,22,26,30,34,38,42,46' WHERE product_id = 123;
UPDATE Products SET account_id = '101418,222630,343842,467790' WHERE product_id = 123;
CREATE TABLE Contacts ( product_id BIGINT UNSIGNED NOT NULL, account_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (product_id, account_id), FOREIGN KEY (product_id) REFERENCES Products(product_id), FOREIGN KEY (account_id) REFERENCES Accounts(account_id) );
جدول
Contacts یک جدول رابطه ایی بین جداول Products,Accounts
بدست آوردن محصولات برای کاربران و موارد مربوط به آن
ما براحتی میتوانیم تمامی محصولاتی که مختص به یک کاربر هستند را بدست آوریم. در این شیوه خاصیت ایندکس بودن شناسهی کاربران حفظ میشود به همین دلیل queryهای آن برای خواندن و بهینه کردن راحتتر میباشند. در این روش به کاراکتری برای جدا کردن ورودیها از یکدیگر نیاز نداریم چون هر کدام از آنها در یک سطر جداگانه ثبت میشوند. برای ویرایش کردن کاربرانی که یک محصول را خریداری نموده اند، کافیست یک سطر از جدول واسط را اضافه یا حذف نماییم. درنمونه کد زیر، ابتدا در جدول Contacts کاربری با شناسهی 34 که محصولی با شناسهی 456 را خریداری کرده، درج شده است و در خط بعد عملیات حذف با شرط آنکه شناسهی کاربر و محصول به ترتیب 34،456 باشد روی جدول Contacts اعمال شده است.
INSERT INTO Contacts (product_id, account_id) VALUES (456, 34); DELETE FROM Contacts WHERE product_id = 456 AND account_id = 34;
ایجاد توابع تجمیعی
به عنوان نمونه در مثال زیر براحتی ما میتوانیم تعداد محصولات در هر حساب کاربری را بدست آوریم:
SELECT account_id, COUNT(*) AS products_per_account FROM Contacts GROUP BY account_id;
اعتبارسنجی شناسه محصولات
از آنجاییکه مقادیری که در جدول قرار دارند کلید خارجی میباشند میتوان صحت اعتبار آنها را بررسی نمود. بعنوان مثال Contacts.account_id به Account.account_id اشاره میکند. در ضمن برای هر فیلد نوع آن را میتوان مشخص کرد تا فقط همان نوع داده را بپذیرد.
محدودیت طول لیست
نسبت به روش قبلی تقریبا در این حالت محدودیتی برای تعداد کاراکترهای ورودی نداریم.
مزیتهای دیگر استفاده از جدول واسط
کارایی روش دوم بهتر از حالت قبلی میباشد چون ایندکس بودن شناسهها حفظ شده است. همچنین براحتی میتوانیم فیلدی را به این جدول اضافه نماییم مثلا (time, date… )
This is the first of an eight part series where I am joined by Phil Japikse to discuss design patterns. A design pattern is a best practice you can use in your code to solve a common problem. In this episode, Phil demonstrates the Command and Memento patterns.
Episodes in this series:
- Command/Memento patterns (this episode)
- Strategy pattern
- Template Method pattern (to be published 7/20)
- Observer/Publish-Subscribe patterns (to be published 7/25)
- Singleton pattern (to be published 8/8)
- Factory patterns (to be published 8/10)
- Adapter/Facade patterns (to be published 8/15)
- Decorator pattern (to be published 8/17)
This article dives into the mock questions I would ask, along with responses that are my personal best guess to the answers. Could my answers not reflect actual opinions shared by the team at Microsoft? Sure, but I'm hoping folks from the .NET team can jump in to correct me if I am way off base.
This is a rather interesting time for .NET – what's being done shapes the future of .NET for the next decade. Let's ask the honest questions and hopefully all of us will understand the new .NET ecosystem a little better.