مطالب
String.format در جاوا اسکریپت
مقدمه 
با اینکه زبان برنامه نویسی جاوا اسکریپت زبانی بسیار قدرتمند و با امکانات زیاد است، اما فقدان برخی متدهای کمکی پرمصرف در آن در برخی موارد باعث دردسرهایی می‌شود. امکانی برای فرمت‌بندی رشته‌ها یکی از این نیازهای نسبتا پرکاربرد است.
متدی که در این مطلب قصد توضیح پیاده‌سازی آنرا داریم، String.format نام دارد که فرایندی مشابه متد متناظر در دات نت را انجام می‌دهد. هم‌چنین سعی شده است تا نحوه پیاده‌سازی این متد کمکی از ابتدایی‌ترین نمونه‌ها تا نسخه‌های پیشرفته‌تر برای درک بهتر مطلب نشان داده شود.
.
پیاده‌سازی متد String.format
1. در این پیاده‌سازی از اولین فرایندی که ممکن است به ذهن یک برنامه‌نویس خطور کند استفاده شده است. این پیاده‌سازی بسیار ساده به صورت زیر است:
String.format = function () {
  var s = arguments[0];
  for (var i = 0; i < arguments.length - 1; i++) {
    s = s.replace("{" + i + "}", arguments[i + 1]);
  }
  return s;
};

2. پیاده‌سازی مشابهی هم با استفاده از نوع دیگری از حلقه for که تقریبا! مشابه با حلقه foreach در #C است به صورت زیر می‌توان درنظر گرفت:
String.format = function () {
  var s = arguments[0];
  for (var arg in arguments) {
    var i = parseInt(arg);
    s = s.replace("{" + i + "}", arguments[i + 1]);
  }
  return s;
};
در این متدها ابتدا فرمت واردشده توسط کاربر از لیست آرگومان‌های متد خوانده شده و در متغیر s ذخیره می‌شود. سپس درون یک حلقه به ازای هر توکن موجود در رشته فرمت، یک عملیات replace با مقدار متناظر در لیست آرگومان‌های متد انجام می‌شود. نحوه استفاده از این متد نیز به صورت زیر است:
console.log(String.format("{0} is nice!", "donettips.info"));
هر دو متد خروجی یکسانی دارند، به صورت زیر:
donettips.info is nice!
تا اینجا به نظر می‌رسد که عملیات به‌درستی پیش می‌رود. اما اولین و بزرگ‌ترین مشکل در این دو متد نحوه کارکردن متد replace در جاوا اسکریپت است. این متد با این نحوه فراخوانی تنها اولین توکن موجود را یافته و عملیات جایگزینی را برای آن انجام می‌دهد. برای روشن‌تر شدن موضوع به مثال زیر توجه کنید:
console.log(String.format("{0} is {1} nice! {0} is {1} nice!", "donettips.info", "very"));
با اجرای این مثال نتیجه زیر حاصل می‌شود:
donettips.info is very nice! {0} is {1} nice!
همان‌طور که می‌بنید عملیات replace برای سایر توکن‌ها انجام نمی‌شود.

3. برای حل مشکل فوق می‌توان از روش ساده زیر استفاده کرد:
String.format = function () {
  var original = arguments[0],
      replaced;
  for (var i = 0; i < arguments.length - 1; i++) {
    replaced = '';
    while (replaced != original) {
      original = replaced || original;
      replaced = original.replace("{" + i + "}", arguments[i + 1]);
    }
  }
  return replaced;
};
در این روش عملیات replace تا زمانی‌که تغییری در رشته جاری ایجاد نشود ادامه می‌یابد. با استفاده از این متد، خروجی مثال قبل درست و به صورت زیر خواهد بود:
donettips.info is very nice! donettips.info is very nice!

4. راه حل دیگر استفاده از امکانات شی RegExp در دستور replace است. نکته مهم استفاده از modifier کلی یا global (که با حرف g مشخص می‌شود) در شی تولیدی از RegExp است (^ و ^ و ^، برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار می‌شود). برای استفاده از این شی متد ما به صورت زیر تغییر می‌کند:
String.format = function () {
  var s = arguments[0];
  for (var i = 0; i < arguments.length - 1; i++) {
    s = s.replace(new RegExp("\\{" + i + "\\}", "g"), arguments[i + 1]);
  }
  return s;
};
استفاده از این متد هم نتیجه درستی برای مثال آخر ارائه می‌دهد.

5. روش دیگری که کمی از دو متد قبلی سریع‌تر اجرا می‌شود (به دلیل استفاده از حلقه while) به صورت زیر است:
String.format = function () {
  var s = arguments[0],
      i = arguments.length - 1;
  while (i--) {
    s = s.replace(new RegExp('\\{' + i + '\\}', 'g'), arguments[i + 1]);
  }
  return s;
};
این متد نیز نتیجه مشابهی ارائه می‌کند. حال به مثال زیر توجه کنید:
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "{2}", "two"));
خروجی صحیح مثال فوق باید به صورت زیر باشد:
zero:0 {2}:1 two:2
درصورتی‌که رشته‌ای که دو متد از سه متد آخر (3 و 4) به عنوان خروجی ارائه می‌دهند به‌صورت زیر است:
zero:0 two:1 two:2
برای آخرین متد که ازحلقه while (درواقع با اندیس معکوس) استفاده می‌کند (5) مثالی که خطای مورد بحث را نشان می‌دهد به صورت زیر است:
console.log(String.format("{0}:0 {1}:1 {2}:2", "zero", "one", "{1}"));
که خروجی اشتباه زیر را برمی‌گرداند:
zero:0 one:1 one:2
درصورتی‌که باید مقدار زیر را برگشت دهد:
zero:0 one:1 {1}:2
دلیل رخدادن این خطا اجرای عملیات replace به صورت جداگانه و کامل برای هر توکن، از اول تا آخر برای رشته‌های replace شده جاری است که کار را خراب می‌کند.

