مطالب
دریافت خطاهای موجود در Viewهای ASP.NET MVC در زمان کامپایل
روشهای زیادی برای انجام این کار وجود دارد:

1- در فایل پروژه (Your-MVC-Project.csproj) مقدار تگ MvcBuildViews را به true تغییر دهید. 

2- استفاده از  RazorGenerator

3- اگر Resharper نصب شده است، روشن کردن Solution-wide analysis (گوشه‌ی پایین سمت راست ویژوال استودیو) همه‌ی خطاهای پروژه از جمله خطاهای موجود در ویوها را نمایش خواهد داد.

4- و روشهای دیگر ...


اشکال روش اول، در طولانی شدن زمان کامپایل است و در روش دوم باید از یک کتابخانه جانبی برای این کار استفاده کنیم (اگر صرفا بخواهیم فقط از این قابلیت استفاده کنیم) و روش سوم را هم خودتان می‌توانید حدس بزنید! (مصرف بیش از حد منابع سیستم)

یک راه ساده‌تر این است که از روش اول فقط در زمانیکه پروژه در حالت Release قرار دارد، استفاده کنیم. در این صورت ویوها در حالت Debug کامپایل نمی‌شوند و برای کامپایل باید به حالت Release سوئیچ کنیم.
برای این کار مراحل زیر را انجام دهید:
1- کلیک راست بر روی پروژه و انتخاب Unload Project.
2- کلیک راست بر روی پروژه و انتخاب Edit Your-MVC-Project.csproj.
3- پیدا کردن تگ <MvcBuildViews>...</MvcBuildViews> و حذف آن.
4- افزودن کدهای زیر به فایل:
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
    <MvcBuildViews>true</MvcBuildViews>
  </PropertyGroup>

به این صورت:

 

5- ذخیره‌ی فایل، راست کلیک بر روی پروژه و انتخاب Reload Project.

اکنون پروژه‌ی خود را در حالت Release قرار داده و Build کنید!
مطالب
آپلود فایل‌ها در یک برنامه‌ی Angular به کمک کامپوننت ng2-file-upload
در مطلب «بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core» روش عمومی آپلود فایل‌ها را بررسی کردیم. آن مطلب وابستگی به کامپوننت خاصی ندارد و عمومی است. در مطلب جاری می‌خواهیم روش دیگری را مبتنی بر کامپوننت ng2-file-upload بررسی کنیم که به همراه نمایش درصد پیشرفت ارسال فایل‌ها، امکان انتخاب بهتر نوع فایل‌های آپلودی و همچنین امکان مشاهده‌ی لیست کامل فایل‌های انتخاب شده و امکان حذف مواردی از آن، پیش از ارسال نهایی است.



پیشنیازهای کار با کامپوننت ng2-file-upload

برای شروع به کار با کامپوننت ng2-file-upload، ابتدا نیاز است بسته‌ی npm آن‌را نصب کرد:
 >npm install ng2-file-upload --save

همچنین یک کامپوننت آزمایشی را هم به برنامه (دقیقا همان مثال مطلب قبلی) جهت اعمال آن اضافه می‌کنیم:
 >ng g c UploadFile/ng2-file-upload-test

پس از آن نیاز است به ماژولی که این کامپوننت جدید در آن قرار دارد، مدخل FileUploadModule کامپوننت ng2-file-upload را افزود:
import { FileUploadModule } from "ng2-file-upload";

@NgModule({
  imports: [
    FileUploadModule
  ]
در غیراینصورت خطای شناخته نشدن خاصیت uploader را در حین اعمال این کامپوننت مشاهده خواهید کرد.


تکمیل Ng2FileUploadTestComponent جهت اعمال ng2-file-upload

اکنون به کلاس کامپوننت جدیدی که ایجاد کردیم، مراجعه کرده و تغییرات ذیل را اعمال می‌کنیم:
import { FileUploader, FileUploaderOptions } from "ng2-file-upload";
import { Ticket } from "./../ticket";

export class Ng2FileUploadTestComponent implements OnInit {
  fileUploader: FileUploader;
  model = new Ticket();
در اینجا یک خاصیت عمومی از نوع FileUploader تعریف شده‌است که در اختیار قالب این کامپوننت قرار خواهد گرفت. همچنین شیء مدل فرم نیز همانند مطلب «بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core» تهیه شده‌است. هدف این است که بررسی کنیم علاوه بر ارسال فایل‌ها، چگونه می‌توان اطلاعات یک فرم را نیز به سمت سرور ارسال کرد.


وهله سازی از کامپوننت ng2-file-upload و انجام تنظیمات اولیه‌ی آن

پس از تعریف خاصیت عمومی fileUploader، اکنون نوبت به وهله سازی آن است:
    this.fileUploader = new FileUploader(
      <FileUploaderOptions>{
        url: "api/SimpleUpload/SaveTicket",
        headers: [
          { name: "X-XSRF-TOKEN", value: this.getCookie("XSRF-TOKEN") },
          { name: "Accept", value: "application/json" }
        ],
        isHTML5: true,
        // allowedMimeType: ["image/jpeg", "image/png", "application/pdf", "application/msword", "application/zip"]
        allowedFileType: [
          "application",
          "image",
          "video",
          "audio",
          "pdf",
          "compress",
          "doc",
          "xls",
          "ppt"
        ],
        removeAfterUpload: true,
        autoUpload: false,
        maxFileSize: 10 * 1024 * 1024
      }
    );
- در اینجا url، مسیر اکشن متدی را در سمت سرور مشخص می‌کند که قرار است فایل‌های ارسالی را دریافت و ذخیره کند.
- اگر برنامه از نکات anti-forgery token استفاده می‌کند، این کامپوننت برخلاف روش مطرح شده‌ی در مطلب مشابه قبلی، هیچ هدری را به سمت سرور ارسال نمی‌کند. بنابراین نیاز است کوکی مرتبط را خودمان یافته و سپس به لیست هدرها اضافه کنیم. در اینجا روش استخراج یک کوکی را توسط کدهای جاوا اسکریپتی مشاهده می‌کنید:
  getCookie(name: string): string {
    const value = "; " + document.cookie;
    const parts = value.split("; " + name + "=");
    if (parts.length === 2) {
      return decodeURIComponent(parts.pop().split(";").shift());
    }
  }

- برای محدود سازی فایل‌های ارسالی توسط این کامپوننت، دو روش وجود دارد:
الف) مشخص سازی مقدار خاصیت allowedMimeType
همانطور که مشاهده می‌کنید، در اینجا باید mime type فایل‌های مجاز را مشخص کرد.
ب) مشخص سازی مقدار خاصیت allowedFileType
برخلاف تصور، در اینجا از پسوند فایل‌ها استفاده نمی‌کند و از یک لیست از پیش مشخص که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید، کمک گرفته می‌شود. بنابراین اگر برای مثال تنها نیاز به ارسال تصاویر بود، مقدار image را نگه داشته و مابقی را از لیست حذف کنید.

- removeAfterUpload به این معنا است که آیا لیست نهایی که نمایش داده می‌شود، پس از آپلود باقی بماند یا خیر؟
- توسط خاصیت maxFileSize می‌توان حداکثر اندازه‌ی قابل قبول فایل‌های ارسالی را مشخص کرد.


مدیریت رخ‌دادهای کامپوننت ng2-file-upload

اکنون که وهله‌ای از این کامپوننت ساخته شده‌است، می‌توان رخ‌دادهای آن‌را نیز مدیریت کرد. برای مثال:
الف) نحوه‌ی ارسال اطلاعات اضافی به همراه یک فایل به سمت سرور
    this.fileUploader.onBuildItemForm = (fileItem, form) => {
      for (const key in this.model) {
        if (this.model.hasOwnProperty(key)) {
          form.append(key, this.model[key]);
        }
      }
    };
در اینجا شبیه به مطلب مشابه قبلی، مقادیر خواص شیء مدل، به صورت خودکار استخراج شده و به خاصیت form این کامپوننت که درحقیقت همان FormData ارسالی به سمت سرور است، اضافه می‌شوند.

ب) اطلاع یافتن از رخ‌داد خاتمه‌ی کار
رخ‌داد onCompleteAll پس از ارسال تمام فایل‌ها به سمت سرور فراخوانی می‌شود:
    this.fileUploader.onCompleteAll = () => {
      // clear the form
      // this.model = new Ticket();
    };

ج) در حین وهله سازی fileUploader، تعدادی محدودیت نیز قابل اعمال هستند. این محدودیت‌ها سبب نمایش هیچگونه پیام خطایی نمی‌شوند. فقط زمانیکه کاربر فایلی را انتخاب می‌کند، این فایل در لیست ظاهر نمی‌شود. اگر علاقمند به مدیریت این وضعیت باشید، می‌توان از رخ‌داد onWhenAddingFileFailed استفاده کرد:
    this.fileUploader.onWhenAddingFileFailed = (item, filter, options) => {
      // msg: `You can't select ${item.name} file because of the ${filter.name} filter.`
    };

د) اگر ارسال فایلی به سمت سرور با شکست مواجه شود، در ر‌خ‌دادگردان onErrorItem می‌توان به نام این فایل و اطلاعات بیشتری که از سمت سرور دریافت شده‌است، دسترسی یافت:
    this.fileUploader.onErrorItem = (fileItem, response, status, headers) => {
       //
    };

ه) اگر از سمت سرور اطلاعات JSON مانندی یا هر اطلاعات دیگری به سمت کلاینت پس از آپلود ارسال می‌شود، این اطلاعات را می‌توان در رخ‌دادگردان onSuccessItem دریافت کرد:
    this.fileUploader.onSuccessItem = (item, response, status, headers) => {
      if (response) {
        const ticket = JSON.parse(response);
        console.log(`ticket:`, ticket);
      }
    };


ارسال نهایی فرم و  فایل‌ها به سمت سرور

در پایان، با فراخوانی متد uploadAll شیء fileUploader جاری، می‌توان اطلاعات فرم و تمام فایل‌های آن‌را به سمت سرور ارسال کرد:
  submitForm(form: NgForm) {
    this.fileUploader.uploadAll();

    // NOTE: Upload multiple files in one request -> https://github.com/valor-software/ng2-file-upload/issues/671
  }
فقط باید دقت داشت که این کامپوننت هر فایل را جداگانه به سمت سرور ارسال می‌کند و برخلاف روش مطلب قبلی، همه را یکجا و در طی یک درخواست به سمت سرور ارسال نمی‌کند. اما کدهای سمت سرور آن با مطلب مشابه قبلی دقیقا یکی است و تفاوتی نمی‌کند (همان نکات قسمت «دریافت فرم درخواست پشتیبانی در سمت سرور و ذخیره‌ی فایل‌های آن‌» مطلب قبلی نیز در اینجا صادق است).


کدهای کامل کامپوننت ng2-file-upload-test.component.ts را در اینجا می‌توانید مشاهده کنید.


تکمیل قالب کامپوننت Ng2FileUploadTestComponent

