Cookie - قسمت اول
مقدمه
GET /index.html HTTP/1.1 Host: www.dotnettips.info |
GET https://www.dntips.ir/ HTTP/1.1 Host: www.dotnettips.info Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 Cookie: BlogPost-1175=NLOpR%2fgHcUGqPL8dZYv3BDDqgd4xOtiiNxHIp1rD%2bAQ%3d; BlogComment-5002=WlS1iaIsiBnQN1UDD4p%2fHFvuoxC3b8ckbw78mAWXZOSWMpxPlLo65%2bA40%2flFVR54; ReaderEmail=DP%2bx4TEtMT2LyhNQ5QqsArka%2fWALP5LYX8Y
HTTP/1.1 200 OK Content-type: text/html Set-Cookie: cookieName=cookieValue Set-Cookie: cookieName2=cookieValue2; Expires=Thr, 10-Jun-2021 10:18:14 GMT ... |
HTTP/1.1 200 OK Date: Wed, 30 Jan 2013 20:25:15 GMT Server: Microsoft-IIS/6.0 X-Powered-By: ASP.NET X-Compressed-By: HttpCompress Set-Cookie: .ASPXROLES=NzZ9qIRpCWHofryYglbsQFv_SSGPn7ivo0zKFoS94gcODVdIKQAe_IBwuc-TQ-03jGeIkZabTuxA0A3k2-nChy7iAWw9rPMYXSkqzMkizRFkDC0k3gQTkdLqLmmeIfnL9UjfMNWO8iVkYQrSv24ecbpFDSQCH827V2kEj8k2oCm_5sKRSmFpifh4N7kinEi0vomG1vW4Rbg9JWMhCgcvndvsFsXxpj-NiEikC1RqHpiLArIyalEMEN-cIuVtRe7uoo938u9l-7OXb8yzXucVl4bdqPy2DXM3ddWzb3OH1jSFM6gxwJ8qRZDlSGmEEbhji7rA-efI4aYGTKx6heWfUsY6E2k73jJLbuZ3RB4oNwRYmz8FRB0-vm1pO7rhF1JIoi1YB17ez-Ox5chNEFkPVREanHVU9DxboJ5dKgN-2B5udUFPunnshbN8EBhixbFQOpqRiiOK4uWWaWy3rVEJYpCCDBRctKCfEyYD1URFYeajB0AXmiMUTcGeuUtwb-XFjbQZnbylmMF3EJgG16bcc1IEkTAUv1JfKjaql0XGWJI1; path=/; HttpOnly Cache-Control: private Content-Type: text/html; charset=utf-8 Content-Length: 106727 <!DOCTYPE html> <html> ...
GET /index2.html HTTP/1.1 Host: www.example.org Cookie: cookieName=cookieValue; cookieName2=cookieValue2 Accept: */* |
آشنایی با Automapping در فریم ورک Fluent NHibernate
اگر قسمتهای قبل را دنبال کرده باشید، احتمالا به پروسه طولانی ساخت نگاشتها توجه کردهاید. با کمک فریم ورک Fluent NHibernate میتوان پروسه نگاشت domain model خود را به data model متناظر آن به صورت خودکار نیز انجام داد و قسمت عمدهای از کار به این صورت حذف خواهد شد. (این مورد یکی از تفاوتهای مهم NHibernate با نمونههای مشابهی است که مایکروسافت تا تاریخ نگارش این مقاله ارائه داده است. برای مثال در نگارشهای فعلی LINQ to SQL یا Entity framework ، اول دیتابیس مطرح است و بعد ساخت کد از روی آن، در حالیکه در اینجا ابتدا کد و طراحی سیستم مطرح است و بعد نگاشت آن به سیستم دادهای و دیتابیس)
امروز قصد داریم یک سیستم ساده ثبت خبر را از صفر با NHibernate پیاده سازی کنیم و همچنین مروری داشته باشیم بر قسمتهای قبلی.
مطابق کلاس دیاگرام فوق، این سیستم از سه کلاس خبر، کاربر ثبت کنندهی خبر و گروه خبری مربوطه تشکیل شده است.
ابتدا یک پروژه کنسول جدید را به نام NHSample2 آغاز کنید. سپس ارجاعاتی را به اسمبلیهای زیر به آن اضافه نمائید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHibernate.Linq.dll
و ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم
سپس پوشهای را به نام Domain به این پروژه اضافه نمائید (کلیک راست روی نام پروژه در VS.Net و سپس مراجعه به منوی Add->New folder). در این پوشه تعاریف موجودیتهای برنامه را قرار خواهیم داد. سه کلاس جدید Category ، User و News را در این پوشه ایجاد نمائید. محتویات این سه کلاس به شرح زیر هستند:
namespace NHSample2.Domain
{
public class User
{
public virtual int Id { get; set; }
public virtual string UserName { get; set; }
public virtual string Password { get; set; }
}
}
namespace NHSample2.Domain
{
public class Category
{
public virtual int Id { get; set; }
public virtual string CategoryName { get; set; }
}
}
using System;
namespace NHSample2.Domain
{
public class News
{
public virtual Guid Id { get; set; }
public virtual string Subject { get; set; }
public virtual string NewsText { get; set; }
public virtual DateTime DateEntered { get; set; }
public virtual Category Category { get; set; }
public virtual User User { get; set; }
}
}
اکنون کلاس جدید Config را به برنامه اضافه نمائید:
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
namespace NHSample2
{
class Config
{
public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly).Configure(cfg);
return cfg;
}
public static void GenerateDbScript(Configuration config, string filePath)
{
bool script = true;//فقط اسکریپت دیتابیس تولید گردد
bool export = false;//نیازی نیست بر روی دیتابیس هم اجرا شود
new SchemaExport(config).SetOutputFile(filePath).Create(script, export);
}
public static void BuildDbSchema(Configuration config)
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool drop = false;//آیا اطلاعات موجود دراپ شوند
new SchemaExport(config).Execute(script, export, drop);
}
public static void CreateSQL2008DbPlusScript(string connectionString, string filePath)
{
Configuration cfg =
GenerateMapping(
MsSqlConfiguration
.MsSql2008
.ConnectionString(connectionString)
.ShowSql()
);
GenerateDbScript(cfg, filePath);
BuildDbSchema(cfg);
}
public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer dbType)
{
return
Fluently.Configure().Database(dbType)
.Mappings(m => m.AutoMappings
.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly))
)
.BuildSessionFactory();
}
}
}
در متد GenerateMapping از قابلیت Automapping موجود در فریم ورک Fluent Nhibernate استفاده شده است (بدون نوشتن حتی یک سطر جهت تعریف این نگاشتها). این متد نوع دیتابیس مورد نظر را جهت ساخت تنظیمات خود دریافت میکند. سپس با کمک کلاس AutoPersistenceModel این فریم ورک، به صورت خودکار از اسمبلی برنامه نگاشتهای لازم را به کلاسهای موجود در پوشه Domain ما اضافه میکند (مرسوم است که این پوشه در یک پروژه Class library مجزا تعریف شود که در این برنامه جهت سهولت کار در خود برنامه قرار گرفته است). قسمت Where ذکر شده به این جهت معرفی گردیده است تا Fluent Nhibernate برای تمامی کلاسهای موجود در اسمبلی جاری، سعی در تعریف نگاشتهای لازم نکند. این نگاشتها تنها به کلاسهای موجود در پوشه دومین ما محدود شدهاند.
سه متد بعدی آن، جهت ایجاد اسکریپت دیتابیس از روی این نگاشتهای تعریف شده و سپس اجرای این اسکریپت بر روی دیتابیس جاری معرفی شده، تهیه شدهاند. برای مثال CreateSQL2008DbPlusScript یک مثال ساده از استفاده دو متد قبلی جهت ایجاد اسکریپت و دیتابیس متناظر اس کیوال سرور 2008 بر اساس نگاشتهای برنامه است.
با متد CreateSessionFactory در قسمتهای قبل آشنا شدهاید. تنها تفاوت آن در این قسمت، استفاده از کلاس AutoPersistenceModel جهت تولید خودکار نگاشتها است.
در ادامه دیتابیس متناظر با موجودیتهای برنامه را ایجاد خواهیم کرد:
using System;
namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
Config.CreateSQL2008DbPlusScript(
"Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true",
"db.sql");
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
پس از اجرای برنامه، ابتدا فایل اسکریپت دیتابیس به نام db.sql در پوشه اجرایی برنامه تشکیل خواهد شد و سپس این اسکریپت به صورت خودکار بر روی دیتابیس معرفی شده اجرا میگردد. دیتابیس دیاگرام حاصل را در شکل زیر میتوانید ملاحظه نمائید:
همچنین اسکریپت تولید شده آن، صرفنظر از عبارات drop اولیه، به صورت زیر است:
create table [Category] (
Id INT IDENTITY NOT NULL,
CategoryName NVARCHAR(255) null,
primary key (Id)
)
create table [User] (
Id INT IDENTITY NOT NULL,
UserName NVARCHAR(255) null,
Password NVARCHAR(255) null,
primary key (Id)
)
create table [News] (
Id UNIQUEIDENTIFIER not null,
Subject NVARCHAR(255) null,
NewsText NVARCHAR(255) null,
DateEntered DATETIME null,
Category_id INT null,
User_id INT null,
primary key (Id)
)
alter table [News]
add constraint FKE660F9E1C9CF79
foreign key (Category_id)
references [Category]
alter table [News]
add constraint FKE660F95C1A3C92
foreign key (User_id)
references [User]
اکنون یک سری گروه خبری، کاربر و خبر را به دیتابیس خواهیم افزود:
using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHSample2.Domain;
namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
//با توجه به کلیدهای خارجی تعریف شده ابتدا باید گروهها را اضافه کرد
Category ca = new Category() { CategoryName = "Sport" };
session.Save(ca);
Category ca2 = new Category() { CategoryName = "IT" };
session.Save(ca2);
Category ca3 = new Category() { CategoryName = "Business" };
session.Save(ca3);
//سپس یک کاربر را به دیتابیس اضافه میکنیم
User u = new User() { Password = "123$5@1", UserName = "VahidNasiri" };
session.Save(u);
//اکنون میتوان یک خبر جدید را ثبت کرد
News news = new News()
{
Category = ca,
User = u,
DateEntered = DateTime.Now,
Id = Guid.NewGuid(),
NewsText = "متن خبر جدید",
Subject = "عنوانی دلخواه"
};
session.Save(news);
transaction.Commit(); //پایان تراکنش
}
}
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
و یا میتوان از LINQ استفاده کرد:
برای مثال کاربر VahidNasiri تعریف شده را یافته، اطلاعات آنرا نمایش دهید؛ سپس نام او را به Vahid ویرایش کرده و دیتابیس را به روز کنید.
برای اینکه کوئریهای LINQ ما شبیه به LINQ to SQL شوند، کلاس NewsContext را به صورت ذیل تشکیل میدهیم. این کلاس از کلاس پایه NHibernateContext مشتق شده و سپس به ازای تمام موجودیتهای برنامه، یک متد از نوع IOrderedQueryable را تشکیل خواهیم داد.
using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample2.Domain;
namespace NHSample2
{
class NewsContext : NHibernateContext
{
public NewsContext(ISession session)
: base(session)
{ }
public IOrderedQueryable<News> News
{
get { return Session.Linq<News>(); }
}
public IOrderedQueryable<Category> Categories
{
get { return Session.Linq<Category>(); }
}
public IOrderedQueryable<User> Users
{
get { return Session.Linq<User>(); }
}
}
}
using System;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using System.Linq;
using NHSample2.Domain;
namespace NHSample2
{
class Program
{
static void Main(string[] args)
{
using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
MsSqlConfiguration
.MsSql2008
.ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
.ShowSql()
))
{
using (ISession session = sessionFactory.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
using (NewsContext db = new NewsContext(session))
{
var query = from x in db.Users
where x.UserName == "VahidNasiri"
select x;
//اگر چیزی یافت شد
if (query.Any())
{
User vahid = query.First();
//نمایش اطلاعات کاربر
Console.WriteLine("Id: {0}, UserName: {0}", vahid.Id, vahid.UserName);
//به روز رسانی نام کاربر
vahid.UserName = "Vahid";
session.Update(vahid);
transaction.Commit(); //پایان تراکنش
}
}
}
}
}
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
اگر به اسکریپت دیتابیس تولید شده دقت کرده باشید، عملیات AutoMapping یک سری پیش فرضهایی را اعمال کرده است. برای مثال فیلد Id را از نوع identity و به صورت کلید تعریف کرده، یا رشتهها را به صورت nvarchar با طول 255 ایجاد نموده است. امکان سفارشی سازی این موارد نیز وجود دارد.
مثال:
using FluentNHibernate.Conventions.Helpers;
public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());
new AutoPersistenceModel()
.Conventions.Add()
.Where(x => x.Namespace.EndsWith("Domain"))
.Conventions.Add(
PrimaryKey.Name.Is(x => "ID"),
DefaultLazy.Always(),
ForeignKey.EndsWith("ID"),
Table.Is(t => "tbl" + t.EntityType.Name)
)
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
.Configure(cfg);
return cfg;
}
تابع GenerateMapping معرفی شده را اینجا با قسمت Conventions.Add تکمیل کردهایم. به این صورت دقیقا مشخص شده است که فیلدهایی با نام ID باید primary key در نظر گرفته شوند، همواره lazy loading صورت گیرد و نام کلید خارجی به ID ختم شود. همچنین نام جداول با tbl شروع گردد.
روش دیگری نیز برای معرفی این قرار دادها و پیش فرضها وجود دارد. فرض کنید میخواهیم طول رشته پیش فرض را از 255 به 500 تغییر دهیم. برای اینکار باید اینترفیس IPropertyConvention را پیاده سازی کرد:
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
namespace NHSample2.Conventions
{
class MyStringLengthConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
instance.Length(500);
}
}
}
public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
{
var cfg = dbType.ConfigureProperties(new Configuration());
new AutoPersistenceModel()
.Conventions.Add()
.Where(x => x.Namespace.EndsWith("Domain"))
.Conventions.Add<MyStringLengthConvention>()
.AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
.Configure(cfg);
return cfg;
}
نکته:
اگر برای یافتن اطلاعات بیشتر در این مورد در وب جستجو کنید، اکثر مثالهایی را که مشاهده خواهید کرد بر اساس نگارش بتای fluent NHibernate هستند و هیچکدام با نگارش نهایی این فریم ورک کار نمیکنند. در نگارش رسمی نهایی ارائه شده، تغییرات بسیاری صورت گرفته که آنها را در این آدرس میتوان مشاهده کرد.
دریافت سورس برنامه قسمت ششم
ادامه دارد ...
برای افزودن یک ماژول به پروژه از طریق گریدل، به صورت زیر اقدام میکنیم:
هر ماژول شامل یک فایل به نام build.gradle است که تنظیمات سطح آن ماژول را به عهده دارد و پروژه نیز یک build.gradle دارد که تنظیمات آن در سطح پروژه صورت میگیرد. برای اقزودن سورس در سطح یک ماژول لازم است که تعدادی خط کد را که معرف و آدرس آن سورس را دارد، به فایل build.gradle اضافه کنیم. به عنوان مثال برای سورس Active Android را که یک ORM عالی در سطح اندروید به شمار میآید، به ماژولمان اضافه میکنیم:
dependencies { compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' }
سوال : اندروید استودیو، کتابخانههای اندرویدی را از کجا دانلود میکند؟
Apache Maven یک سیستم آزاد است که برای توزیع کتابخانهها مورد استفاده قرار میگیرد. سرورهای این سیستم شامل یک مخزن maven هستند که کتابخانهها در آن قرار میگیرند و شناسهی دسترسی به آن کتابخانه از طریق همان شناسهای است که شما در build.gradle تعریف میکنید. به طور عادی دو سرور استاندارد برای اینکار وجود دارند که یکی از آنها jcenter و دیگری mavenCnteral است. البته سرورهای دیگری نیز وجود دارند، یا اینکه حتی خودتان هم میتوانید میزبانی را به عهده بگیرید و یا بعضی از شرکتها برای خود مخزنی جداگانه دارند.
JCenter
این سرور که یک مخزن maven است، توسط Bintray میزبانی میشود که میتوانید آن را در این آدرس بیابید. برای اینکه شناسههای gradle مربوط به این سرور در اندروید استودیو دانلود شود، نیاز است خط زیر را به build.gradle سطح پروژه اضافه کنید:
allprojects { repositories { jcenter() } }
MavenCentral
این مخزن توسط Sonatype.org میزبانی میشود که کل مخزن آن را میتوانید در این آدرس بیابید. برای دسترسی به مخازن این سرور نیاز است خطوط زیر را به gradle سطح پروژه اضافه کنید:
allprojects { repositories { mavenCentral() } }
به عنوان مثال Twitter's Fabric.io خودش کتابخانهی خودش را میزبانی میکند و مخزن آن در این آدرس https://maven.fabric.io/public قرار گرفته است و برای افزودن این کتابخانه به پروژه نیاز است مسیر زیر طی شود:
//project build.gradle repositories { maven { url 'https://maven.fabric.io/public' } } //module build.gradle dependencies { compile 'com.crashlytics.sdk.android:crashlytics:2.2.4@aar' }
سوال : کدامیک از مخازن بالا را انتخاب کنیم؟
دلایل مهاجرت از mavencentral به jcenter:
- سیستم jcenter از طریق یک CDN عمل میکند که در این صورت میتواند تجربهی خوبی از سرعت بهتر را برای توسعه دهندگان به همراه داشته باشد.
- کتابخانههای jcenter بسیار بیشتر از mavencentral هستند؛ تا جایی که میتوان گفت اکثر کتابخانههایی که روی mavencenteral پیدا میشوند، روی jcenter هم هست و jcenter بزرگترین مخزن به شمار میآید.
- آپلود کتابخانه بر روی jcenter بسیار راحتتر است و نیاز به کار پیچیدهای ندارد.
بررسی قسمتهای یک شناسه Gradle
هر شناسه شامل سه قسمت میشود:
GROUP_ID:ARTIFACT_ID:VERSION
کتابخانههای زیر، از یک خانواده هستند که به راحتی میتوانید آنها را از هم تشخیص دهید:
dependencies { compile 'com.squareup:otto:1.3.7' compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.squareup.okhttp:okhttp:2.4.0' compile 'com.squareup.retrofit:retrofit:1.9.0' }
شناخت فایلهای AAR
همانطور که میدانید فرمت فایلهای بایت کدی جاوا JAR میباشد که هم توسط جاوا و هم اندروید پشتیبانی میشود. ولی در صورتیکه کلاس شما یک پروژهی اندرویدی باشد، نمیتوانید آن را در قالب یک فایل JAR منتشر کنید. چرا که که کلاس اندرویدی میتواند شامل فایل مانیفست، منابع و ... باشد که در فایل JAR جایی برای آنها مهیا نشده است. به همین علت فایلهای نوع AAR برای اینکار مهیا شدهاند که این فایل در واقع یک فایل زیپ است که محتویات مورد نظر داخل آن قرار گرفته است و یکی از آن فایل Classes.jar برای کدهاست و مابقی آن به شرح زیر است:
- /AndroidManifest.xml (الزامی) - /classes.jar (الزامی ) - /res/ (الزامی ) - /R.txt (الزامی ) - /assets/ (اختیاری) - /libs/*.jar (اختیاری ) - /jni/<abi>/*.so (اختیاری ) - /proguard.txt (اختیاری ) - /lint.jar (اختیاری )
در مقالهی بعدی کار را با jcenter آغاز میکنیم.
تعیین پیشوندی برای نام کلیهی جداول بانک اطلاعاتی
اگر نیاز باشد تا به تمامی جداول تهیه شده، بر اساس نام کلاسهای مدلهای برنامه، یک پیشوند tbl اضافه شود، میتوان با بازنویسی متد OnModelCreating کلاس Context برنامه شروع کرد:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { // TableNameConvention modelBuilder.Types() .Configure(entity => entity.ToTable("tbl" + entity.ClrType.Name)); base.OnModelCreating(modelBuilder); }
تعیین نام دیگری برای کلید اصلی کلیهی جداول برنامه
فرض کنید نیاز است کلیه PKها، با پیشوند نام جدول جاری در بانک اطلاعاتی تشکیل شوند. یعنی اگر نام PK مساوی Id است و نام جدول Menu، نام کلید اصلی نهایی تشکیل شده در بانک اطلاعاتی باید MenuId باشد و نه Id.
protected override void OnModelCreating(DbModelBuilder modelBuilder) { // PrimaryKeyNameConvention modelBuilder.Properties() .Where(p => p.Name == "Id") .Configure(p => p.IsKey().HasColumnName(p.ClrPropertyInfo.ReflectedType.Name + "Id")); base.OnModelCreating(modelBuilder); }
تعیین حداکثر طول کلیه فیلدهای رشتهای تمامی جداول بانک اطلاعاتی
اگر نیاز باشد تا پیش فرض MaxLength تمام خواص رشتهای را تغییر داد، میتوان از پیاده سازی اینترفیس جدید IStoreModelConvention کمک گرفت:
public class StringConventions : IStoreModelConvention<EdmProperty> { public void Apply(EdmProperty property, DbModel model) { if (property.PrimitiveType.PrimitiveTypeKind == PrimitiveTypeKind.String) { property.MaxLength = 450; } } }
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Add<StringConventions>(); base.OnModelCreating(modelBuilder); }
نظم بخشیدن به تعاریف قراردادهای پیش فرض
اگر علاقمند نیستید که کلاس Context برنامه را شلوغ کنید، میتوان با ارث بری از کلاس پایه Convention، قراردادهای جدید را تعریف و سپس توسط متد modelBuilder.Conventions.Add، کلاس نهایی تهیه شده را به برنامه معرفی کرد.
public class MyConventions : Convention { public MyConventions() { // PrimaryKeyNameConvention this.Properties() .Where(p => p.Name == "Id") .Configure(p => p.IsKey().HasColumnName(p.ClrPropertyInfo.ReflectedType.Name + "Id")); // TableNameConvention this.Types() .Configure(entity => entity.ToTable("tbl" + entity.ClrType.Name)); } }
مثالهای بیشتر
اگر به مستندات EF 6 مراجعه کنید، مثالهای بیشتری را در مورد بکارگیری اینترفیس IStoreModelConvention و یا بازنویسی قراردادهای موجود، خواهید یافت.
OpenCVSharp #1
پردازش تصاویر علمی است برای پیاده سازی الگوریتمهای مختلفی بر روی تصاویر دیجیتال؛ برای مثال تشخیص خودکار شمارهی پلاک خودروهای وارد شدهی به محدودهی طرح ترافیک، تا تشخیص چهرهی افراد، در گوشیهای همراه. پردازش تصاویر، در صنایع مختلف، علوم پزشکی و همچنین نظامی، کاربردهای بسیاری دارند.
برای انجام این کار، کتابخانههای بسیار زیادی طراحی شدهاند؛ اما در این بین OpenCV جایگاه خاصی دارد. این کتابخانهی بسیار مشهور سورس باز، جهت پردازش تصاویر در سیستم عاملهای مختلفی مانند Windows, Mac, Linux, Android و iOS بکار میرود.
محصور کنندههای OpenCV مخصوص دات نت
تا امروز محصور کنندههای زیادی جهت استفادهی از کتابخانهی OpenCV در دات نت طراحی شدهاند که تعدادی از مهمترینهای آنها به شرح زیر هستند:
الف) Emgu CV
این کتابخانه، یکی از مشهورترین محصور کنندههای OpenCV است و دارای مجوزی دوگانه میباشد. برای کارهای سورس باز، مجوز GPL دارد (یعنی باید کارتان را سورس باز کنید) و برای کارهای تجاری باید مجوز آنرا بخرید. البته باید توجه داشت که مجوز کتابخانهی اصلی OpenCV از نوع BSD است و این محدودیتها را ندارد.
ب) OpenCvSharp
کتابخانهی OpenCvSharp دارای مجوز BSD است (همانند کتابخانهی اصلی OpenCV) و محدودیتی برای استفاده ندارد. هر دو نوع مدل برنامه نویسی OpenCV را که شامل متدهای C و ++C آناست، پشتیبانی میکند و در طراحی آن سعی شدهاست که بیشترین نزدیکی به طراحی اصلی OpenCV وجود داشته باشد. همچنین این کتابخانه چندسکویی بوده و با Mono لینوکسی نیز سازگار است و از دات نت 2 به بعد را نیز پشتیبانی میکند. جامعهی کاربری آن فعال است و مدام به روز میشود.
ج) SharperCV
دیگر نگهداری نمیشود.
د) OpenCVDotNet
آخرین تاریخ به روز رسانی آن سال 2007 است.
ه) DirectCV
آخرین تاریخ به روز رسانی آن سال 2011 است.
در این بین یکی از بهترین انتخابها، کتابخانهی OpenCvSharp ژاپنی است. مجوز استفادهی از آن محدود نیست. به روز رسانی مرتب و منظمی دارد و API آن طوری طراحی شدهاست که به سادگی بتوانید مثالهای C و ++C کتابخانهی OpenCV را تبدیل به معادلهای #C کنید.
نصب OpenCvSharp
برای نصب کتابخانهی OpenCvSharp میتوان از بستههای نیوگت آن کمک گرفت. این کتابخانه به همراه دو بستهی نیوگت ارائه میشود.
اگر فرمان ذیل را صادر کنید
PM> Install-Package OpenCvSharp-AnyCPU
و اگر دستور ذیل را اجرا کنید:
PM> Install-Package OpenCvSharp-WithoutDll
روش توصیه شدهی در اینجا، همان نصب بستهی نیوگت OpenCvSharp-AnyCPU است. به این ترتیب نگارشهای X86 و X64 کتابخانهی OpenCV سازگار با OpenCvSharp را نیز دریافت خواهید کرد.
نکتهای در مورد ارائهی نهایی پروژههای مبتنی بر OpenCV
OpenCV یک کتابخانهی native ویندوز است و دات نتی نیست . بنابراین DLLهای آن باید بسته به معماری CPU جاری، انتخاب شوند. یعنی اگر برنامهی دات نتی خود را در حالت Any CPU کامپایل میکنید، این برنامه در یک سیستم 64 بیتی، 64 بیتی رفتار میکند و در یک سیستم 32 بیتی، 32 بیتی. بنابراین باید دقت داشت که اگر سیستم جاری 64 بیتی است و میخواهید از اسمبلیهای X86 مربوط به OpenCV استفاده کنید، برنامه با پیام استثنای یافت نشدن OpenCV و BadImageFormatException کرش خواهد کرد. بستهی نیوگت OpenCvSharp-AnyCPU شامل هر دو معماری X86 و X64 است و هر دو سری DLLهای OpenCV را به همراه دارد.
همچنین OpenCV تحت ویندوز، توسط کامپایلر ویژوال ++C، کامپایل شدهاست. به همین جهت در این حالت، علاوه بر نصب دات نت، نیاز است VC++ redistributable packages را نیز بر روی کامپیوتر کلاینت نصب کرد.
پس از نصب بستهی نیوگت OpenCvSharp-AnyCPU اگر به پوشهی bin برنامهی خود مراجعه کنید، پوشهی جدید dll را نیز میتوان مشاهده کرد. داخل این پوشه، دو پوشهی X86 و X64 وجود دارند که حاوی DLLهای اصلی OpenCV میباشند. در این پوشهها اگر برای مثال فایلی به نام msvcp120.dll را یافتید، یعنی این نگارش از OpenCV نیاز به بستههای مخصوص VC++ 12 دارد.
رعایت این دو نکته بسیار مهم است؛ در غیر اینصورت برنامهی شما آغاز نخواهد شد.
اولین برنامهی OpenCVSharp
پس از نصب بستهی نیوگت OpenCvSharp-AnyCPU، مقدمات نصب OpenCV به پایان میرسد. در ادامه یک برنامهی کنسول جدید را ایجاد کرده و کدهای ذیل را به آن اضافه کنید:
using OpenCvSharp; namespace OpenCVSharpSample01 { class Program { static void Main(string[] args) { var img = Cv.CreateImage(new CvSize(128, 128), BitDepth.U8, 1); for (var y = 0; y < img.Height; y++) { for (var x = 0; x < img.Width; x++) { Cv.Set2D(img, y, x, x + y); } } Cv.NamedWindow("window"); Cv.ShowImage("window", img); Cv.WaitKey(); Cv.DestroyWindow("window"); Cv.ReleaseImage(img); } } }
در این مثال یک تصویر 128*128 ایجاد شده و سپس با گرادیانی از رنگ خاکستری پر میشود. در ادامه یک پنجرهی native مخصوص OpenCV ایجاد شده و این تصویر در آن نمایش داده میشود.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
کلیدهای مربوط به Request
ضروری؟ | نام کلید | مقدار |
بله | "owin.RequestBody" | یک Stream همراه با request body. اگر body برای request وجود نداشته باشد، Stream.Null به عنوان placeholder قابل استفاده است. |
بله | "owin.RequestHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای درخواست. |
بله | "owin.RequestMethod" | رشتهایی حاوی نوع فعل متد HTTP مربوط به درخواست (مانند GET and POST ) |
بله | "owin.RequestPath" | path درخواست شده به صورت string |
بله | "owin.RequestPathBase" | قسمتی از path درخواست به صورت string |
بله | "owin.RequestProtocol" | نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 ) |
بله | "owin.RequestQueryString" | رشتهای حاوی query string ؛ بدون علامت ? (مانند foo=bar&baz=quux ) |
بله | "owin.RequestScheme" | رشتهایی حاوی URL scheme استفاده شده در درخواست (مانند HTTP or HTTPS ) |
ضروری؟ | نام کلید | مقدار |
بله | "owin.ResponseBody" | یک Stream جهت نوشتن response body در خروجی |
بله | "owin.ResponseHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای response |
خیر | "owin.ResponseStatusCode" | یک عدد صحیح حاوی کد وضعیت HTTP response ؛ حالت پیشفرض 200 است. |
خیر | "owin.ResponseReasonPhrase" | یک رشته حاوی reason phrase مربوط به status code ؛ اگر خالی باشد در نتیجه سرور بهتر است آن را مقداردهی کند. |
خیر | "owin.ResponseProtocol" | یک رشته حاوی نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 )؛ اگر خالی باشد؛ “owin.RequestProtocol” به عنوان مقدار پیشفرض در نظر گرفته خواهد شد. |
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net461" /> <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net461" /> <package id="Owin" version="1.0" targetFramework="net461" />
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { } } }
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { app.Use(async (ctx, next) => { await ctx.Response.WriteAsync("Hello"); }); } }
Func<IOwinContext, Func<Task>, Task> handler
app.Use(async (ctx, next) => { var response = ctx.Environment["owin.ResponseBody"] as Stream; using (var writer = new StreamWriter(response)) { await writer.WriteAsync("Hello"); } });
using System; using Microsoft.Owin.Hosting; namespace SimpleOwinConsoleApp { class Program { static void Main(string[] args) { using (WebApp.Start<Startup>("http://localhost:12345")) { Console.WriteLine("Listening to port 12345"); Console.WriteLine("Press Enter to end..."); Console.ReadLine(); } } } }
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace SimpleOwinCoreApp { public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinCoreApp.Middlewares { public class SimpleMiddleware { private readonly RequestDelegate _next; public SimpleMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext ctx) { // قبل از فراخوانی میانافزار بعدی await ctx.Response.WriteAsync("Hello DNT!"); await _next(ctx); // بعد از فراخوانی میانافزار بعدی } } }
app.UseMiddleware<SimpleMiddleware>();
"Microsoft.AspNetCore.Owin": "1.0.0"
app.UseOwin(pipeline => { pipeline(next => new MyKatanaBasedMiddleware(next).Invoke) });
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerMiddleware { private readonly RequestDelegate _next; private readonly IpBlockerOptions _options; public IpBlockerMiddleware(RequestDelegate next, IpBlockerOptions options) { _next = next; _options = options; } public async Task Invoke(HttpContext context) { var ipAddress = context.Request.Host.Host; if (IsBlockedIpAddress(ipAddress)) { context.Response.StatusCode = 403; await context.Response.WriteAsync("Forbidden : The server understood the request, but It is refusing to fulfill it."); return; } await _next.Invoke(context); } private bool IsBlockedIpAddress(string ipAddress) { return _options.Ips.Any(ip => ip == ipAddress); } } }
using System.Collections.Generic; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerOptions { public IpBlockerOptions() { Ips = new[] { "192.168.1.1" }; } public IList<string> Ips { get; set; } } }
using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; namespace SimpleOwinAspNetCore.Middleware { public static class IpBlockerExtensions { public static IApplicationBuilder UseIpBlocker(this IApplicationBuilder builder, IConfigurationRoot configuration, IpBlockerOptions options = null) { return builder.UseMiddleware<IpBlockerMiddleware>(options ?? new IpBlockerOptions { Ips = configuration.GetSection("block_list").GetChildren().Select(p => p.Value).ToArray() }); } } }
{ "block_list": [ "192.168.1.1", "localhost", "127.0.0.1", "172.16.132.151" ] }
public IConfigurationRoot Configuration { set; get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("blockedIps.json"); Configuration = builder.Build(); }
app.UseIpBlocker(Configuration);
صورت مساله
قصد داریم اطلاعاتی را با فرمت JSON، از یک API خارجی، توسط HttpClient دریافت و سپس آنرا به یک DTO فرضی، به نام GitHubRepositoryDto نگاشت کنیم.
راه حل 1
در این روش از وهله سازی مستقیم HttpClient به همراه استفادهی از یک عبارت using کمک گرفته شدهاست. همچنین چون عملیات async است، نتیجهی آنرا به کمک خاصیت Result دریافت کردهایم که پس از آن، کل اطلاعات دریافتی را به صورت یک رشته، در اختیار خواهیم داشت:
public class GitHubClient { public IReadOnlyCollection<GitHubRepositoryDto> GetRepositories() { using (var httpClient = new HttpClient{BaseAddress = new Uri(GitHubConstants.ApiBaseUrl)}) { var result = httpClient.GetStringAsync(GitHubConstants.RepositoriesPath).Result; return JsonConvert.DeserializeObject<List<GitHubRepositoryDto>>(result); } } }
- استفاده از خاصیت Result، هیچگاه ایدهی خوبی نبوده است و یک عملیات async را تبدیل به عملیاتی Blocking میکند که حتی میتواند سبب بروز dead-lock نیز شود.
- HttpClient نباید Dispose شود. علت آنرا در مطلب «روش استفادهی صحیح از HttpClient در برنامههای دات نت» مفصل بررسی کردهایم.
- دریافت کل response یک API به صورت یک رشتهی بزرگ، یک Large object heap را بهوجود میآورد که باز هم ایدهی خوبی نیست.
راه حل 2
اگر خاصیت Result راه حل 1 را حذف کنیم، به راه حل 2 خواهیم رسید:
public class GitHubClient : IGitHubClient { public async Task<IReadOnlyCollection<GitHubRepositoryDto>> GetRepositories() { using (var httpClient = new HttpClient { BaseAddress = new Uri(GitHubConstants.ApiBaseUrl) }) { var result = await httpClient.GetStringAsync(GitHubConstants.RepositoriesPath); return JsonConvert.DeserializeObject<List<GitHubRepositoryDto>>(result); } } }
- اینبار از دسترسی asynchronous واقعی استفاده شدهاست.
معایب:
- ایجاد و تخریب یک HttpClient جدید به ازای هر فراخوانی.
- دریافت و ذخیره سازی کل response به صورت یک رشته.
راه حل 3
در این نگارش، HttpClient از طریق وهله سازی در سازندهی کلاس دریافت شده و به این ترتیب امکان استفادهی مجدد را پیدا میکند:
public class GitHubClient : IGitHubClient { private readonly HttpClient _httpClient; public GitHubClient() { _httpClient = new HttpClient { BaseAddress = new Uri(GitHubConstants.ApiBaseUrl) }; } public async Task<IReadOnlyCollection<GitHubRepositoryDto>> GetRepositories() { var result = await _httpClient.GetStringAsync(GitHubConstants.RepositoriesPath).ConfigureAwait(false); return JsonConvert.DeserializeObject<List<GitHubRepositoryDto>>(result); } }
services.AddSingleton<GitHubClient>();
- دسترسی asynchronous واقعی به API مدنظر.
- استفادهی مجدد از HttpClient
معایب:
- دریافت و ذخیره سازی کل response به صورت یک رشته.
- چون طول عمر GitHubClient از نوع Singleton است و برای همیشه از یک وهلهی سراسری استفاده میکند، از تغییرات DNS آگاه نخواهد شد.
راه حل 4
تا اینجا همانطور که ملاحظه کردید، به سادگی میتوان HttpClient را به نحو نادرستی مورد استفاده قرار داد. ایجاد مجدد آن به علت عدم رها شدن بلافاصلهی سوکتهای لایهی زرین آن توسط سیستم عامل، مشکل حادی را به نام sockets exhaustion پدید میآورد. به همین جهت، این کلاس باید یکبار نمونه سازی شده و در طول عمر برنامه از همین تک وهلهی آن استفاده شود. یک روش اینکار تعریف آن به صورت اشیاء singleton و یا static است. مشکلی که این روش به همراه دارد، عدم باخبر شدن آن از تغییرات DNS است. برای رفع این مسایل، از NET Core 2.1. به بعد، خود مایکروسافت با ارائهی یک IHttpClientFactory، روش استانداری را برای مدیریت وهلههای HttpClient ارائه کردهاست:
public class GitHubClient : IGitHubClient { private readonly IHttpClientFactory _httpClientFactory; public GitHubClient(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); } public async Task<IReadOnlyCollection<GitHubRepositoryDto>> GetRepositories() { var httpClient = _httpClientFactory.CreateClient("GitHub"); var result = await httpClient.GetStringAsync(GitHubConstants.RepositoriesPath).ConfigureAwait(false); return JsonConvert.DeserializeObject<List<GitHubRepositoryDto>>(result); } }
services.AddHttpClient("GitHub", x => { x.BaseAddress = new Uri(GitHubConstants.ApiBaseUrl); }); services.AddSingleton<GitHubClient>();
مزیتها:
- استفادهی از یک IHttpClientFactory توکار
معایب:
- استفادهی یک از کلاینت نامدار، بجای یک کلاینت مشخص شدهی بر اساس نوع آن.
- دریافت و ذخیره سازی کل response به صورت یک رشته.
روش ثبت services.AddHttpClient را که در اینجا ملاحظه میکنید، یک روش ثبت نامدار است و بر اساس نام رشتهای GitHub کار میکند. همین نام در متد GetRepositories به صورت httpClientFactory.CreateClient("GitHub") برای دسترسی به یک HttpClient جدید استفاده شدهاست.
راه حل 5
در اینجا از یک کلاینت نوعدار، بجای یک کلاینت نامدار، استفاده شدهاست:
public class GitHubClient : IGitHubClient { private readonly HttpClient _httpClient; public GitHubClient(HttpClient httpClient) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); } public async Task<IReadOnlyCollection<GitHubRepositoryDto>> GetRepositories() { var result = await _httpClient.GetStringAsync(GitHubConstants.RepositoriesPath).ConfigureAwait(false); return JsonConvert.DeserializeObject<List<GitHubRepositoryDto>>(result); } }
services.AddHttpClient<GitHubClient>(x => { x.BaseAddress = new Uri(GitHubConstants.ApiBaseUrl); });
مزایا:
- استفاده از IHttpClientFactory
- استفاده از یک کلاینت نوعدار، بجای یک نمونهی نامدار
معایب:
- اینبار تمام استفاده کنندگان از IGitHubClient ما باید دارای طول عمر transient باشند (خصوصیت کلاینتهای نوعدار است)؛ برخلاف راه حلهای پیشین که میتوانستند singleton تعریف شوند (یا امکان فراخوانی IGitHubClient از سرویسهای singleton نیز وجود داشت).
- دریافت و ذخیره سازی کل response به صورت یک رشته.
راه حل 6
اگر در جائی نیاز به استفاده و تزریق یک کلاینت نوعدار، در یک سرویس با طول عمر singleton را داشتید، روش آن به صورت زیر است:
public class GitHubClientFactory { private readonly IServiceProvider _serviceProvider; public GitHubClientFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public GitHubClient Create() { return _serviceProvider.GetRequiredService<GitHubClient>(); } }
services.AddHttpClient<GitHubClient>(x => { x.BaseAddress = new Uri(GitHubConstants.ApiBaseUrl); }); services.AddSingleton<GitHubClientFactory>();
مزایا:
- استفاده از IHttpClientFactory
- استفاده از یک کلاینت نوعدار
- استفاده کنندهی از GitHubClientFactory، میتوانند طول عمر singleton نیز داشته باشد
معایب:
- دریافت و ذخیره سازی کل response به صورت یک رشته.
راه حل 7
از اینجا به بعد، هدف ما بهینه سازی عملیات است و رفع مشکل کار با یک رشتهی بزرگ. برای این منظور بجای متد GetStringAsync، از متد SendAsync که امکان streaming را فراهم میکند، استفاده خواهیم کرد. به این ترتیب، بجای ارسال یک رشتهی بزرگ به متد Deserialize، امکان دسترسی به استریم response را توسط آن میسر کردهایم.
public class GitHubClient : IGitHubClient { private readonly HttpClient _httpClient; private readonly JsonSerializer _jsonSerializer; public GitHubClient(HttpClient httpClient, JsonSerializer jsonSerializer) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); } public async Task<IReadOnlyCollection<GitHubRepositoryDto>> GetRepositories() { var request = CreateRequest(); var result = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead); using (var responseStream = await result.Content.ReadAsStreamAsync()) { using (var streamReader = new StreamReader(responseStream)) using (var jsonTextReader = new JsonTextReader(streamReader)) { return _jsonSerializer.Deserialize<List<GitHubRepositoryDto>>(jsonTextReader); } } } private static HttpRequestMessage CreateRequest() { return new HttpRequestMessage(HttpMethod.Get, GitHubConstants.RepositoriesPath); } }
services.AddHttpClient<GitHubClient>(x => { x.BaseAddress = new Uri(GitHubConstants.ApiBaseUrl); }); services.AddSingleton<GitHubClientFactory>(); services.AddSingleton<JsonSerializer>();
- کار با IHttpClientFactory
- استفاده از یک کلاینت نوعدار
- کار با استریم response
معایب:
- استفاده از ResponseContentRead
راه حل 8
در این روش بجای سطر ذیل در راه حل 7
var result = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead);
var result = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
مزایا:
- کار با IHttpClientFactory
- استفاده از یک کلاینت نوعدار
- کار با استریم response
- استفاده از ResponseHeadersRead
معایب:
- شاید بتوان از کتابخانهی دیگری برای json deserialization استفاده کرد؟
AttachDBFilename=|DataDirectory|\database.mdf
اگر به سورس EF مراجعه کنیم، متد DbProviderServices.ExpandDataDirectory پیاده سازی مرتبط را به همراه دارد:
// find the replacement path var rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory");
مقدار DataDirectory در برنامههای وب
در برنامههای ASP.NET مقدار DataDirectory یک AppDomain از پیش تعیین شدهاست و دقیقا به مسیر کامل پوشهی استاندارد App_Data ختم میشود.
مقدار DataDirectory در برنامههای دسکتاپ
در برنامههای غیر وب، مقدار DataDirectory یک AppDomain تعیین نشده و نال است. برای رفع این مشکل کافی است در آغاز برنامه، DataDirectory را برای مثال به نحو زیر مقدار دهی کرد:
AppDomain.CurrentDomain.SetData("DataDirectory", AppDomain.CurrentDomain.BaseDirectory);
چند نکتهی تکمیلی
با مطالعهی سورس EF میتوان دریافت که:
- پس از |DataDirectory| تنها یک \ باید قرار گیرد.
- اگر مسیر ذکر شده پس از |DataDirectory| یک مسیر نسبی مانند \.. باشد، مورد قبول واقع نشده و یک استثناء صادر میشود. جمع |DataDirectory| و مسیر پس از آن باید یک مسیر کامل را تشکیل دهند.