6. برای حل مشکل بالا نیز می‌توان از یکی دیگر از امکانات دستور replace استفاده کرد که به صورت زیر است:
String.format = function () {
  var args = arguments;
  return args[0].replace(/{(\d+)}/g, function (match, number) { return args[parseInt(number) + 1]; });
};
در اینجا از قابلیت سفارشی‌سازی عملیات جایگزینی در دستور replace استفاده شده است. با استفاده از این ویژگی عملیات replace برای هر توکن جداگانه انجام می‌شود و بنابراین تغییرات اعمالی در حین عملیات تاثیر مستقیمی برای ادامه روند نخواهد گذاشت.
دقت کنید که برای بکاربردن RegExp درون دستور replace به جای تولید یک نمونه از شی RegExp می‌توان عبارت مربوطه را نیز مستقیما بکار برد. در اینجا از عبارتی کلی برای دریافت تمامی توکن‌های با فرمتی به صورت {عدد} استفاده شده است.
متد سفارشی مربوطه نیز شماره ردیف توکن یافته‌شده به همراه خود عبارت یافته‌شده را به عنوان آرگومان ورودی دریافت کرده و مقدار متناظر را از لیست آرگومان‌های متد اصلی پس از تبدیل شماره ردیف توکن به یک عدد، برگشت می‌دهد (در اینجا نیز برای جلوگیری از دورشدن از بحث اصلی، جستجو برای کسب اطلاعات بیشتر در این زمینه به خوانندگان واگذار می‌شود).
برای جلوگیری از تداخل بین آرگومان‌های متد اصلی و متد تهیه‌شده برای سفارشی‌سازی عملیات جایگزینی، در ایتدای متد اصلی، لیست آرگومان‌های آن درون متغیر جداگانه‌ای (args) ذخیره شده است.
با استفاده از این متد خروجی درست نشان داده می‌شود. حال مثال زیر را درنظر بگیرید:
console.log(String.format("{0} is {1} nice!", "donettips.info"));
خروجی این مثال به‌صورت زیر است:
donettips.info is undefined nice!
پیاده‌سازی زیر برای حل این مشکل استفاده می‌شود.

7. برای کنترل بیشتر و رفع خطاهای احتمالی در متد بالا، می‌توان ابتدا از وجود آرگومان مربوطه در متغیر args اطمینان حاصل کرد تا از جایگزینی مقدار undefined در رشته نهایی جلوگیری کرد. مانند نمونه زیر:
String.format = function () {
  var s = arguments[0],
      args = arguments;
  return s.replace(/{(\d+)}/g, function (match, number) {
    var i = parseInt(number);
    return typeof args[i + 1] != 'undefined' ? args[i + 1] : match;
  });
};
با استفاده از این متد جدید خروجی مثال‌های قبل درست خواهد بود.
در فرمت بندی رشته‌ها برای نمایش خود کاراکتر { یا } از تکرار آن‌ها (یعنی {{ یا }}) استفاده می‌شود. اما متد ما تا این لحظه این امکان را ندارد. برای مثال:
console.log(String.format("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}}  {{{{2}}}}   {2}", "zero", "{2}", "two"));
که خروجی زیر را ارائه می‌دهد:
zero:0 {2}:1 two:2, {zero} {{{2}}}  {{{two}}}   two
.
8. برای پیاده‌سازی امکان اشاره‌شده در بالا می‌توان از کد زیر استفاده کرد:
String.format = function () {
  var s = arguments[0],
      args = arguments;
  return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) {
    if (match == "{{") { return "{"; }
    if (match == "}}") { return "}"; }
    var i = parseInt(number);
    return typeof args[i + 1] != 'undefined'
                              ? args[i + 1]
                              : match;
  });
};
در اینجا با استفاده از یک عبارت RegExp پیچیده‌تر و کنترل تکرار کاراکترهای { و } در متد سفارشی جایگزینی در دستور replace، پیاده‌سازی اولیه این ویژگی ارائه شده است.
این متد خروجی صحیح زیر را برای مثال آخر ارائه می‌دهد:
zero:0 {2}:1 two:2, {0} {{2}}  {{2}}   two

پیاده‌سازی به‌صورت یک خاصیت prototype
تمامی متدهای نشان داده‌شده تا اینجا به‌صورت مستقیم از طریق String.format در دسترس خواهند بود (تعریفی شبیه به متدهای استاتیک در دات نت). درصورتی‌که بخواهیم از این متدها به صورت یک خاصیت prototype شی string استفاده کنیم (چیزی شبیه به متدهای instance در اشیای دات نت) می‌توانیم از تعریف زیر استفاده کنیم:
String.prototype.format = function () {
   ...
}
تنها فرق مهم این پیاده‌سازی این است که رشته مربوط به فرمت وارده در این متد از طریق شی this در دسترس است و بنابراین شماره اندیس آرگومان‌های متد یکی کمتر از متدهای قبلی است که باید مدنظر قرار گیرد. مثلا برای متد آخر خواهیم داشت:
String.prototype.format = function () {
  var s = this.toString(),
      args = arguments;
  return s.replace(/\{\{|\}\}|\{(\d+)\}/g, function (match, number) {
    if (match == "{{") { return "{"; }
    if (match == "}}") { return "}"; }
    return typeof args[number] != 'undefined'
                              ? args[number]
                              : match;
  });
};

نکته: در تمامی خواص prototype هر شی در جاوا اسکریپت، متغیر this از نوع object است. بنابراین برای جلوگیری از وقوع هر خطا بهتر است ابتدا آن‌را به نوع مناسب تبدیل کرد. مثل استفاده از متد toString در متد فوق که موجب تبدیل آن به رشته می‌شود.