اکنون که کار تکمیل کامپوننت آزمایشی ارسال فایل‌ها به سمت سرور به پایان رسید، نوبت به تکمیل قالب آن است.

افزودن فیلد اضافی توضیحات به فرم

<div class="container">
  <h3>Support Form(ng2-file-upload)</h3>
  <form #form="ngForm" (submit)="submitForm(form)" novalidate>
    <div class="form-group" [class.has-error]="description.invalid && description.touched">
      <label class="control-label">Description</label>
      <input #description="ngModel" required type="text" class="form-control"
        name="description" [(ngModel)]="model.description">
      <div *ngIf="description.invalid && description.touched">
        <div class="alert alert-danger"  *ngIf="description.errors.required">
          description is required.
        </div>
      </div>
    </div>
هدف از این فیلد این است که شیء Ticket را وهله سازی و مقدار دهی کند. از مقدار آن در رخ‌دادگردان onBuildItemForm که پیشتر توضیح داده شد، استفاده می‌شود.

تعریف ویژه‌ی فیلد ارسال فایل‌ها به سمت سرور

    <div class="form-group">
      <label class="control-label">Screenshot(s)</label>
      <input required type="file" multiple ng2FileSelect [uploader]="fileUploader"
        class="form-control" name="screenshot">
    </div>
در اینجا ابتدا دایرکتیو ng2FileSelect ذکر می‌شود تا کامپوننت مرتبط فعالسازی شود. سپس خاصیت uploader این دایرکتیو توسط خاصیت fileUploader که پیشتر در کامپوننت، وهله سازی و تنظیم شد، مقدار دهی می‌شود.
ذکر ویژگی استاندارد multiple را نیز در اینجا مشاهده می‌کنید. وجود آن سبب خواهد شد تا کاربر بتواند چندین فایل را با هم انتخاب کند. اگر نیازی به ارسال چندین فایل نیست، این ویژگی را حذف کنید.

نمایش لیست فایل‌ها و نمایش درصد پیشرفت آپلود آن‌ها

جدولی را که در تصویر ابتدای بحث مشاهده کردید، به صورت ذیل شکل می‌گیرد (کدهای آن در همان صفحه‌ی توضیحات کامپوننت نیز موجود هستند):
    <div style="margin-bottom: 10px" *ngIf="fileUploader.queue.length">
      <h3>Upload queue</h3>
      <p>Queue length: {{ fileUploader?.queue?.length }}</p>
      <table class="table">
        <thead>
          <tr>
            <th width="50%">Name</th>
            <th>Size</th>
            <th>Progress</th>
            <th>Status</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let item of fileUploader.queue">
            <td><strong>{{ item?.file?.name }}</strong></td>
            <td nowrap>{{ item?.file?.size/1024/1024 | number:'.2' }} MB</td>
            <td>
              <div class="progress" style="margin-bottom: 0;">
                <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div>
              </div>
            </td>
            <td class="text-center">
              <span *ngIf="item.isError"><i class="glyphicon glyphicon-remove"></i></span>
            </td>
            <td nowrap>
              <button type="button" class="btn btn-danger btn-xs" (click)="item.remove()">
                <span class="glyphicon glyphicon-trash"></span> Remove
              </button>
            </td>
          </tr>
        </tbody>
      </table>

      <div>
        <div>
          Queue progress:
          <div class="progress">
            <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': fileUploader.progress + '%' }"></div>
          </div>
        </div>
        <button type="button" class="btn btn-danger btn-s" (click)="fileUploader.clearQueue()"
          [disabled]="!fileUploader.queue.length">
          <span class="glyphicon glyphicon-trash"></span> Remove all
        </button>
      </div>
    </div>
شیء fileUploader وهله سازی شده‌ی در کامپوننت این قالب، دارای خاصیت queue است. در این خاصیت، لیست فایل‌های انتخابی توسط کاربر درج می‌شوند. برای مثال مقدار fileUploader?.queue?.length مساوی تعداد فایل‌های انتخابی توسط کاربر است. بنابراین می‌توان حلقه‌ای را بر روی آن تشکیل داد و مشخصات این فایل‌ها را در صفحه نمایش داد. همچنین هر آیتم آن دارای متد remove نیز هست. کار این متد، حذف این آیتم از لیست queue است و یا اگر متد fileUploader.clearQueue فراخوانی شود، تمام آیتم‌های این لیست را حذف می‌کند.
در اینجا از progress-bar بوت استرپ برای نمایش درصد آپلود فایل‌ها استفاده شده‌است:
              <div class="progress" style="margin-bottom: 0;">
                <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div>
              </div>
این کامپوننت ارسال فایل، خاصیت item.progress هر فایل موجود در queue را مدام به روز رسانی می‌کند. به همین جهت می‌توان از آن جهت تغییر عرض پیشرفت progress-bar بوت استرپ استفاده کرد.

غیرفعال کردن دکمه‌ی ارسال، در صورت عدم انتخاب یک فایل

    <button class="btn btn-primary" [disabled]="form.invalid || !fileUploader.queue.length"
      type="submit">Submit</button>
  </form>
اگر بخواهیم انتخاب حداقل یک فایل را توسط کاربر اجباری کنیم، می‌توان خاصیت disabled دکمه‌ی ارسال را به طول صف یا fileUploader.queue.length نیز متصل کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
اضافه شدن JavaScript initializers   به Blazor 6x

در اینجا می‌توان فایل ویژه‌ای به نام NAME.lib.module.js را به پوشه‌ی wwwroot پروژه اضافه کرد که name آن، همان نام اسمبلی، کتابخانه و در اصل package identifier پروژه‌است؛ با این محتوا:
export function beforeStart(options, extensions) {
    console.log("beforeStart");
}

export function afterStarted(blazor) {
    console.log("afterStarted");
}
قالب این محتوا باید به همین نحو باشد و معرف اجرای کدهایی پیش از و پس از load برنامه است. به این ترتیب می‌توان به این مزایا دست یافت:
- سفارشی سازی نحوه‌ی بارگذاری یک برنامه‌ی Blazor
- اجرای کدهای سفارشی، پیش و پس از بارگذاری برنامه
- امکان تنظیم ویژگی‌های Blazor

یک مثال: بارگذاری یک اسکریپت پس از کامل شدن بارگذاری Blazor
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>
نظرات مطالب
Angular CLI - قسمت پنجم - ساخت و توزیع برنامه
با سلام؛
ما یک پروژه با Angular 5 و KendoUI برای یکی از سازمان‌های کشور راه اندازی کرده‌ایم. در این سیستم از AngularCLI استفاده نشده است و همه کارها سمت کلاینت و سرور بروی VisualStudio2017 توسعه داده شده است. الان این سیستم بروی IIS پابلیش شده و در حال استفاده با حدود 200 کاربر آنلاین می‌باشد. مشکلی که برخورد کرده ایم اینه که حجم سیستم برای سمت کاربر حدود 9 مگابایت شده و تعداد درخواست‌ها به سمت سرور برای دانلود فایل‌های کتابخانه و .ts‌ها بسیار زیاد و حدود 500 درخواست شده است مطابق شکل زیر.
این مساله باعث کندی سیستم و عدم پاسخگویی مناسب برای کاربران شده است به نحوی که فقط برای بالا آمدن لاگین اولیه سیستم چند بار باید مرورگر Refresh شود. سوال بنده این هست که آیا می‌توان با استفاده از WebPack  یا gulp این تعداد فایل‌ها (َangular, kendo,...) را کم یا فشرده سازی کرد ؟ و اینکه آیا تعداد بالای این درخواست که البته در سمت مرورگر Cache می‌شود، می‌تواند باعث کند شدن سیستم گردد؟
نظرات مطالب
React 16x - قسمت 16 - مسیریابی - بخش 2 - پارامترهای مسیریابی
به روزرسانی : در نسخه 6 react-router-dom  موارد زیادی دست خوش تغییر شده اند که می‌توان به زیر اشاره کرد:
از این پس به جای Switch از Routes استفاده کنید و استفاده از این تگ اجباری بوده و در صورت اینکه تگهای Route داخل تگهای Routes قرار داده نشوند خطای آن در کنسول مشاهده میگردد.
<Routes>
<Route path="/product/list" element={<ProductList/>}  />
<Route path="/product/new" element={<NewProduct/>} />
</Routes>
همچنین با جای خصوصیت component خصوصیت element را مورد استفاده قرار دهید و تگ کامپوننت را داخل آن بگذارید. در این حالت مسیریابی نیاز به رعایت ترتیب خاصی نیست و دقیقا باید همان مسیر path وارد شود تا کامپوننت در دسترس قرار گیرد.
برای مسیریابی‌های تو در تو یا Nesting که نسبت به قبل خیلی مفاهیم ساده‌تری دارد. روش پیاده سازی مسیریابی تو در تو به صورت تگ‌های تو در تو آمده و از لحاظ طراحی بسیار به مفهوم آن نزدیک‌تر میباشد و اینکه سعی شده است تمام مدخل‌های Route در یک جا قرار بگیرند:
<Routes>

<Route path="/post" element={<Posts/>}>
  <Route path="images" element={<Images />} />
    <Route path="text" element={<Text />} />
    <Route path="/post" element={<Text />} />
</Route>
</Routes>
و برای لینک دادن :
            <div className="list-group">
  <Link to="/post/images" className="list-group-item list-group-item-action active" aria-current="true">
    Images Post
  </Link>
  <Link to="/post/text" className="list-group-item list-group-item-action">Text Post</Link>
</div>
<Outlet />

در این حالت کاربر به مسیر post هدایت شده و با کلیک بر روی گزینه‌های images و text میتواند پست متنی و تصویری را جدا در زیر گزینه‌ها به جای تگ outlet مشاهده نماید
البته مسیرهای زیرین را نیز می‌توان به شکل زیر هم نوشت:
            <div className="list-group">
  <Link to="images" className="list-group-item list-group-item-action active" aria-current="true">
    Images Post
  </Link>
  <Link to="text" className="list-group-item list-group-item-action">Text Post</Link>
</div>
<Outlet />
در این حالت نیاز است تنها مسیر آخر که قرار است به انتهای آدرس اضافه شود نوشته شود. در صورتی که قصد داشته باشیم بخش پست‌های متنی به طور پیش فرض با آدرس post هم باز شود و الزامی حتما به آدرس post/text نباشد میتوان اینگونه تغییر داد:
<Routes>
<Route path="/post" element={<Posts/>}>
  <Route path="images" element={<Images />} />
    <Route path="text" element={<Text />} />
    <Route path="/post" element={<Text />} />
