SELECT TABLE_NAME, column_Name FROM INFORMATION_SCHEMA.COLUMNS WHERE column_Name LIKE '%' + 'Name' + '%'
اهمیت code review
و کسانی هم که نمیخواهند از ORM استفاده کنند، Wrappers خیلی خوبی بعد از دات نت 4 برای ADO.NET اومده. مطلب در موردش در سایت هست تحت عنوان Micro ORMs
حذف موجودیتهای منفصل
فرض کنید موجودیتی را از یک سرویس WCF دریافت کرده اید و میخواهید آن را برای حذف علامت گذاری کنید. مدل زیر را در نظر بگیرید.
همانطور که میبینید مدل ما صورت حسابها و پرداختهای متناظر را ارائه میکند. در اپلیکیشن جاری یک سرویس WCF پیاده سازی کرده ایم که عملیات دیتابیسی کلاینتها را مدیریت میکند. میخواهیم توسط این سرویس آبجکتی را (در اینجا یک موجودیت پرداخت) حذف کنیم. برای ساده نگاه داشتن مثال جاری، مدلها را در خود سرویس تعریف میکنیم. برای ایجاد سرویس مذکور مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع WCF Service Library بسازید و نام آن را به Recipe5 تغییر دهید.
- روی پروژه کلیک راست کنید و گزینه Add New Item را انتخاب کنید. سپس گزینههای Data -> ADO.NET Entity Data Model را برگزینید.
- از ویزارد ویژوال استودیو برای اضافه کردن یک مدل با جداول Invoice و Payment استفاده کنید. برای ساده نگه داشتن مثال جاری، فیلد پیمایشی Payments را از موجودیت Invoice حذف کرده ایم (برای این کار روی خاصیت پیمایشی Payments کلیک راست کنید و گزینه Delete From Model را انتخاب کنید.) روی خاصیت TimeStamp موجودیت Payment کلیک راست کنید و گزینه Properties را انتخاب کنید. سپس مقدار Concurrency Mode آن را به Fixed تغییر دهید. این کار باعث میشود که مقدار این فیلد برای کنترل همزمانی بررسی شود. بنابراین مقدار TimeStamp در عبارت WHERE تمام دستورات بروز رسانی و حذف درج خواهد شد.
- فایل IService1.cs را باز کنید و تعریف سرویس را مانند لیست زیر تغییر دهید.
[ServiceContract] public interface IService1 { [OperationContract] Payment InsertPayment(); [OperationContract] void DeletePayment(Payment payment); }
- فایل Service1.cs را باز کنید و پیاده سازی سرویس را مانند لیست زیر تغییر دهید.
public class Service1 : IService1 { public Payment InsertPayment() { using (var context = new EFRecipesEntities()) { // delete the previous test data context.Database.ExecuteSqlCommand("delete from [payments]"); context.Database.ExecuteSqlCommand("delete from [invoices]"); var payment = new Payment { Amount = 99.95M, Invoice = new Invoice { Description = "Auto Repair" } }; context.Payments.Add(payment); context.SaveChanges(); return payment; } } public void DeletePayment(Payment payment) { using (var context = new EFRecipesEntities()) { context.Entry(payment).State = EntityState.Deleted; context.SaveChanges(); } } }
- برای تست این سرویس به یک کلاینت نیاز داریم. یک پروژه جدید از نوع Console Application به راه حل جاری اضافه کنید و کد آن را مطابق لیست زیر تغییر دهید. فراموش نکنید که ارجاعی به سرویس هم اضافه کنید. روی پروژه کلاینت کلیک راست کرده و Add Service Reference را انتخاب نمایید. ممکن است پیش از آنکه بتوانید سرویس را ارجاع کنید، نیاز باشد پروژه سرویس را ابتدا اجرا کنید (کلیک راست روی پروژه سرویس و انتخاب گزینه Debug -> Start Instance).
class Program { static void Main() { var client = new Service1Client(); var payment = client.InsertPayment(); client.DeletePayment(payment); } }
شرح مثال جاری
در مثال جاری برای بروز رسانی و حذف موجودیتهای منفصل از الگویی رایج استفاده کرده ایم که در سرویسهای WCF و Web API استفاده میشود.
در کلاینت با فراخوانی متد InsertPayment یک پرداخت جدید در دیتابیس ذخیره میکنیم. این متد، موجودیت Payment ایجاد شده را باز میگرداند. موجودیتی که به کلاینت باز میگردد از DbContext منفصل (disconnected) است، در واقع در چنین وضعیتی آبجکت context ممکن است در فضای پروسس دیگری قرار داشته باشد، یا حتی روی کامپیوتر دیگری باشد.
برای حذف موجودیت Payment از متد DeletePayment استفاده میکنیم. این متد به نوبه خود با فراخوانی متد Entry روی آبجکت context و پاس دادن موجودیت پرداخت بعنوان آرگومان، موجودیت را پیدا میکند. سپس وضعیت موجودیت را به EntityState.Deleted تغییر میدهیم که این کار آبجکت را برای حذف علامت گذاری میکند. فراخوانیهای بعدی متد ()SaveChanges موجودیت را از دیتابیس حذف خواهد کرد.
آبجکت پرداختی که برای حذف به context الحاق کرده ایم تمام خاصیت هایش مقدار دهی شده اند، درست مانند هنگامی که این موجودیت به دیتابیس اضافه شده بود. اما از آنجا که از foreign key association استفاده میکنیم، تنها فیلدهای کلید موجودیت، خاصیت همزمانی (concurrency) و TimeStamp برای تولید عبارت where مناسب لازم هستند که نهایتا منجر به حذف موجودیت خواهد شد. تنها استثنا درباره این قاعده هنگامی است که موجودیت شما یک یا چند خاصیت از نوع پیچیده یا Complex Type داشته باشد. از آنجا که خاصیتهای پیچیده، اجزای ساختاری یک موجودیت محسوب میشوند نمیتوانند مقادیر null بپذیرند. یک راه حل ساده این است که هنگامی که EF مشغول ساختن عبارت SQL Delete لازم برای حذف موجودیت بر اساس کلید و خاصیت همزمانی آن است، وهله جدیدی از نوع داده پیچیده خود بسازید. اگر فیلدهای complex type را با مقادیر null رها کنید، فراخوانی متد ()SaveChanges با خطا مواجه خواهد شد.
اگر از یک independent association استفاده میکنید که در آن کثرت (multiplicity) موجودیت مربوطه یک، یا صفر به یک است، EF انتظار دارد که کلیدهای موجودیتها بدرستی مقدار دهی شوند تا بتواند عبارت where مناسب را برای دستورات بروز رسانی و حذف تولید کند. اگر در مثال جاری از یک رابطه independent association بین موجودیتهای Invoice و Payment استفاده میکردیم، لازم بود تا خاصیت پیمایشی Invoice را با وهله ای از صورت حساب مقدار دهی کنیم که خاصیت InvoiceId آن نیز بدرستی مقدار دهی شده باشد. در این صورت عبارت where نهایی شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.
نکته: هنگام پیاده سازی معماریهای n-Tier با Entity Framework، استفاده از رویکرد Foreign Key Association برای موجودیتهای مرتبط باید با ملاحظات جدی انجام شود. پیاده سازی رویکرد Independent Association مشکل است و میتواند کد شما را بسیار پیچیده کند. برای مطالعه بیشتر درباره این رویکردها و مزایا و معایب آنها به این لینک مراجعه کنید که توسط یکی از برنامه نویسان تیم EF نوشته شده است.
اگر موجودیت شما تعداد متعددی Independent Association دارد، مقدار دهی تمام آنها میتواند خسته کننده شود. رویکردی سادهتر این است که وهله مورد نظر را از دیتابیس دریافت کنید و آن را برای حذف علامت گذاری نمایید. این روش کد شما را سادهتر میکند، اما هنگامی که آبجکت را از دیتابیس دریافت میکنید EF کوئری جاری را بازنویسی میکند تا تمام روابط یک، یا صفر به یک بارگذاری شوند. مگر آنکه از گزینه NoTracking روی context خود استفاده کنید. اگر در مثال جاری رویکرد Independent Association را پیاده سازی کرده بودیم، هنگامی که موجودیت Payment را از دیتابیس دریافت میکنیم (قبل از علامت گذاری برای حذف) EF یک Object state entry برای موجودیت پرداخت و یک Relationship entry برای رابطه بین Payment و Invoice میساخت. سپس وقتی که موجودیت پرداخت را برای حذف علامت گذاری میکنیم، EF رابطه بین پرداخت و صورت حساب را هم برای حذف علامت گذاری میکند. در اینجا عبارت where تولید شده مانند قبل، شامل فیلدهای PaymentId, TimeStamp و InvoiceId خواهد بود.
یک گزینه دیگر برای حذف موجودیتها در Independent Associations این است که تمام موجودیتهای مرتبط را مشخصا بارگذاری کنیم (eager loading) و کل Object graph را برای حذف به سرویس WCF یا Web API بفرستیم. در مثال جاری میتوانستیم موجودیت صورتحساب مرتبط با موجودیت پرداخت را مشخصا بارگذاری کنیم. اگر میخواستیم موجودیت Payment را حذف کنیم، میتوانستیم کل گراف را که شامل هر دو موجودیت میشود به سرویس ارسال کنیم. اما هنگام استفاده از چنین روشی باید بسیار دقت کنید، چرا که این رویکرد پهنای باند بیشتری مصرف میکند و زمان پردازش بیشتری هم برای مرتب سازی (serialization) صرف میکند. بنابراین هزینه این رویکرد نسبت به سادگی کدی که بدست میآید به مراتب بیشتر است.
مدل مورد بررسی
public class User { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<BlogPost> BlogPosts { get; set; } } public class BlogPost { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } [ForeignKey("UserId")] public virtual User User { get; set; } public int UserId { get; set; } }
مشکل 1: بارگذاری تعداد زیادی ردیف
var data = context.BlogPosts.ToList();
راه حل: با استفاده از Skip و Take، مباحث صفحهی بندی را اعمال کنید.
مشکل 2: بازگرداندن تعداد زیادی ستون
var data = context.BlogPosts.ToList();
راه حل: اگر تنها نیاز به خاصیت Content است، از Select و سپس ToList استفاده کنید؛ البته به همراه نکته 1.
var list = context.BlogPosts.Select(x => x.Content).Skip(15).Take(15).ToList();
مشکل 3: گزارشگیریهایی که بیشباهت به حملهی به دیتابیس نیستند
foreach (var post in context.BlogPosts) { Console.WriteLine(post.User.Name); }
این مورد به lazy loading مشهور است و در مواردی که قرار است با یک مطلب و یک نویسنده کار شود، شاید اهمیتی نداشته باشد. اما در حین نمایش لیستی از اطلاعات، بیشباهت به یک حملهی شدید به بانک اطلاعاتی نیست.
راه حل: در گزارشگیریها اگر نیاز به نمایش اطلاعات روابط یک موجودیت وجود دارد، از متد Include استفاده کنید تا Lazy loading لغو شود.
foreach (var post in context.BlogPosts.Include(x=>x.User))
مشکل 4: فعال بودن بیجهت مباحث ردیابی اطلاعات
var data = context.BlogPosts.ToList();
راه حل: در گزاشگیریها، dynamic proxies را توسط متد AsNoTracking غیرفعال کنید:
var data = context.BlogPosts.AsNoTracking().Skip(15).Take(15).ToList();
مشکل 5: باز کردن تعداد اتصالات زیاد به بانک اطلاعاتی در طول یک درخواست
هر Context دارای اتصال منحصربفرد خود به بانک اطلاعاتی است. اگر در طول یک درخواست، بیش از یک Context مورد استفاده قرار گیرد، بدیهی است به همین تعداد اتصال باز شده به بانک اطلاعاتی، خواهیم داشت. نتیجهی آن فشار بیشتر بر بانک اطلاعاتی و همچنین کاهش سرعت برنامه است؛ از این لحاظ که اتصالات TCP برقرار شده، هزینهی بالایی را به همراه دارند.
روش تشخیص:
private void problem5MoreThan1ConnectionPerRequest() { using (var context = new MyContext()) { var count = context.BlogPosts.ToList(); } }
راه حل: برای حل این مساله باید از روشهای تزریق وابستگیها استفاده کرد. یک Context وهله سازی شدهی در طول عمر یک درخواست، باید بین وهلههای مختلف اشیایی که نیاز به Context دارند، زنده نگه داشته شده و به اشتراک گذاشته شود.
مشکل 6: فرق است بین IList و IEnumerable
DataContext = from user in context.Users where user.Id>10 select user;
زمانیکه در حال تهیهی گزارشی هستید، ابزارهای گزارشگیر ممکن است چندین بار از نتیجهی کوئری شما در حین تهیهی گزارش استفاده کنند. بنابراین برخلاف تصور، data binding انجام شده، تنها یکبار سبب اجرای این کوئری نمیشود؛ بسته به ساز و کار درونی گزارشگیر، چندین بار ممکن است این کوئری فراخوانی شود.
راه حل: یک ToList را به انتهای این کوئری اضافه کنید. به این ترتیب از نتیجهی کوئری، بجای اصل کوئری استفاده خواهد شد و در این حالت تنها یکبار رفت و برگشت به بانک اطلاعاتی را شاهد خواهید بود.
مشکل 7: فرق است بین IQueryable و IEnumerable
خروجی IEnumerable، یعنی این عبارت را محاسبه کن. خروجی IQueryable یعنی این عبارت را درنظر داشته باش. اگر نیاز است نتایج کوئریها با هم ترکیب شوند، مثلا بر اساس رابط کاربری برنامه، کاربر بتواند شرطهای مختلف را با هم ترکیب کند، باید از ترکیب IQueryableها استفاده کرد تا سبب رفت و برگشت اضافی به بانک اطلاعاتی نشویم.
مشکل 8: استفاده از کوئریهای Like دار
var list = context.BlogPosts.Where(x => x.Content.Contains("test"))
مشکل 9: استفاده از Count بجای Any
اگر نیاز است بررسی کنید مجموعهای دارای مقداری است یا خیر، از Count>0 استفاده نکنید. کارآیی Any و کوئری SQL ایی که تولید میکند، به مراتب بیشتر و بهینهتر است از Count>0.
مشکل 10: سرعت insert پایین است
ردیابی تغییرات را خاموش کرده و از متد جدید AddRange استفاده کنید. همچنین افزونههایی برای Bulk insert نیز موجود هستند.
مشکل 11: شروع برنامه کند است
میتوان تمام مباحث نگاشتهای پویای کلاسهای برنامه به جداول و روابط بانک اطلاعاتی را به صورت کامپایل شده در برنامه ذخیره کرد. این مورد سبب بالا رفتن سرعت شروع برنامه خصوصا در حالتیکه تعداد جداول بالا است میشود.
استفاده از کامپوننت AutoComplete کتابخانهی Angular Material
کتابخانهی Angular Material به همراه یک کامپوننت Auto Complete نیز هست. در اینجا قصد داریم آنرا در یک صفحهی دیالوگ جدید نمایش دهیم و با انتخاب کاربری از لیست توصیههای آن و کلیک بر روی دکمهی نمایش آن کاربر، جزئیات کاربر یافت شده را نمایش دهیم.
به همین جهت ابتدا کامپوننت جدید search-auto-complete را به صورت زیر به مجموعهی کامپوننتهای تعریف شده اضافه میکنیم:
ng g c contact-manager/components/search-auto-complete --no-spec
import { SearchAutoCompleteComponent } from "./components/search-auto-complete/search-auto-complete.component"; @NgModule({ entryComponents: [ SearchAutoCompleteComponent ] }) export class ContactManagerModule { }
در ادامه برای نمایش این کامپوننت به صورت popup، دکمهی جدید جستجو را به toolbar اضافه میکنیم:
برای این منظور به فایل toolbar\toolbar.component.html مراجعه کرده و دکمهی جستجو را پیش از دکمهی نمایش منو، قرار میدهیم:
<span fxFlex="1 1 auto"></span> <button mat-button (click)="openSearchDialog()"> <mat-icon>search</mat-icon> </button> <button mat-button [matMenuTriggerFor]="menu"> <mat-icon>more_vert</mat-icon> </button>
@Component() export class ToolbarComponent { constructor( private dialog: MatDialog, private router: Router) { } openSearchDialog() { const dialogRef = this.dialog.open(SearchAutoCompleteComponent, { width: "650px" }); dialogRef.afterClosed().subscribe((result: User) => { console.log("The SearchAutoComplete dialog was closed", result); if (result) { this.router.navigate(["/contactmanager", result.id]); } }); } }
کنترلر جستجوی سمت سرور و سرویس سمت کلاینت استفاده کنندهی از آن
در اینجا کنترلر و اکشن متدی را جهت جستجوی قسمتی از نام کاربران را مشاهده میکنید:
namespace MaterialAspNetCoreBackend.WebApp.Controllers { [Route("api/[controller]")] public class TypeaheadController : Controller { private readonly IUsersService _usersService; public TypeaheadController(IUsersService usersService) { _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); } [HttpGet("[action]")] public async Task<IActionResult> SearchUsers(string term) { return Ok(await _usersService.SearchUsersAsync(term)); } } }
از این کنترلر به نحو ذیل در برنامهی Angular برای ارسال اطلاعات و انجام جستجو استفاده میشود:
import { HttpClient, HttpErrorResponse } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { Observable, throwError } from "rxjs"; import { catchError, map } from "rxjs/operators"; import { User } from "../models/user"; @Injectable({ providedIn: "root" }) export class UserService { constructor(private http: HttpClient) { } searchUsers(term: string): Observable<User[]> { return this.http .get<User[]>(`/api/Typeahead/SearchUsers?term=${encodeURIComponent(term)}`) .pipe( map(response => response || []), catchError((error: HttpErrorResponse) => throwError(error)) ); } }
تکمیل کامپوننت جستجوی کاربران توسط یک AutoComplete
پس از این مقدمات که شامل تکمیل سرویسهای سمت سرور و کلاینت دریافت اطلاعات کاربران جستجو شده و نمایش صفحهی جستجو به صورت یک popup است، اکنون میخواهیم محتوای این popup را تکمیل کنیم. البته در اینجا فرض بر این است که مطلب «کنترل نرخ ورود اطلاعات در برنامههای Angular» را پیشتر مطالعه کردهاید و با جزئیات آن آشنایی دارید.
تکمیل قالب search-auto-complete.component.html
<h2 mat-dialog-title>Search</h2> <mat-dialog-content> <div fxLayout="column"> <mat-form-field class="example-full-width"> <input matInput placeholder="Choose a user" [matAutocomplete]="auto1" (input)="onSearchChange($event.target.value)"> </mat-form-field> <mat-autocomplete #auto1="matAutocomplete" [displayWith]="displayFn" (optionSelected)="onOptionSelected($event)"> <mat-option *ngIf="isLoading" class="is-loading"> <mat-spinner diameter="50"></mat-spinner> </mat-option> <ng-container *ngIf="!isLoading"> <mat-option *ngFor="let user of filteredUsers" [value]="user"> <span>{{ user.name }}</span> <small> | ID: {{user.id}}</small> </mat-option> </ng-container> </mat-autocomplete> </div> </mat-dialog-content> <mat-dialog-actions> <button mat-button color="primary" (click)="showUser()"> <mat-icon>search</mat-icon> Show User </button> <button mat-button color="primary" [mat-dialog-close]="true"> <mat-icon>cancel</mat-icon> Close </button> </mat-dialog-actions>
سپس نحوهی اتصال یک Input box معمولی را به کامپوننت mat-autocomplete مشاهده میکنید که شامل این موارد است:
- جعبه متنی که قرار است به یک mat-autocomplete متصل شود، توسط دایرکتیو matAutocomplete به template reference variable تعریف شدهی در آن autocomplete اشاره میکند. برای مثال در اینجا این متغیر auto1 است.
- برای انتقال دکمههای فشرده شدهی در input box به کامپوننت، از رخداد input استفاده شدهاست. این روش با هر دو نوع حالت مدیریت فرمهای Angular سازگاری دارد و کدهای آن یکی است.
در کامپوننت mat-autocomplete این تنظیمات صورت گرفتهاند:
- در لیست ظاهر شدهی توسط یک autocomplete، هر نوع ظاهری را میتوان طراحی کرد. برای مثال در اینجا نام و id کاربر نمایش داده میشوند. اما برای تعیین اینکه پس از انتخاب یک آیتم از لیست، چه گزینهای در input box ظاهر شود، از خاصیت displayWith که در اینجا به متد displayFn کامپوننت متصل شدهاست، کمک گرفته خواهد شد.
- از رخداد optionSelected برای دریافت آیتم انتخاب شده، در کدهای کامپوننت استفاده میشود.
- در آخر کار نمایش لیستی از کاربران توسط mat-optionها انجام میشود. در اینجا برای اینکه بتوان تاخیر دریافت اطلاعات از سرور را توسط یک mat-spinner نمایش داد، از خاصیت isLoading تعریف شدهی در کامپوننت استفاده خواهد شد.
تکمیل کامپوننت search-auto-complete.component.ts
کدهای کامل این کامپوننت را در ادامه مشاهده میکنید:
import { Component, OnDestroy, OnInit } from "@angular/core"; import { MatAutocompleteSelectedEvent, MatDialogRef } from "@angular/material"; import { Subject, Subscription } from "rxjs"; import { debounceTime, distinctUntilChanged, finalize, switchMap, tap } from "rxjs/operators"; import { User } from "../../models/user"; import { UserService } from "../../services/user.service"; @Component({ selector: "app-search-auto-complete", templateUrl: "./search-auto-complete.component.html", styleUrls: ["./search-auto-complete.component.css"] }) export class SearchAutoCompleteComponent implements OnInit, OnDestroy { private modelChanged: Subject<string> = new Subject<string>(); private dueTime = 300; private modelChangeSubscription: Subscription; private selectedUser: User = null; filteredUsers: User[] = []; isLoading = false; constructor( private userService: UserService, private dialogRef: MatDialogRef<SearchAutoCompleteComponent>) { } ngOnInit() { this.modelChangeSubscription = this.modelChanged .pipe( debounceTime(this.dueTime), distinctUntilChanged(), tap(() => this.isLoading = true), switchMap(inputValue => this.userService.searchUsers(inputValue).pipe( finalize(() => this.isLoading = false) ) ) ) .subscribe(users => { this.filteredUsers = users; }); } ngOnDestroy() { if (this.modelChangeSubscription) { this.modelChangeSubscription.unsubscribe(); } } onSearchChange(value: string) { this.modelChanged.next(value); } displayFn(user: User) { if (user) { return user.name; } } onOptionSelected(event: MatAutocompleteSelectedEvent) { console.log("Selected user", event.option.value); this.selectedUser = event.option.value as User; } showUser() { if (this.selectedUser) { this.dialogRef.close(this.selectedUser); } } }
- توسط متد displayFn، عبارتی که در نهایت پس از انتخاب از لیست نمایش داده شده در input box قرار میگیرد، مشخص خواهد شد.
- در متد onOptionSelected، میتوان به شیء انتخاب شدهی توسط کاربر از لیست mat-autocomplete دسترسی داشت.
- این شیء انتخاب شده را در متد showUser و توسط سرویس MatDialogRef به کامپوننت toolbar که در حال گوش فرادادن به رخداد بسته شدن کامپوننت جاری است، ارسال میکنیم. به این صورت است که کامپوننت toolbar میتواند کار هدایت به جزئیات این کاربر را انجام دهد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
ویژگی ها
- امکان ایجاد progress bar و همچنین قابلیت لغو آپلود.
- Drag & Drop
- امکان paste کردن از Clipboard و قابلیت drag & drop از browser
- امکان resize کردن تصویر قبل از آپلود شدن
- امکان تغییر دادن orientation تصویر پیش از ارسال
- قابلیت resume و pause/play در آپلود
- امکان فیلتر کردن type، size و طول و عرض تصاویر
- قابلیت تبدیل به Flash shim در صورت عدم پشتیبانی html5 توسط مرورگر
نصب و پیاده سازی
bower install ng-file-upload-shim --save bower install ng-file-upload --save
<input type="file" ngf-select ng-model="picFile" name="file" accept="image/*" ngf-max-size="2MB" required>
<div ngf-drop ng-model="picFile" ngf-pattern="image/*"> ... </div>
<i ng-show="myForm.file.$error.required">*required</i><br> <i ng-show="myForm.file.$error.maxSize"> File too large: max is 2M </i>
app.controller('MyCtrl', ['$scope', 'Upload', '$timeout', function ($scope, Upload, $timeout) { // the method uploadPic called from Angular View $scope.uploadPic = function(file) { file.upload = Upload.upload({ url: 'https://angular-file-upload-cors-srv.appspot.com/upload', data: {file: file, otherData: $scope.otherData}, }); file.upload.then(function (response) { // waiting for response $timeout(function () { file.result = response.data; }); }, function (response) { //error if (response.status > 0) $scope.errorMsg = response.status + ': ' + response.data; }, function (evt) { //progress file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total)); console.log(file.progress); }); } }]);
- سماموس - مدلسازی | somamos.blogfa.com
- Figaro Embedded Native XML Database for .NET | bdbxml.net
- Mono for Android 4.0 is Here! | blog.xamarin.com
- WCF Community Site - Download: WCF Web API Preview 6 | wcf.codeplex.com
- What is Functional Programming? - Christopher Bennage | dev.bennage.com
- Microsoft Exchange Server 2010 Service Pack 2 (SP2) | microsoft.com
فرض کنید دارید در پروژه، از Entity Framework استفاده میکنید و یک مدل با نام Person دارید که تعریفش به صورت زیر است
public class Person { public int PersonId { get; set; } public string Name { get; set; } }
حالا میخوایم تعداد ٥٠٠٠ رکورد از Person رو یکجا وارد دیتابیس کنیم. برای استفاده از SqlBulkCopy، روش به این شکل هست که ابتدا یکDataTable ایجاد میکنیم. سپس ستونهای متناظر با جدول Person رو با استفاده از DataColumn ایجاد میکنیم و DataColumnهای ایجاد شده رو به DataTable اضافه میکنیم و سپس اطلاعات رو وارد DataTable میکنیم و اون رو با استفاده از SqlBulkCopy وارد دیتابیس میکنیم که این روش یکم وقتگیر و خسته کننده است.
راه آسانتر استفاده از یک کتابخانه با نام EntityDataReader هست که توسط مایکروسافت نوشته شده که دیگه نیازی به ساختنDataTable نیست و این کتابخانه کارهای لازم رو خودش انجام میده. در پروژەتون یک کلاس با نامEntityDataReader ایجاد کنید و سورس مربوط این کلاس رو از اینجا copy و در داخل کلاس paste کنید.
حالا یک لیست از Pesron با نام personList ایجاد مینماییم و با استفاده از یک حلقه تعداد ٥٠٠٠ تا نمونه از Person ایجاد و به لیست اضافه میکنیم.
var personList = new List<Person>(); for (var i = 0; i < 5000; i++) { var person = new Person { Name = "John Doe", }; }
در ادامه برای استفاده از SqlBulkCopy نیاز به ConnectionString و نام جدول متناظر با کلاس Person در دیتابیس داریم.
اگر از پروژ وب استفاده میکنید میتونید با این خط کد ConnectionString رو که در فایل web.config ذخیره شده است بروگردونید که در اینجا DataConnection نام ConnectionString ذخیره شده در web.config هست.
var connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString;
اگر از EF Code First استفاده میکنید و در تنظیمات Context خاصیت PluralizingTableNameConvention رو حذف کردیدەاید نام جدول dbo.Person هست و در غیر اینصورت db.People هست.
و در ادامه داریم:
var connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString; var bulkCopy = new SqlBulkCopy(connectionString) { DestinationTableName = "dbo.Person" }; bulkCopy.WriteToServer(personList.AsDataReader() );
سرعت این روش بسیار بالاست و برای درجهای با تعداد بالا بهینه است.
برای ویرایش و حذف چندین رکورد بصورت همزمان متیونید از کتابخانه Entity Framework Extended Library استفاده کنید که امکانات دیگری هم داره و از طریق nuget هم قابل نصب است.
قبل شروع این قسمت بد نیست با یک سری از وبلاگهای اعضای تیم Oslo آشنا شویم:
در ادامهی مثال قسمت قبل، اکنون میخواهیم entity جدیدی به نام Project را به مدل اضافه کنیم:
//mschema to define a Project type
type Project
{
ProjectID : Integer64 = AutoNumber();
ProjectName : Text#25;
ConectionStringSource : Text;
ConectionStringDestination : Text;
DateCompared: DateTime;
Comment: Text?;
ProjectOwner: ApplicationUser;
} where identity ProjectID;
//this will define a SQL foreign key relationship
ProjectCollection : Project* where item.ProjectOwner in ApplicationUserCollection;
set xact_abort on;
go
begin transaction;
go
set ansi_nulls on;
go
create schema [Test1];
go
create table [Test1].[ApplicationUserCollection]
(
[UserID] bigint not null identity,
[FirstName] nvarchar(max) null,
[LastName] nvarchar(25) not null,
[Password] nvarchar(10) not null,
constraint [PK_ApplicationUserCollection] primary key clustered ([UserID])
);
go
create table [Test1].[ProjectCollection]
(
[ProjectID] bigint not null identity,
[Comment] nvarchar(max) null,
[ConectionStringDestination] nvarchar(max) not null,
[ConectionStringSource] nvarchar(max) not null,
[DateCompared] datetime2 not null,
[ProjectName] nvarchar(25) not null,
[ProjectOwner] bigint not null,
constraint [PK_ProjectCollection] primary key clustered ([ProjectID]),
constraint [FK_ProjectCollection_ProjectOwner_Test1_ApplicationUserCollection] foreign key ([ProjectOwner]) references [Test1].[ApplicationUserCollection] ([UserID])
);
go
insert into [Test1].[ApplicationUserCollection] ([FirstName], [LastName], [Password])
values (N'user1', N'name1', N'1@34')
;
insert into [Test1].[ApplicationUserCollection] ([FirstName], [LastName], [Password])
values (N'user2', N'name2', N'123@4')
;
insert into [Test1].[ApplicationUserCollection] ([FirstName], [LastName], [Password])
values (N'user3', N'name3', N'56#2')
;
insert into [Test1].[ApplicationUserCollection] ([FirstName], [LastName], [Password])
values (N'user4', N'name4', N'789@5')
;
go
commit transaction;
Go
نکته:
جهت آشنایی با انواع دادههای مجاز در زبان M میتوان به مستندات رسمی آن مراجعه نمود:
The "Oslo" Modeling Language Specification
اکنون قصد داریم همانند مثال قسمت قبل، تعدادی رکورد آزمایشی را برای این جدول تعریف کنیم:
ProjectCollection
{
Project1{
ProjectName = "My Project 1",
ConectionStringSource = "Data Source=.;Initial Catalog=MyDB1;Integrated Security=True;",
ConectionStringDestination = "Data Source=.;Initial Catalog=MyDB2;Integrated Security=True;",
Comment="Project Comment",
DateCompared=2009-01-01T00:00:00,
ProjectOwner=ApplicationUserCollection.User1 //direct ref to User1 (FK)
},
Project2{
ProjectName = "My Project 2",
ConectionStringSource = "Data Source=.;Initial Catalog=MyDB1;Integrated Security=True;",
ConectionStringDestination = "Data Source=.;Initial Catalog=MyDB2;Integrated Security=True;",
Comment="Project Comment",
DateCompared=2009-01-01T00:00:00,
ProjectOwner=ApplicationUserCollection.User2 //direct ref to User2 (FK)
}
}
ادامه دارد ...
شاخهها ( نودها ) میتوانند فونتهای مختلف داشته باشند.برای تنظیم فونت باید از تابع ()setFont استفاده شود.البته که باید فونت انتخابی بر روی سیستم کاربر موجود باشد در غیر این صورت مرورگر یک فونت دلخواه و پیش فرض خود را جایگزین فونت شما خواهد نمود. در صورت بروز هر گونه خطا در فونت ، متن داخل گرهها کوتاه خواهد شد.
با توجه به محدودیت IE در پیاده سازی excanvas ، در کل کاراکترها متن نود کوتاه میشود. ( اگر کاراکترهای نود ، کاملا پرشونده fit نشوند ، بخشی از کل متن کاراکترهای نود نوشته یا رسم خواهد شد )
پارامترهای تابع ()setFont :
-
نام فونت . حالت فونت ضخیم bold یا مورب italic قابل استفاده است.
- اندازه فونت در واحد پیکسل
- رنگ فونت ( اختیاری )
- چیدمان عمودی ( 1 و c یا center برای وسط چین . ( اختیاری )
var o = new orgChart(); o.setColor('#99CC99', '#CCFFCC'); o.setFont('arial', 18); o.addNode(0, '', '', 'Arial 18', 1); o.setColor('#CCCC66', '#FFFF99'); o.setFont('arial', 10, '#000000'); o.addNode(11, 0, 'u', 'text will be split if it does not fit. ThisIsAVeryLongWordAndItWillBeClipped. Too many lines will be clipped too.'); o.setFont('arial', 10, '#000000', 0); o.addNode(12, 0, 'u', 'aligned at top'); o.setFont('arial', 10, '#000000', 1); o.addNode(13, 0, 'u', 'centered'); o.setColor('#CC4950', '#FF7C80'); o.setFont('times', 16, '#FF0F00'); o.addNode(21, 0, 'l', 'Times 16 red'); o.setFont('times', 16, '#000000'); o.addNode(22, 0, 'l', 'Times 16'); o.setFont('times', 48, '#000000'); o.addNode(23, 0, 'l', 'Times 48 does not fit at all'); o.setColor('#CC9966', '#FFCC99'); o.setFont('jokerman', 12, '#000000'); o.addNode(31, 0, 'r', 'Jokerman (if available)'); o.setFont('bold times', 16, '#000000'); o.addNode(32, 0, 'r', 'bold Times 16'); o.setFont('italic times', 16, '#000000'); o.addNode(33, 0, 'r', 'italic Times 16'); o.setFont('bold italic times', 16, '#000000'); o.addNode(34, 0, 'r', 'bold italic Times 16'); o.setColor('#B5D9EA', '#CFE8EF'); o.setFont('arial', 28, '#000000'); o.addNode(50, '', '', 'Arial 28'); o.setFont('arial', 29); o.addNode(51, 50, 'u', 'Arial 29'); o.setFont('arial', 30); o.addNode(52, 51, 'u', 'Arial 30'); o.setFont('arial', 31); o.addNode(53, 52, 'u', 'Arial 31'); o.setFont('arial', 32); o.addNode(54, 53, 'u', 'Arial 32'); o.drawChart('c_fonts');
اندازه و مکان :
شما میتوانید اندازه نودها و فضا و offset بین نودها را نیز تنظیم نمائید.این تنظیم بصورت عمومی تاثیر گذار است و تمامی نودها از این تنظیم تبعیت خواهند نمود:
پارامترهای تابع ()setSize:
- عرض نودها در واحد پیکسل.
- ارتفاع نودها در واحد پیکسل.
- فاصله عرضی بین نودهای پدر u-nodes. ( اختیاری ).
- فاصله طولی بین نودها ( اختیاری ).
- offset عرضی ( فاصله ) از نود چپ و نود راست ( اختیاری ).
var o = new orgChart(); o.setSize(36, 12, 4, 25, 180); o.setColor('#99CC99', '#CCFFCC'); o.setFont('arial', 18); o.addNode(0, '', '', 'Root node'); o.setFont('arial', 12); o.setColor('#CCCC66', '#FFFF99'); o.addNode(11, 0, 'u', 'u-node 1'); o.addNode(12, 0, 'u', 'u-node 2'); o.addNode(13, 0, 'u', 'u-node 3'); o.setColor('#CC4950', '#FF7C80'); o.addNode(21, 0, 'l', 'l-node 1'); o.addNode(22, 0, 'l', 'l-node 2'); o.addNode(23, 0, 'l', 'l-node 3'); o.setColor('#CC9966', '#FFCC99'); o.addNode(31, 0, 'r', 'r-node 1'); o.drawChart('c_size');
شما میتوانید به نودها در پارامتر ششم تابع ()addNode آدرس پیوند خود را اضافه نمائید.
در صورت ایجاد پیوند کامل ( مانند : http://www.yourdomain.com ) پیوند در برگه ( tab ) یا یک پنجره جدید ( بسته به تنظیمات مرورگر سمت کاربر ) باز خواهد شد.
اگر نشانگر ماوس ، روی این نوع از نودها قرار بگیرد تغییر شکل به مانند دست ( اشاره گر ) میدهد.
نکته : در این نمونه کد ، هر نود در یک چارت سازمانی جدید دوباره رسم شده اند.در چارت سازمانی قدیمی ، نودها از بین نمیروند و همه مسیرهای باقی مانده فعال خواهند ماند.بنابراین اگر reDraw در این نمونه استفاده شود ، چند پیوند در یک نود باز خواهد شد .
اگر بخواهید فقط یک لینک به نودی اختصاص دهید ، یک نود پیوندی بدون پیوند به آن اضافه کنید ( مانند نودها سبز مثال نمونه ).
var o = new orgChart(); o.setColor('#99CC99', '#CCFFCC'); o.setFont('arial', 18); o.addNode( 0, '', '', 'Searching', 1); o.addNode(50, '', '', 'Social', 1); o.addNode(90, '', '', 'Misc.', 1); o.setColor('#CCCC66', '#FFFF99'); o.setFont('arial', 12); o.addNode(11, 50, 'u', 'Facebook', 0, 'http://facebook.com'); o.addNode(13, 90, 'u', 'Youtube', 0, 'http://youtube.com'); o.addNode(14, 13, 'l', 'Youtube Music', 0, 'http://youtube.com/music'); o.addNode(15, 13, 'l', 'Youtube Entertainment', 0, 'http://youtube.com/entertainment'); o.setColor('#CC4950', '#FF7C80'); o.addNode(21, 0, 'l', 'Google', 0, 'http://google.com'); o.addNode(22, 0, 'l', 'Bing', 0, 'http://bing.com'); o.addNode('r2', '', '', 'Top of this Page', 0, '#'); o.addNode('', 'r2', 'u', 'Back to the introduction', 0, '/orgchart'); o.drawChart('c_links');
در قسمت چهارم و آخر این مطلب ، نمونههای بیشتری از ایجاد چارت سازمانی تحت وب ، درج تصویر در نودها و نمایش نمودار بعنوان یک تصویر ارائه خواهد شد.