ازآنجاکه نیاز به تغییر اندیس در متد سفارشی عملیات replace وجود ندارد، بنابراین خط مربوط به تبدیل آرگومان number به یک مقدار عددی (با دستور parseInt) حذف شده است و از این متغیر به صورت مستقیم استفاده شده است. در این حالت عملیات تبدیل توسط خود جاوا اسکریپت مدیریت می‌شود که کار را راحت‌تر می‌سازد.
بنابراین متد ما به صورت زیر قابل استفاده است:
console.log("{0}:0 {1}:1 {2}:2, {{0}} {{{1}}}  {{{{2}}}}   {2}".format("zero", "{2}", "two"));

پیاده‌سازی با استفاده از توکن‌های غیرعددی
برای استفاده از توکن‌های غیرعددی می‌توانیم به صورت زیر عمل کنیم:
String.format = function () {
  var s = arguments[0],
      args = arguments[1];
  for (var arg in args) {
    s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]);
  }
  return s;
};
برای حالت prototype نیز داریم:
String.prototype.format = function () {
  var s = this.toString(),
      args = arguments[0];
  for (var arg in args) {
    s = s.replace(new RegExp("{" + arg + "}", "g"), args[arg]);
  }
  return s;
};
با استفاده از این دو متد داریم:
console.log(String.format("{site} is {adj}! {site} is {adj}!", { site: "donettips.info", adj: "nice" }));
console.log("{site} is {adj}! {site} is {adj}!".format({ site: "donettips.info", adj: "nice" }));
.
تا اینجا متدهایی نسبتا کامل برای نیازهای عادی برنامه‌نویسی تهیه شده است. البته کار توسعه این متد برای پشتیبانی از امکانات پیشرفته‌تر فرمت‌بندی رشته‌ها می‌تواند ادامه پیدا کند.

کتابخانه‌های موجود
یکی از کامل‌ترین کتابخانه‌های کار با رشته‌ها همان کتابخانه معروف Microsoft Ajax Client Libray است که بیشتر امکانات موجود کار با رشته‌ها در دات نت را در خود دارد. صرفا جهت آشنایی، پیاده‌سازی متد String.format در این کتابخانه در زیر آورده شده است:
String.format = function String$format(format, args) {
  /// <summary locid="M:J#String.format" />
  /// <param name="format" type="String"></param>
  /// <param name="args" parameterArray="true" mayBeNull="true"></param>
  /// <returns type="String"></returns>
//  var e = Function._validateParams(arguments, [
//    { name: "format", type: String },
//    { name: "args", mayBeNull: true, parameterArray: true }
//  ]);
//  if (e) throw e;
  return String._toFormattedString(false, arguments);
};
String._toFormattedString = function String$_toFormattedString(useLocale, args) {
  var result = '';
  var format = args[0];
  for (var i = 0; ; ) {
    var open = format.indexOf('{', i);
    var close = format.indexOf('}', i);
    if ((open < 0) && (close < 0)) {
      result += format.slice(i);
      break;
    }
    if ((close > 0) && ((close < open) || (open < 0))) {
      if (format.charAt(close + 1) !== '}') {
        throw Error.argument('format', Sys.Res.stringFormatBraceMismatch);
      }
      result += format.slice(i, close + 1);
      i = close + 2;
      continue;
    }
    result += format.slice(i, open);
    i = open + 1;
    if (format.charAt(i) === '{') {
      result += '{';
      i++;
      continue;
    }
    if (close < 0) throw Error.argument('format', Sys.Res.stringFormatBraceMismatch);
    var brace = format.substring(i, close);
    var colonIndex = brace.indexOf(':');
    var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1;
    if (isNaN(argNumber)) throw Error.argument('format', Sys.Res.stringFormatInvalid);
    var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1);
    var arg = args[argNumber];
    if (typeof (arg) === "undefined" || arg === null) {
      arg = '';
    }
    if (arg.toFormattedString) {
      result += arg.toFormattedString(argFormat);
    }
    else if (useLocale && arg.localeFormat) {
      result += arg.localeFormat(argFormat);
    }
    else if (arg.format) {
      result += arg.format(argFormat);
    }
    else
      result += arg.toString();
    i = close + 1;
  }
  return result;
}
دقت کنید قسمت ابتدایی این متد که برای بررسی اعتبار آرگومان‌های ورودی است، برای سادگی عملیات کامنت شده است. همان‌طور که می‌بینید این متد پیاده‌سازی نسبتا مفصلی دارد و امکانات بیشتری نیز در اختیار برنامه نویسان قرار می‌دهد. البته سایر متدهای مربوطه بدلیل طولانی بودن در اینجا آورده نشده است. برای مثال امکانات پیشرفته‌تری مثل زیر با استفاده از این کتابخانه در دسترس هستند:
console.log(String.format("{0:n}, {0:c}, {0:p}, {0:d}", 100.0001));
// result:   100.00, ¤100.00, 10,000.01 %, 100.0001

console.log(String.format("{0:d}, {0:t}", new Date(2015, 1, 1, 10, 45)));
// result:   02/01/2015, 10:45
آخرین نسخه این کتابخانه از اینجا قابل دریافت است (این متدها درون فایل MicrosoftAjax.debug.js قرار دارند). این کتابخانه دیگر به این صورت و با این نام توسعه داده نمیشود و چند سالی است که تصمیم به توسعه ویژگی‌های جدید آن به صورت پلاگین‌های jQuery گرفته شده است.

کتابخانه دیگری که می‌توان برای عملیات فرمت‌بندی رشته‌ها در جاوا اسکریپت از آن استفاده کرد، کتابخانه معروف jQuery Validation است. این کتابخانه یک متد نسبتا خوب با نام format برای فرمت کردن رشته‌ها دارد. نحوه استفاده از این متد به صورت زیر است:
var template = jQuery.validator.format("{0} is not a valid value");
console.log(template("abc"));
// result: 'abc is not a valid value'