</Route>
</Routes>
در قسمت مسیریابی همان نام post/ نوشته می‌شود.
در انتها برای صفحاتی مانند NotFound میتوان از علامت * و اشاره به آن کامپوننت استفاده کرد:
<Routes>
<Route path="/product/list" element={<ProductList/>}  />
<Route path="/product/new" element={<NewProduct/>} />
<Route path="/post" element={<Posts/>}>
  <Route path="images" element={<Images />} />
    <Route path="text" element={<Text />} />
    <Route path="/post" element={<Text />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>

مطالب
آموزش مفاهیم Data Warehouse

مفاهیم مقدماتی Data Warehouse :

OLTP   ( Online Transaction Processing ) : سیستم‌هایی می‌باشند که برای اهداف اصلی سازمان استفاده می‌شوند و این سیستم‌ها کار پردازش و ذخیره کردن داد‌ه‌ها را در OLTP Database انجام می‌دهند. مانند تمامی سیستم‌های ERP,MIS,…

OLTP Database  : پایگاه داده‌ی سیستم‌های OLTP می‌باشد. به طور معمول هر تراکنش کاربر در کمترین زمان ممکن برروی این سیستم‌ها ذخیره می‌گردد و در طول روز بار‌ها دستورات ( Insert/Update/Delete ) برروی آنها انجام می‌شود. این پایگاه‌های داده، همان Main Data ‌ها یا Source System ‌ها می‌باشند.

ETL  ( extract, transform, and load ) : مراحل انتقال داده از OLTP Database به پایگاه داده‌ی Stage می‌باشد. ETL سیستمی می‌باشد که توانایی اتصال به OLTP را دارد و اطلاعات را از OLTP واکشی می‌کند و به پایگاه داده‌ی Stage انتقال می‌دهد. سپس ETL داده‌ها را مجتمع ( integrates ) کرده و از Stage به DDS ( Dimensional Data Source ) انتقال می‌دهد .

Retrieves Data : عملیات واکشی داده‌ها طبق یک سری قوانین و قواعد می‌باشد .

برای انجام عملیات ETL دو روش وجود دارد

1. Data مجتمع ( Integrate ) و تمیز ( Data cleansing ) شود و در نهایت وارد Data Warehouse گردد.

2. Data وارد Data Warehouse گردد سپس مراحل مجتمع سازی و پاک سازی داده‌ها بر روی داده‌ها در خود Data Warehouse انجام گردد.

Consolidates Data : برخی شرکت‌ها داده‌های اصلی خودشان را در چندین پایگاه داده دارند. در این حالت برای انجام عملیات ETL باید داده‌ها تحکیم و مجتمع شوند و سپس در Data Warehouse  ذخیره شوند.

به طور کلی موارد زیر در فرایند   ETL در نظر گرفته می‌شود:

1. Data availability : برخی داده‌ها در یک سیستم وجود دارند ولی در سیستم دیگری وجود ندارند و یا تفاوت در نگهداری داده‌ها در سیستم‌های مختلف داریم. مثلا در یک سیستم آدرس در سه فیلد نگه داری می‌شود (کشور-شهر-آدرس) اما در سیستمی دیگر در دو فیلد(کشور-آدرس) نگه داری می‌شود. در این حالت باید ما در ETL راه کار هایی برای مجتمع کردن این موارد در نظر بگیریم.

2. Time ranges : در سیستم‌های مختلف امکان دارد بعد‌های زمانی مختلف باشد . مثلا در یک سیستم بررسی‌ها در بازه‌ی ساعتی و در سیستم دیگر بررسی‌ها در بازه‌ی روزانه یا ماهانه باشد . بنابر این در تجمیع داده‌ها باید این مورد مد نظر گرفته شود.

3. Definitions  : تعاریف در سیستم‌های مختلف می‌تواند متفاوت باشد. مثلا در یک سیستم، مبلغ کل فاکتور شامل مالیات می‌باشد ولی در سیستمی دیگر این مبلغ فاقد مالیات می‌باشد.

4. Conversion  : در فرآیند ETL باید باز از قواعد موجود در سیستم‌های مختلف آگاهی داشته باشیم. مثلا در یک سیستم ممکن است دما را به صورت سانتیگراد و در دیگری فارنهایت نگه داری کنند.

5. Matching : باید بررسی لازم را انجام دهیم که کدام داده مرتبط با کدام سیستم می‌باشد. به عبارت دیگر کدام سیستم مالک داده می‌باشد و دقیقا  داده‌ها در کدام سیستم معتبر‌تر می‌باشند. مثلا پرسنل، هم در سیستم حسابداری می‌باشند هم در سیستم پرسنلی؛ ولی معمولا داده‌های اصلی از سیستم پرسنلی می‌آیند.

Periodically : عملیات واکشی داده‌ها ( Retrieves Data ) و مجتمع سازی داده‌ها ( Consolidates Data ) در فرآیند   ETL فقط یکبار اتفاق نمی‌افتد و این مراحل در بازه‌های زمانی خاص تکرار می‌گردند. این واکشی و انتقال داده‌ها می‌تواند در روز چند بار تکرار شود یا می‌تواند چند روز یک بار اجرا گردد و این بستگی دارد به سیاست موجود در Data Warehouse .

DDS (Dimensional Data Source) (Data Warehouse) : یک پایگاه داده از نوع نرمال شده ( Normalized ) یا بعدی ( Dimensional ) می‌باشد. که داده‌های مجتمع شده و تمیز شده سیستم‌های OLTP را در خود جای داده است. این پایگاه داده برای واکشی‌های سیستم‌های آنالیز داده مورد استفاده قرار می‌گیرد. ورود اطلاعات در Data Warehouse به صورت Batch می‌باشد و به هیچ عنوان مانند پایگاه داده‌های OLTP ویرایش داده‌ها به صورت Online و هر زمان که داده‌ها تغییر می‌کنند، صورت نمی‌گیرد. اطلاعات در Data Warehouse معمولا به صورت تجمیع شده روزانه، ماهانه، فصلی یا سالانه می‌باشد. DDS ‌ها مجموعه ای از Dimensional Data Mart ‌ها هستند. و عمدتا به صورت denormalized می‌باشند.

Dimensional Data Mart : مجموعه ای از جداول Fact , Dimension می‌باشند که در یک بیزینس خاص باهم در ارتباط و مشترک می‌باشند.

dimensional data store schemas : طراحی‌های مختلفی از جداول Fact , Dimension در DDS وجود دارد که عبارتند از

1. Star schema : ساده‌ترین روش پیاده سازی Data Warehouse

2. Snowflake : در این روش جداول Dimension کمی نرمال سازی بیشتری دارند. سیستم‌های آنالیز داده با این روش بهتر کار می‌کنند.

3. Galaxy schemas : طراحی در این روش بسیار سخت و پیچیده می‌باشد. با این وجود فرایند ETL در این طراحی ساده‌تر انجام می‌شود.

نمونه‌ی طراحی Star به صورت زیر می‌باشد :

تفاوت‌های DDS و NDS :

1. در DDS ‌ها هیچ گونه نرمال سازی خاصی انجام نمی‌دهیم و عملا تمامی جداول را دینرمال کرده ایم، در حالی که در NDS تمامی جداول تا سطح سوم و گاهی تا سطح پنجم نرمال شده اند.

2. سرعت واکشی و پردازش کوئری‌ها روی DDS خیلی بیشتر از NDS ‌ها می‌باشد.

3. در صورتی که نیاز باشد Data Warehouse ‌های خیلی بزرگ طراحی کنیم با حجم بسیار زیاد توصیه می‌شود از NDS ‌ها استفاده شود در حالی که برای Data Warehouse ‌های کوچک و متوسط بهتر است از DDS ‌ها استفاده شود.

تصویر طراحی یک  (Enterprise Data Source = NDS) EDS در زیر آمده است :

History : جداول Data Warehouse میتوانند در طول زمان بسیار بزرگ شوند و دارای تعداد رکورد زیادی گردند. اینکه حداکثر داده‌های چند سال را در Data Warehouse نگه داری کنیم بستگی به سیاست‌های سازمانی دارد که سیستم OLAP برای آن تهیه می‌گردد. استفاده کردن از table partitioning می‌تواند در جبران افزایش تعداد رکورد کمک زیادی به ما بکند.

slowly changing dimension (SCD) : سه روش برای نگه داری سابقه‌ی تغییرات در جداول Dimension وجود دارد.

1. SCD type 1 : هیچ گونه سابقه‌ی تغییراتی را نگه داری نمی‌کنیم

2. SCD type 2 : سابقه‌ی تغییرات در ردیف‌ها نگه داری می‌شود. در این روش هر ردیف، شماره ردیف قبلی را دارد و تعداد نا محدودی از تغییرات را نگه داری می‌کنیم.

3. SCD type 3 : سابقه‌ی تغییرات در ستون‌ها نگه داری می‌شوند و فقط ردیف جاری و آخرین تغییرات را نگه داری می‌کنیم.

Query : فقط ETL حق تغییرات در Data Warehouse را دارد و کاربر نمی‌تواند Data Warehouse  را تغییر دهد. البته کاربران حق Query کردن از Data Warehouse را دارند.

دقت داشته باشید که کوئری‌های پیچیده در NDS ‌ها بسیار کندتر از همان کوئری در DDS می‌باشد.

Business Intelligence : مجموعه ای از فعالیت‌ها که در یک سازمان برای شناخت بهتر وضعیت Business آن سازمان انجام می‌شود. نتایج BI کمک بسیاری برای تصمیم گیری‌های تکنیکی و استراتژیکی درون سازمان می‌کند. همچنین کمک به بهبود فرایند‌های Business جاری می‌کند.

فعالیت‌های Business Intelligence در سه دسته بندی قرار می‌گیرند :

1. Reporting : گزارشاتی که از Data Warehouse گرفته می‌شود و به کاربر نمایش داده می‌شود و عمدتا این گزارشات به صورت tabular form می‌باشند.

2. OLAP : فعالیت‌های انجام شده روی MDB برای گرفتن گزارشات Drill-Down و ... می‌باشد.

3. Data mining : فرآیند واکشی و داده کاوی داده‌های درون سیستم می‌باشد، که منجر به کشف الگوها و رفتار‌ها و ارتباطات داده‌ها در سیستم می‌شود. توسط داده کاوی ما متوجه می‌شویم چرا برخی داده‌ها در سیستم تولید شده اند.

a. descriptive analytics : زمانی که از داده کاوی برای شرح وقایع گذشته و حال استفاده می‌شود.

b. predictive analytics : زمانی که از داده کاوی برای پیش بینی وقایع گذشته استفاده می‌شود.

Real time data warehouse  : به DW هایی گفته می‌شود که در کمترین زمان، تغییرات OLTP را در خود خواهند داشت. امروزه این نوع DW ‌ها تغییرات 5 دقیقه تا حداکثر 1 ساعت قبل را در خود دارند. برای دسترسی به چنین DW هایی دو راه زیر وجود دارد :

1. بر روی هر جدول، Trigger هایی باشد تا تغییرات را به DW انتقال دهد. (البته برای این منظور باید Business مربوط به ETL را در این تریگر‌ها نوشت)

2. سورس برنامه‌های اصلی کاربر ( OLTP ) تغییر کند تا علاوه بر OLTP Database ‌ها Data Warehouse را هم تغییر دهند.

روش‌های فوق بسیار روی سرعت و کارایی برنامه‌های اصلی تاثیر خواهند گذاشت.

NDS ( Normalize Data Source ) : در صورتی که طراحی Data Warehouse به صورت Dimensional نباشد و به صورت Normalize باشد، نوع Data Warehouse از نوع NDS می‌باشد.

روش ساخت MDB  :

OLTP Database -> ETL -> Stage Database ->  DDS (Dimensional Data Source = Data Warehouse) -> SSAS -> MDB

روش ساده‌تر ساخت Data Warehouse :

 

منظور از Source System  همان OLTP Database ‌ها می‌باشد.

به خاطر داشته باشید که Source System ‌ها جزئی از Data Warehouse نمی‌باشند.

از کاربرد‌های Data Warehouse می‌توان به موارد زیر اشاره کرد

1. Data Mining

2. استفاده در گزارشات

3. تجمیع داده ها

Data Mining کمک به درک بهتر Business جاری در سازمان می‌کند. همچنین منجر به کشف دانش از درون داده‌ها می‌شود.

برای Data Mining می‌توانید از انواع پایگاه داده‌های موجود مانند رابطه ای ، سلسله مراتبی و چند بعدی استفاده کرد . حتا می‌توان از فایل‌های XML , Excel نیز استفاده کرد.

Customer Relationship Management (CRM) :

منظور از مشتری، مصرف کننده‌ی سرویسی است که سازمان شما ارایه می‌کند. یک سیستم CRM شامل تمامی برنامه ایی می‌باشد که تمام فعالیت‌های مشتری را پشتیبانی می‌کند.

Operational Data Store (ODS) :

این پایگاه داده به صورت رابطه ای و نرمال شده می‌باشد و شامل تمامی اطلاعات پایگاه داده ای OLTP می‌باشد که در این پایگاه داده مجتمع شده اند. تفاوت ODS با Data Warehouse در این می‌باشد که داده‌ها در ODS با هر Transaction به روز می‌شوند (سرعت بروز رسانی اطلاعات در ODS بالاتر از DW می‌باشد).

Master Data Management (MDM)  :

در یک نگاه می‌توان داده‌ها را به دو دسته تقسیم کرد

1. transaction data

2. master data

transaction data : شامل داده ای transactional در سیستم‌های OLTP می‌باشد.

master data : توضیح دهنده‌ی Business جاری در سازمان می‌باشد.

برای تشخیص این دو نیاز است Business سازمان را به خوبی شناسایی نمایید. به عبارت دیگر رویداد‌های Business ی همان transaction data می‌باشند و master data شامل پاسخ‌های این سوال‌ها می‌باشد. چه کسی، چه چیزی و کجا در مورد Business transaction .

Customer data integration (CDI) : عبارت است از MDM در رابطه با مشتری داده ها. کار این قسمت عبارت است از واکشی، پاک سازی ، ذخیره سازی ، نگه داری و به اشتراک گذاشتن داده ای مشتری می‌باشد.

Unstructured Data : داده ای ذخیره شده در پایگاه داده ، structured Data می‌باشند و داده هایی مانند عکس و فیلم و صوت و ...

Service-Oriented Architecture (SOA) : یک متد ساخت برنامه می‌باشد که در این روش تمامی اجزا برنامه به صورت ماژول هایی دیده می‌شود که در آنها ارتباطات با دیگر سیستم‌ها به صورت سرویس می‌باشد و این زیر سیستم‌ها را می‌توان در پروژه‌های مختلف به کار برد.

Real-Time Data Warehouse : DW هایی که توسط ETL به روز می‌شوند در هنگامی که یک Transaction روی OLTP اتفاق می‌افتد.

مراحل انتقال داده از OLTP Database به MDB به صورت زیر می‌باشد.

Data quality : مکانیسم اطمینان بخشی از این که در DW دادهای مناسب و درست وارد می‌شوند. به عبارت دیگر DQ همان firewall برای DW در مقابل داده‌های نامناسب می‌باشد.

برای بهتر مشخص شدن مکان DQ شکل زیر را در نظر بگیرید

نحوه‌ی حرکت داده ای از OLTP به MDB اولین چیزی می‌باشد که شما باید به آن فکر کنید و برای آن روشی را انتخاب نمایید قبل از ساخت   Data Warehouse .

چهار روش برای معماری انتقال اطلاعات از OLTP به DW وجود دارد (البته به عنوان نمونه و شما می‌توانید از روش‌های دیگر و طراحی‌های مختلف و ترکیبی نیز بهره ببرید)

1. single DDS : در این روش فقط Stage , DDS وجود دارد.

2. NDS + DDS : در این روش علاوه بر Stage,DDS از NDS نیز استفاده می‌شود.

3. ODS + DDS : در این روش از Stage,ODS,DDS استفاده می‌گردد.

4. federated data warehouse (FDW ) : استفاده از چندین DW که با هم تجمیع شده اند.

تصویر Single DDS :

تصویر NDS + DDS :

تصویر ODS + DDS :

تصویر federated data warehouse (FDW ) :

منبع : Building a Data Warehouse With Examples in SQL Server  انتشارات Apress

مطالب
مقایسه بین Proxy و ChannelFactory در WCF
برای ساخت یک WCF Client یا دسترسی به یک سرویس WCF دو راه وجود دارد.
  • استفاده از WCF Proxy
  • استفاده از ChannelFactory

قصد دارم طی یک مقایسه کوتاه این دو روش را بررسی کنیم:

WCF Proxy:

Proxy در واقع یک کلاس CLR است که به عنوان نماینده یک اینترفیس که از نوع  Service Contract است مورد استفاده قرار می‌گیرد. یا به زبان ساده تر، یک Proxy در واقع نماینده Service Contract ای که سمت سرور پیاده سازی شده است در سمت کلاینت خواهد بود. Proxy تمام متد یا Operation Contract‌های سمت سرور را داراست به همراه یک سری متد‌ها و خواص دیگر برای مدیریت چرخه طول عمر سرویس،  هم چنین اطلاعات مربوط به وضعیت سرویس و نحوه اتصال آن به سرور. ساخت Proxy به دو روش امکان پذیر است:

  • با استفاده از امکانات AddServiceReference موجود در Visual Studio. کافیست از پنجره معروف زیر با استفاده از یک URL سرویس مورد نظر را به پروژه سمت کلاینت خود اضافه نمایید

همچنین  می‌توانید از قسمت Advanced نیز برای تنظیمات خاص مورد نظر خود(مثل تولید کد برای متد‌های Async یا تعیین نوع Collection‌ها در هنگام انتقال داده و ...) استفاده نمایید.

  • با استفاده از SvcUtil.exe . کاربرد svcutil.exe در موارد Metadata Export، Service Validtation، XmlSerialization Type Generator و Metadata Download و ... خلاصه می‌شود. با استفاده از Vs.Net Command Promptو svcutil می‌توان به سرویس مورد نظر دسترسی داشت.مثال
    svcutil.exe /language:vb /out:generatedProxy.vb /config:app.config http://localhost:8000/ServiceModelSamples/service

ChannelFactory:

ChannelFactory یک کلاس تعبیه شده در دات نت می‌باشد که به وسیله یک اینترفیس که به عنوان تعاریف سرویس سمت سرور است یک نمونه از سرویس مورد نظر را برای ما خواهد ساخت. اما به خاظر داشته باشید از این روش زمانی می‌توان استفاده کرد که دسترسی کامل به سمت سرور و کلاینت داشته باشید.

برای آشنایی با نحوه پیاده سازی به این روش نیز می‌توانید از این مقاله کمک بگیرید.

مثال:

public static TChannel CreateChannel()
        {
            IBookService service;

            var endPointAddress = new EndpointAddress( "http://localhost:7000/service.svc" );

            var httpBinding = new BasicHttpBinding();
            
            ChannelFactory<TChannel> factory = new ChannelFactory<TChannel>( httpBinding, endPointAddress );

            instance= factory.CreateChannel();

            return instance;
        }
همان طور که مشاهده می‌کنید در این روش نیاز به یک EndpointAddress به همراه یک نمونه از نوع Binding مورد نظر دارید. نوع این Binding حتما باید با نوع نمونه ساخته شده در سمت سرور که برای هاست کردن سرویس‌ها مورد استفاده قرار گرفته است یکی باشد. این نوع‌ها می‌تواند شامل NetTcpBidning ،WShttpBinding  BasicHttpBinding ،WSDualHttpBinding، MSMQ Binding و البته چند نوع دیگر نیز باشد.
در نتیجه برای ساخت یک سرویس به روش ChannelFactory باید مراحل زیر را طی نمایید:
  • یک نمونه از WCF Binding بسازید
  • یک نمونه از کلاس EndPointAddress به همراه آدرس سرویس مورد نظر در سمت سرور بسازید(البته می‌توان این مرحله را نادیده گرفت و آدرس سرویس را مستقیما به نمونه ChannelFactory به عنوان پارامتر پاس داد)
  • یک نمونه از کلاس ChannelFactory یا استفاده از EndPointAddress بسازید
  • با استفاده از ChannelFactory یک نمونه از Channel مورد نظر را فراخوانی نمایید(فراخوانی متد CreateChannel)

تفاوت‌های دو روش

Proxy
 ChannelFactory
فقط نیاز به یک URL برای ساخت سرویس مورد نظر دارد. بقیه مراحل توسط ابزار‌های مرتبط انجام خواهد شد  
 شما نیاز به دسترسی مستقیم به اسمبلی حاوی Service Contract پروژه خود دارید.
 استفاده از این روش بسیار آسان و ساده است
 پیاده سازی آن پیچیدگی بیشتر دارد
فهم مفاهیم این روش بسیار راحت است
نیاز به دانش اولیه از مفاهیم WCF برای پیاده سازی دارد
 زمانی که میزان تغییرات در کلاس‌های مدل و Entity‌ها زیاد باشد این روش بسیار موثر است.(مدیریت تغییرات در WCF)
 زمانی که اطمینان دارید که مدل و entity‌ها پروژه زیاد تغییر نخواهند کرد و از طرفی نیاز به کد نویسی کمتر در سمت کلاینت دارید، این روش موثرتر خواهد بود
 فقط به اینترفیس هایی که دارای ServiceContractAttribute هستند دسترسی خواهیم داشت.
به تمام اینترفیس‌های تعریف شده در بخش  Contracts دسترسی داریم.
 فقط به متد‌های که دارای OperationContractAttribute هستند دسترسی خواهیم داشت.    به تمام متد‌های عمومی سرویس دسترسی داریم.  

آیا می‌توان از روش AddServiceReference تعبیه شده در Vs.Net، برای ساخت ChannelFactory استفاده کرد؟

بله! کافیست هنگام ساخت سرویس، در پنجره AddServiceReference از قسمت Advanced وارد برگه تنظیمات شوید. سپس تیک مربوط به قسمت های  Reused Type in referenced assemblies  و Reused Types in specified referenced assemblies را بزنید. بعد از لیست پایین، اسمبلی‌های مربوط به Domain Model و هم چنین Contract‌های سمت سرور را انتخاب نمایید. در این حالت شما از روش Channel Factory برای ساخت سرویس WCF استفاده کرده اید.

مطالب
بررسی تغییرات Blazor 8x - قسمت دهم - مدیریت حالت کاربران در روش‌های مختلف رندر
رفتار Blazorهای پیش از دات‌نت 8 در مورد مدیریت حالت

پیش از دات نت 8، دو حالت عمده برای توسعه‌ی برنامه‌های Blazor وجود داشت: Blazor Server و Blazor WASM. در هر دو حالت، طول عمر سیستم تزریق وابستگی‌های ایجاد و مدیریت شده‌ی توسط Blazor، معادل طول عمر برنامه‌است.

در برنامه‌های Blazor Server، طول عمر سیستم تزریق وابستگی‌ها، توسط ASP.NET Core قرار گرفته‌ی بر روی سرور مدیریت شده و نمونه‌های ایجاد شده‌ی سرویس‌های توسط آن، به ازای هر کاربر متفاوت است. بنابراین اگر طول عمر سرویسی در اینجا به صورت Scoped تعریف شود، این سرویس فقط یکبار در طول عمر برنامه، به ازای یک کاربر جاری برنامه، تولید و نمونه سازی می‌شود. در این مدل برنامه‌ها، سرویس‌هایی با طول عمر Singleton، بین تمام کاربران به اشتراک گذاشته می‌شوند. به همین جهت است که در این نوع برنامه‌ها، مدیریت سرویس Context مخصوص EF-Core‌ نکات خاصی را به همراه دارد. چون اگر بر اساس سیستم پیش‌فرض تزریق وابستگی‌ها و طول عمر Scoped این سرویس عمل شود، یک Context فقط یکبار به‌ازای یک کاربر، یکبار نمونه سازی شده و تا پایان طول عمر برنامه، بدون تغییر زنده نگه داشته می‌شود؛ در حالیکه عموم توسعه دهندگان EF-Core تصور می‌کنند سرویس‌های Scoped، پس از پایان یک درخواست، پایان یافته و Dispose می‌شوند، اما در اینجا پایان درخواستی نداریم. یک اتصال دائم SignalR را داریم و تا زمانیکه برقرار است، یعنی برنامه زنده‌است. بنابراین در برنامه‌های Blazor Server، سرویس‌های Scoped، به ازای هر کاربر، همانند Singleton رفتار می‌کنند (در سراسر برنامه به ازای یک کاربر در دسترس هستند) و سرویس‌هایی از اساس Singleton، بین تمام کاربران به اشتراک گذاشته می‌شوند.

در برنامه‌های Blazor WASM، طول عمر سیستم تزریق وابستگی‌ها، توسط برنامه‌ی وب‌اسمبلی در حال اجرای بر روی مرورگر مدیریت می‌شود. یعنی مختص به یک کاربر بوده و طول عمر آن وابسته‌است به طول عمر برگه‌ی جاری مرورگر. بنابراین دراینجا بین سرویس‌های Scoped و Singleton، تفاوتی وجود ندارد و همانند هم رفتار می‌کنند (هر دو مختص به یک کاربر و وابسته به طول عمر برگه‌ی جاری هستند).

در هیچکدام از این حالت‌ها، امکان دسترسی به HttpContext وجود ندارد (نه داخل اتصال دائم SignalR برنامه‌های Blazor Server و نه داخل برنامه‌ی وب‌اسمبلی در حال اجرای در مرورگر). اطلاعات بیشتر
بنابراین در این برنامه‌ها برای نگهداری اطلاعات کاربر لاگین شده‌ی به سیستم و یا سایر اطلاعات سراسری برنامه، عموما از سرویس‌هایی با طول عمر Scoped استفاده می‌شود که در تمام قسمت‌های برنامه به ازای هر کاربر، قابل دسترسی هستند.

 
رفتار Blazor 8x در مورد مدیریت حالت

هرچند دات نت 8 به همراه حالت‌های رندر جدیدی است، اما هنوز هم می‌توان برنامه‌هایی کاملا توسعه یافته بر اساس مدل‌های قبلی Blazor Server و یا Blazor WASM را همانند دات‌نت‌های پیش از 8 داشت. بنابراین اگر تصمیم گرفتید که بجای استفاده از جزیره‌های تعاملی، کل برنامه را به صورت سراسری تعاملی کنید، همان نکات قبلی، در اینجا هم صادق هستند و از لحاظ مدیریت حالت، تفاوتی نمی‌کنند.

اما ... اگر تصمیم گرفتید که از حالت‌های رندر جدید استفاده کنید، مدیریت حالت آن متفاوت است؛ برای مثال دیگر با یک سیستم مدیریت تزریق وابستگی‌ها که طول عمر آن با طول عمر برنامه‌ی Blazor یکی است، مواجه نیستیم و حالت‌های زیر برای آن‌ها متصور است:

حالت رندر: صفحات رندر شده‌ی در سمت سرور یا Server-rendered pages
مفهوم: یک صفحه‌ی Blazor که در سمت سرور رندر شده و HTML نهایی آن به سمت مرورگر کاربر ارسال می‌شود. در این حالت هیچ اتصال SignalR و یا برنامه‌ی وب‌اسمبلی اجرا نخواهد شد.
عواقب: طول عمر سرویس‌های Scoped، به‌محض پایان رندر صفحه در سمت سرور، پایان خواهند یافت.
بنابراین در این حالت طول عمر یک سرویس Scoped، بسیار کوتاه است (در حد ابتدا و انتهای رندر صفحه). همچنین چون برنامه در سمت سرور اجرا می‌شود، دسترسی کامل و بدون مشکلی را به HttpContext دارد.
صفحات SSR، بدون حالت (stateless) هستند؛ به این معنا که حالت کاربر در بین هدایت به صفحات مختلف برنامه ذخیره نمی‌شود. به آن‌ها می‌توان از این لحاظ به‌مانند برنامه‌های MVC/Razor pages نگاه کرد. در این حالت اگر می‌خواهید حالت کاربران را ذخیره کنید، استفاده از کوکی‌ها و یا سشن‌ها، راه‌حل متداول اینکار هستند.

حالت رندر: صفحات استریمی (Streamed pages)
مفهوم: یک صفحه‌ی Blazor که در سمت سرور رندر شده و قطعات آماده شده‌ی HTML آن به صورت استریمی از داده‌ها، به سمت مرورگر کاربر ارسال می‌شوند. در این حالت هیچ اتصال SignalR و یا برنامه‌ی وب‌اسمبلی اجرا نخواهد شد.
عواقب: طول عمر سرویس‌های Scoped، به‌محض پایان رندر صفحه در سمت سرور، پایان خواهند یافت.
بنابراین در این حالت طول عمر یک سرویس Scoped، بسیار کوتاه است (در حد ابتدا و انتهای رندر صفحه). همچنین چون برنامه در سمت سرور اجرا می‌شود، دسترسی کامل و بدون مشکلی را به HttpContext دارد.

حالت رندر: Blazor server page
مفهوم: یک صفحه‌ی Blazor Server که یک اتصال دائم SignalR را با سرور دارد.
عواقب: طول عمر سرویس‌های Scoped، معادل طول عمر اتصال SignalR است و با قطع این اتصال، پایان خواهند یافت. این نوع برنامه‌ها اصطلاحا stateful هستند و از لحاظ دسترسی به حالت کاربر، تجربه‌ی کاربری همانند یک برنامه‌ی دسکتاپ را ارائه می‌دهند.
در این نوع برنامه‌ها و درون اتصال SignalR، دسترسی به HttpContext وجود ندارد.

حالت رندر: Blazor wasm page
مفهوم: صفحه‌ای که به کمک فناوری وب‌اسمبلی، درون مرورگر کاربر اجرا می‌شود.
عواقب: طول عمر سرویس‌های Scoped، معادل طول عمر برگه و صفحه‌ی جاری است و با بسته شدن آن، پایان می‌پذیرد. این نوع برنامه‌ها نیز اصطلاحا stateful هستند و از لحاظ دسترسی به حالت کاربر، تجربه‌ی کاربری همانند یک برنامه‌ی دسکتاپ را ارائه می‌دهند (البته فقط درون مروگر کاربر).
در این نوع برنامه‌ها، دسترسی به HttpContext وجود ندارد.

حالت رندر: جزیره‌ی تعاملی Blazor Server و یا Blazor server island
مفهوم: یک کامپوننت Blazor Server که درون یک صفحه‌ی دیگر (که عموما از نوع SSR است) قرار گرفته و یک اتصال SignalR را با سرور برقرار می‌کند.
عواقب: طول عمر سرویس‌های Scoped، معادل طول عمر اتصال SignalR است و با قطع این اتصال، پایان خواهند یافت؛ برای مثال کاربر به صفحه‌ای دیگر در این برنامه مراجعه کند. بنابراین این نوع کامپوننت‌ها هم تا زمانیکه کاربر در صفحه‌ی جاری قرار دارد، stateful هستند.
در این نوع برنامه‌ها و درون اتصال SignalR، دسترسی به HttpContext وجود ندارد.

حالت رندر: جزیره‌ی تعاملی Blazor WASM و یا Blazor wasm island
مفهوم: یک کامپوننت Blazor WASM که درون یک صفحه‌ی دیگر (که عموما از نوع SSR است) توسط فناوری وب‌اسمبلی، درون مرورگر کاربر اجرا می‌شود.
عواقب: طول عمر سرویس‌های Scoped، معادل مدت زمان فعال بودن صفحه‌ی جاری است. به محض اینکه کاربر به صفحه‌ای دیگر مراجعه و این کامپوننت دیگر فعال نباشد، طول عمر آن خاتمه خواهد یافت. بنابراین این نوع کامپوننت‌ها هم تا زمانیکه کاربر در صفحه‌ی جاری قرار دارد، stateful هستند (البته این حالت درون مرورگر کاربر مدیریت می‌شود و نه در سمت سرور).
در این نوع برنامه‌ها، دسترسی به HttpContext وجود ندارد.


نتیجه‌گیری

همانطور که مشاهده می‌کنید، در صفحات SSR، دسترسی کاملی به HttpContext سمت سرور وجود دارد (که البته کوتاه مدت بوده و با پایان رندر صفحه، خاتمه خواهد یافت؛ حالتی مانند صفحات MVC و Razor pages)، اما در جزایر تعاملی واقع در آن‌ها، خیر.
مساله‌ی مهم در اینجا، مدیریت اختلاط حالت صفحات SSR و جزایر تعاملی واقع در آن‌ها است. مایکروسافت جهت پیاده سازی اعتبارسنجی و احراز هویت کاربران در Blazor 8x و برای انتقال حالت به این جزایر، از دو روش Root-level cascading values و سرویس PersistentComponentState استفاده کرده‌است که آن‌ها را در دو قسمت بعدی، با توضیحات بیشتری بررسی می‌کنیم.
مطالب
رمزگشایی عنوان یک ایمیل فارسی دریافت شده

گوگل اجازه‌ی فعال کردن POP3 را روی اکانت‌ها GMail می‌دهد. فرض کنید با استفاده از یکی از کلاینت‌های POP3 دات نت می‌خواهیم ایمیل‌ها را با برنامه نویسی دریافت کنیم (و مثلا از Outlook استفاده نکنیم). اکنون به نظر شما عنوان دریافت شده زیر چه معنایی دارد؟
=?UTF-8?B?QW5hbHl0aWNzIHZhaGlkbmFzaXJpLmJsb2dzcG90LmNvbSAyMDA4MTIyNiAo2KLZhdin?= =?UTF-8?B?2LEg2LPYp9mK2Kop?=

برای درک اتفاق رخ داده باید به RFC ‌های مربوطه مراجعه کرد (RFC-2822 و RFC-2047). مطابق استانداردهای ذکر شده، هدر ارسالی یک ایمیل همواره باید از حروف اسکی تشکیل شود. حال اگر عنوان ایمیل که جزئی از هدر را تشکیل می‌دهد از حروف غیر اسکی تشکیل شد، حتما باید یک لایه encoding روی آن‌ها صورت گیرد. دو حالت تعریف شده در این‌جا مطابق استاندارد میسر است:
الف) Quoted Printable : در این حالت عنوان با =?utf-8?Q شروع می‌شود.
ب) Base64 : در این روش عنوان با =?utf-8?B شروع خواهد شد.

روش متداول، روش ب است که نسبت به روش الف فشرده‌تر می‌باشد. در این حالت برای درک معنای قسمت‌های مختلف رشته دریافت شده باید به الگوی زیر مراجعه کرد:
=?charset?encoding?EncodedText?=
در این‌جا charset بیانگر نحوه encoding متن اصلی است که بر روی آن الگوریتم base64 اعمال شده.
در رشته طولانی فوق که در ابتدای مقاله به آن اشاره شده، عنوان به دو قسمت تجزیه شده. یا به عبارتی دوبار الگوی فوق در آن تکرار شده است که باید EncodedText های آن‌ها را یافت و سپس آن‌ها را با توجه به charset مربوطه از حالت base64 به یک رشته معمولی تبدیل نمود.

//using System.Text;
public static string Base64ToString(string charset, string encodedString)
{
//تبدیل بیس 64 به آرایه‌ای از بایت‌ها
byte[] buffer = Convert.FromBase64String(encodedString);
//تبدیل آرایه‌ای از بایت‌ها به رشته با توجه به انکدینگ مربوطه
return Encoding.GetEncoding(charset).GetString(buffer);
}
اکنون عنوان صحیح ایمیل فوق به صورت زیر قابل دریافت خواهد بود:

string subject = Base64ToString("utf-8", "QW5hbHl0aWNzIHZhaGlkbmFzaXJpLmJsb2dzcG90LmNvbSAyMDA4MTIyNiAo2KLZhdin2LEg2LPYp9mK2Kop");