کتابخانه نسبتا کامل دیگری که وجود دارد، با عنوان Stringformat از اینجا قابل دریافت است. برای استفاده از این کتابخانه باید به صورت زیر عمل کرد:
String.format([full format string], [arguments...]);
// or:
[date|number].format([partial format string]);
همان‌طور که می‌بینید این کتابخانه امکانات کامل‌تری نیز دارد. مثال‌های مربوط به این کتابخانه به صورت زیر هستند که توانایی‌های نسبتا کامل آن‌را نشان می‌دهد:
// Object path
String.format("Welcome back, {username}!", 
{ id: 3, username: "JohnDoe" });
// Result: "Welcome back, JohnDoe!"

// Date/time formatting
String.format("The time is now {0:t}.", 
new Date(2009, 5, 1, 13, 22));
// Result: "The time is now 01:22 PM."

// Date/time formatting (without using a full format string)
var d = new Date();
d.format("hh:mm:ss tt");
// Result: "02:28:06 PM"

// Custom number format string
String.format("Please call me at {0:+##0 (0) 000-00 00}.", 4601111111);
// Result: "Please call me at +46 (0) 111-11 11."

// Another custom number format string
String.format("The last year result was {0:+$#,0.00;-$#,0.00;0}.", -5543.346);
// Result: "The last year result was -$5,543.35."

// Alignment
String.format("|{0,10:PI=0.00}|", Math.PI);
// Result: "|   PI=3.14|"

// Rounding
String.format("1/3 ~ {0:0.00}", 1/3);
// Result: "1/3 ~ 0.33"

// Boolean values
String.format("{0:true;;false}", 0);
// Result: "false"

// Explicitly specified localization
// (note that you have to include the .js file for used cultures)
msf.setCulture("en-US");
String.format("{0:#,0.0}", 3641.667);
// Result: "3,641.7"

msf.setCulture("sv-SE");
String.format("{0:#,0.0}", 3641.667);
// Result: "3 641,7"

یک کتابخانه دیگر نیز از این آدرس قابل دریافت است. این کتابخانه با عنوان String.format نام‌گذاری شده است. نحوه استفاده از این کتابخانه نیز به صورت زیر است:
//inline arguments
String.format("some string with {0} and {1} injected using argument {{number}}", 'first value', 'second value');
//returns: 'some string with first value and second value injected argument {number}'

//single array
String.format("some string with {0} and {1} injected using array {{number}}", [ 'first value', 'second value' ]);
//returns: 'some string with first value and second value injected using array {number}'

//single object
String.format("some string with {first} and {second} value injected using {{propertyName}}",{first:'first value',second:'second value'});
//returns: 'some string with first value and second value injected using {propertyName}'
کتابخانه نسبتا معروف و کامل sprintf نیز در اینجا وجود دارد. این کتابخانه امکانات بسیاری همچون متدهای متناظر در زبان C دارد.

منابع

مطالب
منابع مطالعاتی بیشتر در مورد iTextSharp

  • آشنایی با صفحه بندی در iTextSharp : [+]
  • تعریف هدر و فوتر: [+]
  • افزودن متن ساده در iTextSharp: [+]
  • کار با فونت‌های مختلف در iTextSharp: [+]
  • نحوه‌ی افزودن جدول در iTextSharp: [+]
  • ترسیم اشکال گرافیکی با iTextSharp: [+]
  • کار با تصاویر در iTextSharp: [+] و [+]
  • امکان تبدیل HTML به PDF در iTextSharp: [+]، [+]، [+] و [+]
  • نحوه‌ی تعریف لینک در iTextSharp: [+]
  • نحوه‌ی تعریف لیست در iTextSharp: [+]
  • افزودن نمودار به کمک کنترل‌های چارت مایکروسافت در iTextSharp: [+]
  • امکان تعریف بارکد در iTextSharp: [+]
  • یک سری مثال: [+]
  • یکی کردن چند فایل پی دی اف موجود با هم توسط iTextSharp: [+]

نظرات مطالب
بخش دوم - بررسی امکانات (کلاس ها و متدهای) کتابخانه Gridify
با استفاده از Gridify شما میتونید query خودتون رو تولید کنید ولی امکان ارسال نام جدول وجود ندارد. ,ولی حدودا 70 درصد از چیزی که نیاز دارید رو در اختیارتون میگذاره.
یک نکته که شاید کار رو براتون آسون‌تر کنه اینکه: کلاس `GridifyQuery` یک متد به نام `GetFilteringExpression` داره که query رو در اختیارتون قرار میده. فقط به دلیل اینکه generic هست باید در runtime ایجادش کنید. (همینطور کلاس QueryBuilder )