مطالب
EF Code First #3

بررسی تعاریف نگاشت‌ها به کمک متادیتا در EF Code first

در قسمت قبل مروری سطحی داشتیم بر امکانات مهیای جهت تعاریف نگاشت‌ها در EF Code first. در این قسمت، حالت استفاده از متادیتا یا همان data annotations را با جزئیات بیشتری بررسی خواهیم کرد.
برای این منظور پروژه کنسول جدیدی را آغاز نمائید. همچنین به کمک NuGet، ارجاعات لازم را به اسمبلی EF، اضافه کنید. در ادامه مدل‌های زیر را به پروژه اضافه نمائید؛ یک شخص که تعدادی پروژه منتسب می‌تواند داشته باشد:

using System;
using System.Collections.Generic;

namespace EF_Sample02.Models
{
public class User
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Name { set; get; }
public string LastName { set; get; }
public string Email { set; get; }
public string Description { set; get; }
public byte[] Photo { set; get; }
public IList<Project> Projects { set; get; }
}
}

using System;

namespace EF_Sample02.Models
{
public class Project
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Title { set; get; }
public string Description { set; get; }
public virtual User User { set; get; }
}
}

به خاصیت public virtual User User در کلاس Project اصطلاحا Navigation property هم گفته می‌شود.
دو کلاس زیر را نیز جهت تعریف کلاس Context که بیانگر کلاس‌های شرکت کننده در تشکیل بانک اطلاعاتی هستند و همچنین کلاس آغاز کننده بانک اطلاعاتی سفارشی را به همراه تعدادی رکورد پیش فرض مشخص می‌کنند، به پروژه اضافه نمائید.

using System;
using System.Collections.Generic;
using System.Data.Entity;
using EF_Sample02.Models;

namespace EF_Sample02
{
public class Sample2Context : DbContext
{
public DbSet<User> Users { set; get; }
public DbSet<Project> Projects { set; get; }
}

public class Sample2DbInitializer : DropCreateDatabaseAlways<Sample2Context>
{
protected override void Seed(Sample2Context context)
{
context.Users.Add(new User
{
AddDate = DateTime.Now,
Name = "Vahid",
LastName = "N.",
Email = "name@site.com",
Description = "-",
Projects = new List<Project>
{
new Project
{
Title = "Project 1",
AddDate = DateTime.Now.AddDays(-10),
Description = "..."
}
}
});

base.Seed(context);
}
}
}

به علاوه در فایل کانفیگ برنامه، تنظیمات رشته اتصالی را نیز اضافه نمائید:

<connectionStrings>
<add
name="Sample2Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>

همانطور که ملاحظه می‌کنید، در اینجا name به نام کلاس مشتق شده از DbContext اشاره می‌کند (یکی از قراردادهای توکار EF Code first است).

یک نکته:
مرسوم است کلاس‌های مدل را در یک class library جداگانه اضافه کنند به نام DomainClasses و کلاس‌های مرتبط با DbContext را در پروژه class library دیگری به نام DataLayer. هیچکدام از این پروژه‌ها نیازی به فایل کانفیگ و تنظیمات رشته اتصالی ندارند؛ زیرا اطلاعات لازم را از فایل کانفیگ پروژه اصلی که این دو پروژه class library را به خود الحاق کرده، دریافت می‌کنند. دو پروژه class library اضافه شده تنها باید ارجاعاتی را به اسمبلی‌های EF و data annotations داشته باشند.

در ادامه به کمک متد Database.SetInitializer که در قسمت دوم به بررسی آن پرداختیم و با استفاده از کلاس سفارشی Sample2DbInitializer فوق، نسبت به ایجاد یک بانک اطلاعاتی خالی تشکیل شده بر اساس تعاریف کلاس‌های دومین پروژه، اقدام خواهیم کرد:

using System;
using System.Data.Entity;

namespace EF_Sample02
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new Sample2DbInitializer());
using (var db = new Sample2Context())
{
var project1 = db.Projects.Find(1);
Console.WriteLine(project1.Title);
}
}
}
}

تا زمانیکه وهله‌ای از Sample2Context ساخته نشود و همچنین یک کوئری نیز به بانک اطلاعاتی ارسال نگردد، Sample2DbInitializer در عمل فراخوانی نخواهد شد.
ساختار بانک اطلاعاتی پیش فرض تشکیل شده نیز مطابق اسکریپت زیر است:

CREATE TABLE [dbo].[Users](
[Id] [int] IDENTITY(1,1) NOT NULL,
[AddDate] [datetime] NOT NULL,
[Name] [nvarchar](max) NULL,
[LastName] [nvarchar](max) NULL,
[Email] [nvarchar](max) NULL,
[Description] [nvarchar](max) NULL,
[Photo] [varbinary](max) NULL,
CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]


CREATE TABLE [dbo].[Projects](
[Id] [int] IDENTITY(1,1) NOT NULL,
[AddDate] [datetime] NOT NULL,
[Title] [nvarchar](max) NULL,
[Description] [nvarchar](max) NULL,
[User_Id] [int] NULL,
CONSTRAINT [PK_Projects] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[Projects] WITH CHECK ADD CONSTRAINT [FK_Projects_Users_User_Id] FOREIGN KEY([User_Id])
REFERENCES [dbo].[Users] ([Id])
GO

ALTER TABLE [dbo].[Projects] CHECK CONSTRAINT [FK_Projects_Users_User_Id]
GO

توضیحاتی در مورد ساختار فوق، جهت یادآوری مباحث دو قسمت قبل:
- خواصی با نام Id تبدیل به primary key و identity field شده‌اند.
- نام جداول، همان نام خواص تعریف شده در کلاس Context است.
- تمام رشته‌ها به nvarchar از نوع max نگاشت شده‌اند و null پذیر می‌باشند.
- خاصیت تصویر که با آرایه‌ای از بایت‌ها تعریف شده به varbinary از نوع max نگاشت شده است.
- بر اساس ارتباط بین کلاس‌ها فیلد User_Id در جدول Projects اضافه شده است که توسط قیدی به نام FK_Projects_Users_User_Id، جهت تعریف کلید خارجی عمل می‌کند. این نام گذاری پیش فرض هم بر اساس نام خواص در دو کلاس انجام می‌شود.
- schema پیش فرض بکارگرفته شده، dbo است.
- null پذیری پیش فرض فیلدها بر اساس اصول زبان مورد استفاده تعیین شده است. برای مثال در سی شارپ، نوع int نال پذیر نیست یا نوع DateTime نیز به همین ترتیب یک value type است. بنابراین در اینجا این دو نوع به صورت not null تعریف شده‌اند (صرفنظر از اینکه در SQL Server هر دو نوع یاد شده، null پذیر هم می‌توانند باشند). بدیهی است امکان تعریف nullable types نیز وجود دارد.


مروری بر انواع متادیتای قابل استفاده در EF Code first

1) Key
همانطور که ملاحظه کردید اگر نام خاصیتی Id یا ClassName+Id باشد، به صورت خودکار به عنوان primary key جدول، مورد استفاده قرار خواهد گرفت. این یک قرارداد توکار است.
اگر یک چنین خاصیتی با نام‌های ذکر شده در کلاس وجود نداشته باشد، می‌توان با مزین سازی خاصیتی مفروض با ویژگی Key که در فضای نام System.ComponentModel.DataAnnotations قرار دارد، آن‌را به عنوان Primary key معرفی نمود. برای مثال:

public class Project
{
[Key]
public int ThisIsMyPrimaryKey { set; get; }

و ضمنا باید دقت داشت که حین کار با ORMs فرقی نمی‌کند EF باشد یا سایر فریم ورک‌های دیگر، داشتن یک key جهت عملکرد صحیح فریم ورک، ضروری است. بر اساس یک Key است که Entity معنا پیدا می‌کند.


2) Required
ویژگی Required که در فضای نام System.ComponentModel.DataAnnotations تعریف شده است، سبب خواهد شد یک خاصیت به صورت not null در بانک اطلاعاتی تعریف شود. همچنین در مباحث اعتبارسنجی برنامه، پیش از ارسال اطلاعات به سرور نیز نقش خواهد داشت. در صورت نال بودن خاصیتی که با ویژگی Required مزین شده است، یک استثنای اعتبارسنجی پیش از ذخیره سازی اطلاعات در بانک اطلاعاتی صادر می‌گردد. این ویژگی علاوه بر EF Code first در ASP.NET MVC نیز به نحو یکسانی تاثیرگذار است.