اگر بخوام یک مثال بزنم, فرض کنید شما قصد تولید چنین کدی در runtime برای  ("TableName= "Users ) دارید
var result = _db.Users.Where(filteringExpression).ToList();
با استفاده از Gridify میتونید filteringExpression رو تولید کنید. 
-----
راه حل دومی که بنظرم میرسه اینکه یک Dictionary بین نام جداول و Gridify.QueryBuilder تولید کنید و بسته به نام جدول درخواست شده توسط کاربر از QueryBuilder از پیش تعریف شده برای اعمال آن استفاده کنید.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
با سلام و خسته نباشید.
توی این پروژه ما یک جدول نقش داریم که تمامی نقش‌ها درون آن تعریف می‌شود و هر نقشی هم یک سری claim داریم که  برای آن مشخص می‌کنیم و آن نقش را به کاربر می‌دهیم. سوالی که من دارم اینه که من توی یک پروژه یک سری نقش هایی دارم مثل نماینده فروش ، مدیر داخلی و... حالا این نقش‌ها خودشون یه سری ویژگی اضافه هم دارند مثلا خود نماینده فروش میتونه یه سری نماینده فروش دیگه زیر گروهش داشته باشه با توجه به این نقش‌ها آیا بنظرتون این نقش‌ها هم مثل بقیه نقش‌ها باید داخل جدول Role قرار بگیره و تمام دسترسی‌ها رو طبق همون دسترسی  داینامیک بهش بدیم؟ و اگه اینجوری باشه تکلیف اون فیلد‌های اضافی چی میشه؟ یا حتی اگه یه نقشی بعدا اضافه بشه که همین شرایط نماینده فروش را داشته باشد. لطفا اگه ممکنه راهنماییم کنید.
ممنون
نظرات مطالب
EF Code First #2
با سلام خدمت جناب نصیری
بنده از روی مثاله شما جلو رفتم و الگوی خودم رو در آوردم به این ترتیب که :
یک کلاس پایه ابسترکت درست کردم که یکسری پراپرتی در آن تعریف نمودم( فرضا کلاس جانداران )
چند کلاس دیگر ایجاد کردم که از کلاس پایه ارث برده بودند و پراپرتی های خود را هم داشتند
زمانیکه برنامه را اجرا کردم و  دیتابیس از روی برنامه ایجاد شد ، تنها به ازای کلاس های فرزند جدول درست شده بود ولی تمامی پراپرتی های کلاس پایه در جداول ایجاد شده بود اما جدولی به اسم جاندار ( کلاس پایه ) ایجاد نشده بود .
سواله بنده اینه که آیا بایستی کاره خاصی کرد که یک جدول جداگانه برای کلاس پایه هم ایجاد شود ؟
من این کار رو توی مدل فرست بدون مشکل انجام میدادم و نتیجه کار هم خوب بود .
با تشکر از زحمات جنابعالی
مطالب
کوئری نویسی در EF Core - قسمت چهارم - اعمال تغییرات در داده‌ها
نوع دیگری از کوئری‌های پرکاربرد، کوئری‌های مرتبط با ثبت، حذف و ویرایش اطلاعات هستند که در این قسمت آن‌ها را بررسی می‌کنیم. البته این مثال‌ها از یکسری مثال کوئری‌های مرتبط با PostgreSQL، به EF-Core تبدیل و ترجمه شده‌اند. به همین جهت تطابق یک به یکی در اینجا وجود نداشته و روش شیءگرایی که ORMها برای کار با داده‌ها بکار می‌گیرند، الزاما کوئری‌های یکسانی را تولید نمی‌کنند؛ اما نتیجه‌ی نهایی آن‌ها یکی است.


مثال 1: افزودن ردیفی به یک جدول بانک اطلاعاتی

امکان و ویژگی جدیدی به نام SPA قرار است به مجموعه اضافه شود. اطلاعات آن که شامل موارد ذیل است، نیاز است به جدول facilities اضافه شود:
facid: 9, Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800.
اگر قرار باشد چنین کاری را توسط دستورات SQL انجام دهیم، عموما به یکی از دو روش زیر عمل می‌شود:
insert into facilities
    (facid, name, membercost, guestcost, initialoutlay, monthlymaintenance)
    values (9, 'Spa', 20, 30, 100000, 800);
-- OR
insert into facilities values (9, 'Spa', 20, 30, 100000, 800);
که معادل آن در EF-Core به صورت زیر است:
context.Facilities.Add(new Facility
                {
                    Name = "Spa",
                    MemberCost = 20,
                    GuestCost = 30,
                    InitialOutlay = 100000,
                    MonthlyMaintenance = 800
                });
context.SaveChanges();
ابتدا وهله‌ای از موجودیت Facility به DbSet مرتبط با آن اضافه می‌شود و در آخر SaveChanges فراخوانی خواهد شد تا کوئری متناظر با آن ساخته شده و به بانک اطلاعاتی اعمال شود.


مثال 2: افزودن چندین ردیف از اطلاعات به یک جدول بانک اطلاعاتی

همان مثال قبلی را درنظر بگیرید. اینبار می‌خواهیم دو ردیف را به آن اضافه کنیم:
facid: 9, Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800.
facid: 10, Name: 'Squash Court 2', membercost: 3.5, guestcost: 17.5, initialoutlay: 5000, monthlymaintenance: 80.
معادل کدهای SQL چنین عملی، می‌تواند کوئری زیر باشد:
insert into facilities
    (facid, name, membercost, guestcost, initialoutlay, monthlymaintenance)
    values
        (9, 'Spa', 20, 30, 100000, 800),
        (10, 'Squash Court 2', 3.5, 17.5, 5000, 80);
و روش انجام آن در EF-Core تفاوتی با مثال قبلی ندارد:
context.Facilities.Add(new Facility
                {
                    Name = "Spa",
                    MemberCost = 20,
                    GuestCost = 30,
                    InitialOutlay = 100000,
                    MonthlyMaintenance = 800
                });

context.Facilities.Add(new Facility
                {
                    Name = "Squash Court 2",
                    MemberCost = 3.5M,
                    GuestCost = 17.5M,
                    InitialOutlay = 5000,
                    MonthlyMaintenance = 80
                });
context.SaveChanges();
در اینجا می‌توان به هر تعدادی که نیاز است وهله‌های جدیدی از Facility را به context افزودن و سپس SaveChanges را در آخر کار فراخوانی کرد. اینکه EF-Core دستورات insert معادل را به یکباره و یا به صورت مجزایی اجرا می‌کند، به مفهومی به نام batching مرتبط است. اطلاعات بیشتر


مثال 3: افزودن اطلاعات محاسبه شده به یک جدول بانک اطلاعاتی