3) MaxLength و MinLength
این دو ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارند (اما در اسمبلی EntityFramework.dll تعریف شده‌اند و جزو اسمبلی‌ پایه System.ComponentModel.DataAnnotations.dll نیستند). در ذیل نمونه‌ای از تعریف این‌ها را مشاهده می‌کنید. همچنین باید درنظر داشت که روش دیگر تعریف متادیتا، ترکیب آن‌ها در یک سطر نیز می‌باشد. یعنی الزامی ندارد در هر سطر یک متادیتا را تعریف کرد:

[MaxLength(50, ErrorMessage = "حداکثر 50 حرف"), MinLength(4, ErrorMessage = "حداقل 4 حرف")]
public string Title { set; get; }

ویژگی MaxLength بر روی طول فیلد تعریف شده در بانک اطلاعاتی تاثیر دارد. برای مثال در اینجا فیلد Title از نوع nvarchar با طول 30 تعریف خواهد شد.
ویژگی MinLength در بانک اطلاعاتی معنایی ندارد.
هر دوی این ویژگی‌ها در پروسه اعتبار سنجی اطلاعات مدل دریافتی تاثیر دارند. برای مثال در اینجا اگر طول عنوان کمتر از 4 حرف باشد، یک استثنای اعتبارسنجی صادر خواهد شد.

ویژگی دیگری نیز به نام StringLength وجود دارد که جهت تعیین حداکثر طول رشته‌ها به کار می‌رود. این ویژگی سازگاری بیشتر با ASP.NET MVC‌ دارد از این جهت که Client side validation آن‌را نیز فعال می‌کند.


4) Table و Column
این دو ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارند، اما در اسمبلی EntityFramework.dll تعریف شده‌اند. بنابراین اگر تعاریف مدل‌های شما در پروژه Class library جداگانه‌ای قراردارند، نیاز خواهد بود تا ارجاعی را به اسمبلی EntityFramework.dll نیز داشته باشند.
اگر از نام پیش فرض جداول تشکیل شده خرسند نیستید، ویژگی Table را بر روی یک کلاس قرار داده و نام دیگری را تعریف کنید. همچنین اگر Schema کاربری رشته اتصالی به بانک اطلاعاتی شما dbo نیست، باید آن‌را در اینجا صریحا ذکر کنید تا کوئری‌های تشکیل شده به درستی بر روی بانک اطلاعاتی اجرا گردند:

[Table("tblProject", Schema="guest")]
public class Project

توسط ویژگی Column سه خاصیت یک فیلد بانک اطلاعاتی را می‌توان تعیین کرد:

[Column("DateStarted", Order = 4, TypeName = "date")]
public DateTime AddDate { set; get; }

به صورت پیش فرض، خاصیت فوق با همین نام AddDate در بانک اطلاعاتی ظاهر می‌گردد. اگر برای مثال قرار است از یک بانک اطلاعاتی قدیمی استفاده شود یا قرار نیست از شیوه نامگذاری خواص در سی شارپ در یک بانک اطلاعاتی پیروی شود، توسط ویژگی Column می‌توان این تعاریف را سفارشی نمود.
توسط پارامتر Order آن که از صفر شروع می‌شود، ترتیب قرارگیری فیلدها در حین تشکیل یک جدول مشخص می‌گردد.
اگر نیاز است نوع فیلد تشکیل شده را نیز سفارشی سازی نمائید، می‌توان از پارامتر TypeName استفاده کرد. برای مثال در اینجا علاقمندیم از نوع date مهیا در SQL Server 2008 استفاده کنیم و نه از نوع datetime پیش فرض آن.

نکته‌ای در مورد Order:
Order پیش فرض تمام خواصی که قرار است به بانک اطلاعاتی نگاشت شوند، به int.MaxValue تنظیم شده‌اند. به این معنا که تنظیم فوق با Order=4 سبب خواهد شد تا این فیلد، پیش از تمام فیلدهای دیگر قرار گیرد. بنابراین نیاز است Order اولین خاصیت تعریف شده را به صفر تنظیم نمود. (البته اگر واقعا نیاز به تنظیم دستی Order داشتید)


نکاتی در مورد تنظیمات ارث بری در حالت استفاده از متادیتا:
حداقل سه حالت ارث بری را در EF code first می‌توان تعریف و مدیریت کرد:
الف) Table per Hierarchy - TPH
حالت پیش فرض است. نیازی به هیچگونه تنظیمی ندارد. معنای آن این است که «لطفا تمام اطلاعات کلاس‌هایی را که از هم ارث بری کرده‌اند در یک جدول بانک اطلاعاتی قرار بده». فرض کنید یک کلاس پایه شخص را دارید که کلاس‌های بازیکن و مربی از آن ارث بری می‌کنند. زمانیکه کلاس پایه شخص توسط DbSet در کلاس مشتق شده از DbContext در معرض استفاده EF قرار می‌گیرد، بدون نیاز به هیچ تنظیمی، تمام این سه کلاس، تبدیل به یک جدول شخص در بانک اطلاعاتی خواهند شد. یعنی یک table به ازای سلسله مراتبی (Hierarchy) که تعریف شده.
ب) Table per Type - TPT
به این معنا است که به ازای هر نوع، باید یک جدول تشکیل شود. به عبارتی در مثال قبل، یک جدول برای شخص، یک جدول برای مربی و یک جدول برای بازیکن تشکیل خواهد شد. دو جدول مربی و بازیکن با یک کلید خارجی به جدول شخص مرتبط می‌شوند. تنها تنظیمی که در اینجا نیاز است، قرار دادن ویژگی Table بر روی نام کلاس‌های بازیکن و مربی است. به این ترتیب حالت پیش فرض الف (TPH) اعمال نخواهد شد.
ج) Table per Concrete Type - TPC
در این حالت فقط دو جدول برای بازیکن و مربی تشکیل می‌شوند و جدولی برای شخص تشکیل نخواهد شد. خواص کلاس شخص، در هر دو جدول مربی و بازیکن به صورت جداگانه‌ای تکرار خواهد شد. تنظیم این مورد نیاز به استفاده از Fluent API دارد.

توضیحات بیشتر این موارد به همراه مثال، موکول خواهد شد به مباحث استفاده از Fluent API که برای تعریف تنظیمات پیشرفته نگاشت‌ها طراحی شده است. استفاده از متادیتا تنها قسمت کوچکی از توانایی‌های Fluent API را شامل می‌شود.



5) ConcurrencyCheck و Timestamp
هر دوی این ویژگی‌ها در فضای نام System.ComponentModel.DataAnnotations و اسمبلی به همین نام تعریف شده‌اند.
در EF Code first دو راه برای مدیریت مسایل همزمانی وجود دارد:
[ConcurrencyCheck]
public string Name { set; get; }

[Timestamp]
public byte[] RowVersion { set; get; }

زمانیکه از ویژگی ConcurrencyCheck استفاده می‌شود، تغییر خاصی در سمت بانک اطلاعاتی صورت نخواهد گرفت، اما در برنامه، کوئری‌های update و delete ایی که توسط EF صادر می‌شوند، اینبار اندکی متفاوت خواهند بود. برای مثال برنامه جاری را به نحو زیر تغییر دهید:

using System;
using System.Data.Entity;

namespace EF_Sample02
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new Sample2DbInitializer());
using (var db = new Sample2Context())
{
//update
var user = db.Users.Find(1);
user.Name = "User name 1";
db.SaveChanges();
}
}
}
}

متد Find بر اساس primary key عمل می‌کند. به این ترتیب، اول رکورد یافت شده و سپس نام آن‌ تغییر کرده و در ادامه، اطلاعات ذخیره خواهند شد.
اکنون اگر توسط SQL Server Profiler کوئری update حاصل را بررسی کنیم، به نحو زیر خواهد بود:

exec sp_executesql N'update [dbo].[Users]
set [Name] = @0
where (([Id] = @1) and ([Name] = @2))
',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'User name 1',@1=1,@2=N'Vahid'

همانطور که ملاحظه می‌کنید، برای به روز رسانی فقط از primary key جهت یافتن رکورد استفاده نکرده، بلکه فیلد Name را نیز دخالت داده است. از این جهت که مطمئن شود در این بین، رکوردی که در حال به روز رسانی آن هستیم، توسط کاربر دیگری در شبکه تغییر نکرده باشد و اگر در این بین تغییری رخ داده باشد، یک استثناء صادر خواهد شد.
همین رفتار در مورد delete نیز وجود دارد:
//delete
var user = db.Users.Find(1);
db.Users.Remove(user);
db.SaveChanges();
که خروجی آن به صورت زیر است:

exec sp_executesql N'delete [dbo].[Users]
where (([Id] = @0) and ([Name] = @1))',N'@0 int,@1 nvarchar(max) ',@0=1,@1=N'Vahid'

در اینجا نیز به علت مزین بودن خاصیت Name به ویژگی ConcurrencyCheck، فقط همان رکوردی که یافت شده باید حذف شود و نه نمونه تغییر یافته آن توسط کاربری دیگر در شبکه.
البته در این مثال شاید این پروسه تنها چند میلی ثانیه به نظر برسد. اما در برنامه‌ای با رابط کاربری، شخصی ممکن است اطلاعات یک رکورد را در یک صفحه دریافت کرده و 5 دقیقه بعد بر روی دکمه save کلیک کند. در این بین ممکن است شخص دیگری در شبکه همین رکورد را تغییر داده باشد. بنابراین اطلاعاتی را که شخص مشاهده می‌کند، فاقد اعتبار شده‌اند.

ConcurrencyCheck را بر روی هر فیلدی می‌توان بکاربرد، اما ویژگی Timestamp کاربرد مشخص و محدودی دارد. باید به خاصیتی از نوع byte array اعمال شود (که نمونه‌ای از آن‌را در بالا در خاصیت public byte[] RowVersion مشاهده نمودید). علاوه بر آن، این ویژگی بر روی بانک اطلاعاتی نیز تاثیر دارد (نوع فیلد را در SQL Server تبدیل به timestamp می‌کند و نه از نوع varbinary مانند فیلد تصویر). SQL Server با این نوع فیلد به خوبی آشنا است و قابلیت مقدار دهی خودکار آن‌را دارد. بنابراین نیازی نیست در حین تشکیل اشیاء در برنامه، قید شود.
پس از آن، این فیلد مقدار دهی شده به صورت خودکار توسط بانک اطلاعاتی، در تمام updateها و deleteهای EF Code first حضور خواهد داشت:

exec sp_executesql N'delete [dbo].[Users]
where ((([Id] = @0) and ([Name] = @1)) and ([RowVersion] = @2))',N'@0 int,@1 nvarchar(max) ,
@2 binary(8)',@0=1,@1=N'Vahid',@2=0x00000000000007D1

از این جهت که اطمینان حاصل شود، واقعا مشغول به روز رسانی یا حذف رکوردی هستیم که در ابتدای عملیات از بانک اطلاعاتی دریافت کرده‌ایم. اگر در این بین RowVesrion تغییر کرده باشد، یعنی کاربر دیگری در شبکه این رکورد را تغییر داده و ما در حال حاضر مشغول به کار با رکوردی غیرمعتبر هستیم.
بنابراین استفاده از Timestamp را می‌توان به عنوان یکی از best practices طراحی برنامه‌های چند کاربره ASP.NET درنظر داشت.


6) NotMapped و DatabaseGenerated
این دو ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارند، اما در اسمبلی EntityFramework.dll تعریف شده‌اند.
به کمک ویژگی DatabaseGenerated، مشخص خواهیم کرد که این فیلد قرار است توسط بانک اطلاعاتی تولید شود. برای مثال خواصی از نوع public int Id به صورت خودکار به فیلدهایی از نوع identity که توسط بانک اطلاعاتی تولید می‌شوند، نگاشت خواهند شد و نیازی نیست تا به صورت صریح از ویژگی DatabaseGenerated جهت مزین سازی آن‌ها کمک گرفت. البته اگر علاقمند نیستید که primary key شما از نوع identity باشد، می‌توانید از گزینه DatabaseGeneratedOption.None استفاده نمائید:
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { set; get; }

DatabaseGeneratedOption در اینجا یک enum است که به نحو زیر تعریف شده است:

public enum DatabaseGeneratedOption
{
None = 0,
Identity = 1,
Computed = 2
}

تا اینجا حالت‌های None و Identity آن، بحث شدند.
در SQL Server امکان تعریف فیلدهای محاسباتی و Computed با T-SQL نویسی نیز وجود دارد. این نوع فیلدها در هربار insert یا update یک رکورد، به صورت خودکار توسط بانک اطلاعاتی مقدار دهی می‌شوند. بنابراین اگر قرار است خاصیتی به این نوع فیلدها در SQL Server نگاشت شود، می‌توان از گزینه DatabaseGeneratedOption.Computed استفاده کرد.
یا اگر برای فیلدی در بانک اطلاعاتی default value تعریف کرده‌اید، مثلا برای فیلد date متد getdate توکار SQL Server را به عنوان پیش فرض درنظر گرفته‌اید و قرار هم نیست توسط برنامه مقدار دهی شود، باز هم می‌توان آن‌را از نوع DatabaseGeneratedOption.Computed تعریف کرد.
البته باید درنظر داشت که اگر خاصیت DateTime تعریف شده در اینجا به همین نحو بکاربرده شود، اگر مقداری برای آن در حین تعریف یک وهله جدید از کلاس User درکدهای برنامه درنظر گرفته نشود، یک مقدار پیش فرض حداقل به آن انتساب داده خواهد شد (چون value type است). بنابراین نیاز است این خاصیت را از نوع nullable تعریف کرد (public DateTime? AddDate).

همچنین اگر یک خاصیت محاسباتی در کلاسی به صورت ReadOnly تعریف شده است (توسط کدهای مثلا سی شارپ یا وی بی):

[NotMapped]
public string FullName
{
get { return Name + " " + LastName; }
}

بدیهی است نیازی نیست تا آن‌را به یک فیلد بانک اطلاعاتی نگاشت کرد. این نوع خواص را با ویژگی NotMapped می‌توان مزین کرد.
همچنین باید دقت داشت در این حالت، از این نوع خواص دیگر نمی‌توان در کوئری‌های EF استفاده کرد. چون نهایتا این کوئری‌ها قرار هستند به عبارات SQL ترجمه شوند و چنین فیلدی در جدول بانک اطلاعاتی وجود ندارد. البته بدیهی است امکان تهیه کوئری LINQ to Objects (کوئری از اطلاعات درون حافظه) همیشه مهیا است و اهمیتی ندارد که این خاصیت درون بانک اطلاعاتی معادلی دارد یا خیر.


7) ComplexType
ComplexType یا Component mapping مربوط به حالتی است که شما یک سری خواص را در یک کلاس تعریف می‌کنید، اما قصد ندارید این‌ها واقعا تبدیل به یک جدول مجزا (به همراه کلید خارجی) در بانک اطلاعاتی شوند. می‌خواهید این خواص دقیقا در همان جدول اصلی کنار مابقی خواص قرار گیرند؛ اما در طرف کدهای ما به شکل یک کلاس مجزا تعریف و مدیریت شوند.
یک مثال:
کلاس زیر را به همراه ویژگی ComplexType به برنامه مطلب جاری اضافه نمائید:

using System.ComponentModel.DataAnnotations;

namespace EF_Sample02.Models
{
[ComplexType]
public class InterestComponent
{
[MaxLength(450, ErrorMessage = "حداکثر 450 حرف")]
public string Interest1 { get; set; }

[MaxLength(450, ErrorMessage = "حداکثر 450 حرف")]
public string Interest2 { get; set; }
}
}

سپس خاصیت زیر را نیز به کلاس User اضافه کنید:

public InterestComponent Interests { set; get; }

همانطور که ملاحظه می‌کنید کلاس InterestComponent فاقد Id است؛ بنابراین هدف از آن تعریف یک Entity نیست و قرار هم نیست در کلاس مشتق شده از DbContext تعریف شود. از آن صرفا جهت نظم بخشیدن به یک سری خاصیت مرتبط و هم‌خانواده استفاده شده است (مثلا آدرس یک، آدرس 2، تا آدرس 10 یک شخص، یا تلفن یک تلفن 2 یا موبایل 10 یک شخص).
اکنون اگر پروژه را اجرا نمائیم، ساختار جدول کاربر به نحو زیر تغییر خواهد کرد:

CREATE TABLE [dbo].[Users](
---...
[Interests_Interest1] [nvarchar](450) NULL,
[Interests_Interest2] [nvarchar](450) NULL,
---...

در اینجا خواص کلاس InterestComponent، داخل همان کلاس User تعریف شده‌اند و نه در یک جدول مجزا. تنها در سمت کدهای ما است که مدیریت آن‌ها منطقی‌تر شده‌اند.

یک نکته:
یکی از الگوهایی که حین تشکیل مدل‌های برنامه عموما مورد استفاده قرار می‌گیرد، null object pattern نام دارد. برای مثال:

namespace EF_Sample02.Models
{
public class User
{
public InterestComponent Interests { set; get; }
public User()
{
Interests = new InterestComponent();
}
}
}

در اینجا در سازنده کلاس User، به خاصیت Interests وهله‌ای از کلاس InterestComponent نسبت داده شده است. به این ترتیب دیگر در کدهای برنامه مدام نیازی نخواهد بود تا بررسی شود که آیا Interests نال است یا خیر. همچنین استفاده از این الگو حین کار با یک ComplexType ضروری است؛ زیرا EF امکان ثبت رکورد جاری را در صورت نال بودن خاصیت Interests (صرفنظر از اینکه خواص آن مقدار دهی شده‌اند یا خیر) نخواهد داد.


8) ForeignKey
این ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارد، اما در اسمبلی EntityFramework.dll تعریف شده‌است.
اگر از قراردادهای پیش فرض نامگذاری کلیدهای خارجی در EF Code first خرسند نیستید، می‌توانید توسط ویژگی ForeignKey، نامگذاری مورد نظر خود را اعمال نمائید. باید دقت داشت که ویژگی ForeignKey را باید به یک Reference property اعمال کرد. همچنین در این حالت، کلید خارجی را با یک value type نیز می‌توان نمایش داد:
[ForeignKey("FK_User_Id")]
public virtual User User { set; get; }
public int FK_User_Id { set; get; }

در اینجا فیلد اضافی دوم FK_User_Id به جدول Project اضافه نخواهد شد (چون توسط ویژگی ForeignKey تعریف شده است و فقط یکبار تعریف می‌شود). اما در این حالت نیز وجود Reference property ضروری است.


9) InverseProperty
این ویژگی نیز در فضای نام System.ComponentModel.DataAnnotations قرار دارد، اما در اسمبلی EntityFramework.dll تعریف شده‌است.
از ویژگی InverseProperty برای تعریف روابط دو طرفه استفاده می‌شود.
برای مثال دو کلاس زیر را درنظر بگیرید:
public class Book
{
public int ID {get; set;}
public string Title {get; set;}

[InverseProperty("Books")]
public Author Author {get; set;}
}

public class Author
{
public int ID {get; set;}
public string Name {get; set;}

[InverseProperty("Author")]
public virtual ICollection<Book> Books {get; set;}
}

این دو کلاس همانند کلاس‌های User و Project فوق هستند. ذکر ویژگی InverseProperty برای مشخص سازی ارتباطات بین این دو غیرضروری است و قراردادهای توکار EF Code first یک چنین مواردی را به خوبی مدیریت می‌کنند.
اما اکنون مثال زیر را درنظر بگیرید:
public class Book
{
public int ID {get; set;}
public string Title {get; set;}

public Author FirstAuthor {get; set;}
public Author SecondAuthor {get; set;}
}

public class Author
{
public int ID {get; set;}
public string Name {get; set;}

public virtual ICollection<Book> BooksAsFirstAuthor {get; set;}
public virtual ICollection<Book> BooksAsSecondAuthor {get; set;}
}

این مثال ویژه‌ای است از کتابخانه‌ای که کتاب‌های آن، تنها توسط دو نویسنده نوشته‌ شده‌اند. اگر برنامه را بر اساس این دو کلاس اجرا کنیم، EF Code first قادر نخواهد بود تشخیص دهد، روابط کدام به کدام هستند و در جدول Books چهار کلید خارجی را ایجاد می‌کند. برای مدیریت این مساله و تعین ابتدا و انتهای روابط می‌توان از ویژگی InverseProperty کمک گرفت:

public class Book
{
public int ID {get; set;}
public string Title {get; set;}

[InverseProperty("BooksAsFirstAuthor")]
public Author FirstAuthor {get; set;}
[InverseProperty("BooksAsSecondAuthor")]
public Author SecondAuthor {get; set;}
}

public class Author
{
public int ID {get; set;}
public string Name {get; set;}

[InverseProperty("FirstAuthor")]
public virtual ICollection<Book> BooksAsFirstAuthor {get; set;}
[InverseProperty("SecondAuthor")]
public virtual ICollection<Book> BooksAsSecondAuthor {get; set;}
}

اینبار اگر برنامه را اجرا کنیم، بین این دو جدول تنها دو رابطه تشکیل خواهد شد و نه چهار رابطه؛ چون EF اکنون می‌داند که ابتدا و انتهای روابط کجا است. همچنین ذکر ویژگی InverseProperty در یک سر رابطه کفایت می‌کند و نیازی به ذکر آن در طرف دوم نیست.