اطلاعات زیر را درنظر بگیرید:
Name: 'Spa', membercost: 20, guestcost: 30, initialoutlay: 100000, monthlymaintenance: 800.
در مثال اصلی عنوان شده که می‌خواهیم ID آن‌را یکی بیشتر از ردیف قبلی ثبت کنیم. در EF-Core و تنظیمات موجودیت‌هایی که داریم:
namespace EFCorePgExercises.Entities
{
    public class FacilityConfiguration : IEntityTypeConfiguration<Facility>
    {
        public void Configure(EntityTypeBuilder<Facility> builder)
        {
            builder.HasKey(facility => facility.FacId);
            builder.Property(facility => facility.FacId).IsRequired().UseIdentityColumn(seed: 0, increment: 1);
چون ستون ID به صورت خود افزایش یابنده معرفی شده‌است که از صفر شروع می‌شود و به صورت خودکار توسط بانک اطلاعاتی یکی یکی افزایش می‌یابد، نیازی به حل این مساله وجود ندارد. چون ID افزایش یابنده را خود بانک اطلاعاتی محاسبه می‌کند. همچنین به همین علت در مثال‌های قبلی نیز ID را به صورت مستقیمی مقدار دهی نکردیم. اگر نیاز به انجام چنین کاری وجود داشته باشد (ذکر صریح ID خاصی)، با توجه به طراحی بانک اطلاعاتی حاصل از این تنظیمات:
CREATE TABLE [dbo].[Facilities](
[FacId] [int] IDENTITY(0,1) NOT NULL,
--- ...
 CONSTRAINT [PK_Facilities] PRIMARY KEY CLUSTERED 
(
[FacId] ASC
);
باید مانند مثال ثبت اطلاعات اولیه‌ی در بانک اطلاعاتی در قسمت اول این سری، از روش SET IDENTITY_INSERT Facilities ON استفاده کرد تا بتوان مجوز ثبت دستی این ID کنترل شده‌ی توسط بانک اطلاعاتی را پیدا کرد.


مثال 4: به روز رسانی اطلاعاتی از پیش موجود

می‌خواهیم مقدار InitialOutlay دومین زمین تنیس را از 8000 موجود به 10000 تغییر دهیم. با توجه به اینکه ID این زمین شماره 1 است، در حالت متداول SQL نویسی، به کدهای زیر خواهیم رسید:
update facilities
    set initialoutlay = 10000
    where facid = 1;
که معادل EF-Core آن به صورت زیر است:
var facility1 = context.Facilities.Find(1);
facility1.InitialOutlay = 10000;
context.SaveChanges();
این دستورات کوئری مشابهی را تولید نمی‌کنند. ابتدا موجودیت متناظر با ID شماره‌ی 1 از بانک اطلاعاتی واکشی شده و سپس مقدار خاصیتی از آن تغییر کرده‌است. در آخر SaveChanges بر روی آن فراخوانی می‌شود.
EF-Core برای اینکه بتواند تغییرات اعمالی به یک شیء را محاسبه کند، نیاز دارد تا آن شیء را به نحوی در سیستم change tracking خودش موجود داشته باشد. هر نوع کوئری که در EF-Core نوشته می‌شود و به همراه متد AsNoTracking نیست، خروجی تک تک اشیاء حاصل از آن پیش از ارائه‌ی نهایی، وارد سیستم change tracking آن می‌شوند. یعنی اگر مقادیر خواص این اشیاء را تغییر داده و بر روی آن‌ها SaveChanges را فراخوانی کنیم، کوئری‌های متناظر با به روز رسانی تنها این خواص تغییر یافته به صورت خودکار محاسبه شده و به بانک اطلاعاتی اعمال می‌شوند.
فراخوانی متد AsNoTracking بر روی کوئری‌های EF-Core، تولید پروکسی‌های change tracking را غیرفعال می‌کند. یک چنین کوئری‌هایی صرفا کاربردهای گزارشگیری فقط خواندنی را دارند و نسبت به کوئری‌های معمولی، سریعتر و با مصرف حافظه‌ی کمتری هستند. بنابراین نتایج حاصل از کوئری‌های متداول EF-Core، به صورت پیش‌فرض (یعنی بدون داشتن متد AsNoTracking) هم خواندنی و هم نوشتنی با قابلیت اعمال به بانک اطلاعاتی هستند.


مثال 5: به روز رسانی چندین ردیف و چندین جدول در یک زمان

می‌خواهیم مقادیر MemberCost  و GuestCost دو زمین تنیس را به 6 و 30 تغییر دهیم. روش انجام اینکار با SQL نویسی معمولی به صورت زیر است:
update cd.facilities
    set
        membercost = 6,
        guestcost = 30
    where facid in (0,1);
اما همانطور که عنوان شد در EF-Core ابتدا باید اشیاء متناظر با این زمین‌های تنیس را در سیستم change tacking موجود داشت و سپس نسبت به ویرایش آن‌ها اقدام نمود. یکی از روش‌های وارد کردن اشیاء به سیستم change tacking، نوشتن کوئری‌های بدون متد AsNoTracking است و سپس به روز رسانی نتایج حاصل از آن‌ها که اکنون توسط پروکسی‌های change tracking محصور شده‌اند و در آخر فراخوانی SaveChanges بر روی context جاری:
int[] facIds = { 0, 1 };
var tennisCourts = context.Facilities.Where(x => facIds.Contains(x.FacId)).ToList();
foreach (var tennisCourt in tennisCourts)
{
     tennisCourt.MemberCost = 6;
     tennisCourt.GuestCost = 30;
}

context.SaveChanges();


مثال 6: به روز رسانی اطلاعات یک ردیف بر اساس اطلاعات ردیفی دیگر

می‌خواهیم هزینه‌ی دومین زمین تنیس را به نحوی ویرایش کنیم که 10 درصد بیشتر از هزینه‌ی اولین زمین تنیس باشد.
روش پیشنهادی انجام اینکار با SQL نویسی مستقیم به صورت زیر است:
update cd.facilities facs
    set
        membercost = (select membercost * 1.1 from cd.facilities where facid = 0),
        guestcost = (select guestcost * 1.1 from cd.facilities where facid = 0)
    where facs.facid = 1;
در EF-Core می‌توان اشیاء متناظر با این دو زمین تنیس را ابتدا واکشی کرد، سپس تغییر داد و در نهایت ذخیره کرد:
var fac0 = context.Facilities.Where(x => x.FacId == 0).First();
var fac1 = context.Facilities.Where(x => x.FacId == 1).First();
fac1.MemberCost = fac0.MemberCost * 1.1M;
fac1.GuestCost = fac0.GuestCost * 1.1M;

context.SaveChanges();


مثال 7: حذف تمام اطلاعات یک جدول

می‌خواهیم تمام اطلاعات جدول bookings را حذف کنیم.
روش انجام اینکار با SQL نویسی مستقیم به صورت زیر است:
delete from bookings
اما ... این تک کوئری، معادلی را در EF-Core استاندارد ندارد. چون EF-Core نیاز دارد مدام تمام اطلاعات ویرایشی/حذف و به روز رسانی را در context و سیستم change tracking خودش داشته باشد، ابتدا باید توسط یک کوئری لیست اشیاء مدنظر را تهیه کرد و سپس آن‌را به متد RemoveRange معرفی کرد تا حذف تک تک آن‌ها که شامل صدها کوئری خواهد شد، صورت گیرد:
context.Bookings.RemoveRange(context.Bookings.ToList());
context.SaveChanges();
این روش سریع نیست؛ اما کار می‌کند!
البته هستند کتابخانه‌های ثالثی (^ و ^) که انجام به روز رسانی دسته‌ای و یا حذف دسته‌ای از رکوردها را تنها با یک کوئری SQL میسر می‌کنند؛ اما ... هنوز جزئی از EF استاندارد نشده‌اند و مهم‌ترین مشکل احتمالی این روش‌ها، همگام نبودن context و سیستم change tacking، با نتیجه‌ی حاصل از به روز رسانی یکباره‌ی صدها ردیف است.



مثال 8: حذف یک کاربر از جدول کاربران

می‌خواهیم کاربر شماره‌ی 37 را حذف کنیم.
روش انجام اینکار با SQL نویسی به صورت زیر است:
delete from members where memid = 37;
و در EF-Core برای انجام اینکار می‌توان ابتدا شیء متناظر با کاربر 37 را از طریق یک کوئری به سیستم change tracking وارد کرد و سپس آن‌را حذف نمود:
var mem37 = context.Members.Where(x => x.MemId == 37).First();
context.Members.Remove(mem37);

context.SaveChanges();

یک نکته: امکان ساده‌تر حذف یک ردیف با داشتن ID آن

کوئری گرفتن از بانک اطلاعاتی، یک روش وارد کردن شیءای به context و سیستم change tacking آن است. در این حالت عموما فرض بر این است که ID شیء را نمی‌دانیم. اما اگر این ID مانند مثال جاری از پیش مشخص بود، نیازی نیست تا ابتدا از بانک اطلاعاتی کوئری گرفت و کل شیء را در حافظه وارد کرد. در این حالت خاص می‌توان با استفاده از روش زیر، این ID را وارد سیستم tracking کرد و سپس حالت آن‌را به Deleted تغییر داد و در آخر آن‌را ذخیره کرد:
var entry = context.Entry(new Member { MemId = 37 });
entry.State = EntityState.Deleted;
context.SaveChanges();
در کدهای فوق می‌توان سطر entry.State = EntityState.Deleted را با context.Remove(entry) نیز جایگزین کرد و هر دو به یک معنا هستند.
روش فوق چنین کوئری‌هایی را ایجاد می‌کند:
SET NOCOUNT ON;
DELETE FROM [Members]
WHERE [MemId] = @p0;
SELECT @@ROWCOUNT;


مثال 9: حذف بر اساس یک sub-query

می‌خواهیم تمام کاربرانی را که هیچگاه رزروی را انجام نداده‌اند، حذف کنیم.
این مورد نیز با SQL نویسی مستقیم نیز توسط یک کوئری دسته‌ای قابل انجام است:
delete from members where memid not in (select memid from cd.bookings);
اما همانطور که عنوان شد، EF-Core این نوع اعمال ویرایش دسته‌ای را در طی یک تک کوئری پشتیبانی نمی‌کند. به همین جهت ابتدا آن‌ها را توسط یک کوئری به context وارد کرده و سپس حذف می‌کنیم:
var mems = context.Members.Where(x =>
  !context.Bookings.Select(x => x.MemId).Contains(x.MemId)).ToList();
context.Members.RemoveRange(mems);

context.SaveChanges();


کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
مطالب
MongoDB #8
اجرای کوئری در سند MongoDB
متد ()find
برای اجرای یک کوئری نیاز دارید تا از متد ()find در MongoDB استفاه کنید.

گرامر:
گرامر پایه برای این متد به شکل زیر است:
>db.COLLECTION_NAME.find()
متد ()find تمام سندها را در یک حالت بدون ساختار نمایش می‌دهد.

متد ()Pretty
برای نمایش نتیجه، بصورت فرمت دهی شده و ساخت یافته می‌توانید از متد ()pretty استفاده کنید.
گرامر:
>db.mycol.find().pretty()
مثال:
>db.mycol.find().pretty()
{
   "_id": ObjectId(7df78ad8902c),
   "title": "MongoDB Overview", 
   "description": "MongoDB is no sql database",
   "by": "tutorials point",
   "url": "http://www.tutorialspoint.com",
   "tags": ["mongodb", "database", "NoSQL"],
   "likes": "100"
}
>
جدای از متد ()find، متد ()findOne نیز وجود دارد که فقط یک سند را برمی گرداند.

معادل‌های عبارت Where  در MongoDB
برای اجرای کوئری‌های بر اساس چندین شرط بر روی سندها می‌توانید از عملگرهای زیر استفاده کنید:
عملگر   گرامر  مثال  مشابه در پایگاه داده رابطه ای
Equality {<key>:<value>} ()db.mycol.find({"by":"tutorialspoint"}).pretty
' where by =  'tutorials point
 Less Than {<key>:{$lt:<value>}}  ()db.mycol.find({"likes":{$lt:50}}).pretty where likes < 50
Less Than  Equals
 {<key>:{$lte:<value>}} ()db.mycol.find({"likes":{$lte:50}}).pretty
where likes <= 50
 Greater Than  {<key>:{$gt:<value>}} ()db.mycol.find({"likes":{$gt:50}}).pretty
where likes > 50
Greater Than  Equals
 {<key>:{$gte:<value>}} ()db.mycol.find({"likes":{$gte:50}}).pretty
where likes >= 5 0
 Not Equals  {<key>:{$ne:<value>}} ()db.mycol.find({"likes":{$ne:50}}).pretty
where likes != 50

عبارت And در MongoDB
گرامر:
اگر چندین کلید را به متد ()find پاس دهید و آنها را با کاما (,) از هم جداکنید، MongoDB با آن‌ها مانند عبارت And رفتار می‌کند. گرامر پایه عبارت AND در جدول زیر نشان داده شده است:
>db.mycol.find({key1:value1, key2:value2}).pretty()
مثال:
در ادامه یک مثال آمده است که همه‌ی دوره‌های آموزشی که توسط 'tutorials point' ارائه شده‌اند و عنوان آنها 'MongoDB Overview’ است را نشان می‌دهد:
>db.mycol.find({"by":"tutorials point","title": "MongoDB Overview"}).pretty()
{
   "_id": ObjectId(7df78ad8902c),
   "title": "MongoDB Overview", 
   "description": "MongoDB is no sql database",
   "by": "tutorials point",
   "url": "http://www.tutorialspoint.com",
   "tags": ["mongodb", "database", "NoSQL"],
   "likes": "100"
}
>
برای مثال بالا، معادل عبارت Where آن ' where by='tutorials point' AND title='MongoDB Overview' خواهد بود. شما می‌توانید چندین جفت کلید-مقدار به عبارت find پاس دهید.

عبارت OR در MongoDB
گرامر:
برای اجرای کوئری‌های مبتنی بر عبارت OR روی سند نیاز دارید تا از کلمه‌ی کلیدی or$ استفاده کنید. گرامر پایه عبارت OR در زیر نشان داده شده است:
>db.mycol.find(
   {
      $or: [
     {key1: value1}, {key2:value2}
      ]
   }
).pretty()
مثال
در ادامه یک مثال آمده است که همه‌ی دوره‌های آموزشی را که توسط 'tutorials point' ارائه شده‌اند یا عنوان آنها 'MongoDB Overview’ است، نشان می‌دهد:
>db.mycol.find({$or:[{"by":"tutorials point"},{"title": "MongoDB Overview"}]}).pretty()
{
   "_id": ObjectId(7df78ad8902c),
   "title": "MongoDB Overview", 
   "description": "MongoDB is no sql database",
   "by": "tutorials point",
   "url": "http://www.tutorialspoint.com",
   "tags": ["mongodb", "database", "NoSQL"],
   "likes": "100"
}
>

استفاده از عبارات AND و OR باهم
مثال
در ادامه یک مثال آمده است که سندهایی را که مقدار فیلد likes آنها بیشتر از 100 و عنوان آنها برابر 'MongoDB Overview’ یا توسط 'tutorials point' ارائه شده‌اند، نشان خواهد داد. معادل عبارت Where آن برابر 'where likes>10 AND (by = 'tutorials point' OR title= 'MongoDB Overview’)’ است.
>db.mycol.find("likes": {$gt:10}, $or: [{"by": "tutorials point"}, {"title": "MongoDB Overview"}] }).pretty()
{
   "_id": ObjectId(7df78ad8902c),
   "title": "MongoDB Overview", 
   "description": "MongoDB is no sql database",
   "by": "tutorials point",
   "url": "http://www.tutorialspoint.com",
   "tags": ["mongodb", "database", "NoSQL"],
   "likes": "100"
}
>

نظرات مطالب
بررسی تفصیلی رابطه Many-to-Many در EF Code first
فرض کنید من لازم دارم در یک برنامه مدیریت آزمایشگاه؛ کاربر بتونه ساختار تستها رو تعریف کنه و بگه اون تست چه پارامترهایی داره و مقادیر مجاز هر پارامتر چی‌ها هست و بعدش بر اساس این ساختار تعریف شده، مقادیر مشاهده شده در آزمایش واقعی رو برای این تست ثبت کنه بنابر این من این کلاسها رو طراحی کرده ام:
تا اینجا ساختار تست که شامل چند پارامتر با مقادیر مجازشون هست رو میشه با این کلاسها تعریف کرد.
حالا فرض کنید برای یک تست تعریف شده با این ساختار میخواهیم مقادیر مشاهده شده واقعی رو ثبت کنیم(ActualTest,ActualParameterValue). وقتی بخوام رنگ خون رو از لیست رنگهای مجاز تعریف شده برای این پارامتر انتخاب کنم، قاعدتا بایستی id مربوط به جدول واسط(TestAcceptableValueInTestParameter) به عنوان id رنگ انتخاب شده در جدول مقدار واقعی پارامتر ثبت بشه. به عبارت دیگه من با id کلاسی کار دارم که اگر با روش many-to-many  ذکر شده برای EF کار کنم، همچین کلاسی جزو مدلهای من نیست. آیا EF راهکاری برای این موارد داره؟
ممنون.
 
نظرات مطالب
نحوه نمایش فیلدهای تصویری و همچنین بارگذاری تصاویر از مسیری مشخص در PdfReport
- می‌تونید یک قالب ستون سفارشی تعریف کنید. چیزی شبیه به همین مثال است. در یک سلول هم تصویر نمایش داده شده و هم یک رشته.
- برای استفاده از مقدار یک فیلد در دو ستون می‌تونید از فیلدهای محاسباتی استفاده کنید.