واسه نرم افزارهایی که یه DB کوچیک با سرعت خوب میخوان SQLite بهترین انتخابه.
نظرات مطالب
جزئیات برنامه نویسی افزونه فارسی به پارسی
سلام،لطفا در مورد نحوه کار با تاریخ در دیتا بیس sqlite کمی توضیح و راهنمایی بفرمایید.
با تشکر
با تشکر
با آمدن ORMها به دنیای برنامه نویسی، کار برنامه نویسی نسبت به قبل سادهتر و راحتتر شد. عدم استفاده کوئریهای دستی، پشتیبانی از چند دیتابیس و از همه مهمتر و اصلیترین هدف این ابزار "تنها درگیری با اشیا و مدل شیء گرایی" کار را پیش از پیش آسانتر نمود.
در این بین به راحتی میتوان چندین نمونه از این ORMها را نام برد مثل IBatis , Hibernate ,Nhibernate و EF که از معروفترین آنها هستند.
من در حال حاضر قصد شروع یک پروژه اندرویدی را دارم و دوست دارم بجای استفادهی از Sqlitehelper، از یک ORM مناسب بهره ببرم که چند سوال برای من پیش میآید. آیا ORM ای برای آن تهیه شده است؟ اگر آری چندتا و کدامیک از آنها بهتر هستند؟ شاید در اولین مورد کتابخانهی Hibernate جاوا را نام ببرید؛ ولی توجه به این نکته ضروری است که ما در مورد پلتفرم موبایل و محدودیتهای آن صحبت میکنیم. یک کتابخانه همانند Hibernate مطمئنا برای یک برنامه اندروید چه از نظر حجم نهایی برنامه و چه از نظر حجم بزرگش در اجرا، مشکل زا خواهد بود و وجود وابستگیهای متعدد و دارا بودن بسیاری از قابلیتهایی که اصلا در بانکهای اطلاعاتی موبایل قابل اجرا نیست، باعث میشود این فریمورک انتخاب خوبی برای یک برنامه اندروید نباشد.
معیارهای انتخاب یک فریم ورک مناسب برای موبایل:
OrmLight
این فریمورک مختص اندروید طراحی نشده ولی سبک بودن آن موجب شدهاست که بسیاری از برنامه نویسان از آن در برنامههای اندرویدی استفاده کنند. این فریم ورک جهت اتصالات JDBC و Spring و اندروید طراحی شده است.
نحوه معرفی جداول در این فریمورک به صورت زیر است:
در این بین به راحتی میتوان چندین نمونه از این ORMها را نام برد مثل IBatis , Hibernate ,Nhibernate و EF که از معروفترین آنها هستند.
من در حال حاضر قصد شروع یک پروژه اندرویدی را دارم و دوست دارم بجای استفادهی از Sqlitehelper، از یک ORM مناسب بهره ببرم که چند سوال برای من پیش میآید. آیا ORM ای برای آن تهیه شده است؟ اگر آری چندتا و کدامیک از آنها بهتر هستند؟ شاید در اولین مورد کتابخانهی Hibernate جاوا را نام ببرید؛ ولی توجه به این نکته ضروری است که ما در مورد پلتفرم موبایل و محدودیتهای آن صحبت میکنیم. یک کتابخانه همانند Hibernate مطمئنا برای یک برنامه اندروید چه از نظر حجم نهایی برنامه و چه از نظر حجم بزرگش در اجرا، مشکل زا خواهد بود و وجود وابستگیهای متعدد و دارا بودن بسیاری از قابلیتهایی که اصلا در بانکهای اطلاعاتی موبایل قابل اجرا نیست، باعث میشود این فریمورک انتخاب خوبی برای یک برنامه اندروید نباشد.
معیارهای انتخاب یک فریم ورک مناسب برای موبایل:
- سبک بودن: مهمترین مورد سبک بودن آن است؛ چه از لحاظ اجرای برنامه و چه از لحاظ حجم نهایی برنامه
- سریع بودن: مطمئنا ORMهای طراحی شدهی موجود، از سرعت خیلی بدی برخوردار نخواهند بود؛ اگر سر زبان هم افتاده باشند. ولی باز هم انتخاب سریع بودن یک ORM، مورد علاقهی بسیاری از ماهاست.
- یادگیری آسان و کانفیگ راحت تر.
OrmLight
این فریمورک مختص اندروید طراحی نشده ولی سبک بودن آن موجب شدهاست که بسیاری از برنامه نویسان از آن در برنامههای اندرویدی استفاده کنند. این فریم ورک جهت اتصالات JDBC و Spring و اندروید طراحی شده است.
نحوه معرفی جداول در این فریمورک به صورت زیر است:
@DatabaseTable(tableName = "users") public class User { @DatabaseField(id = true) private String username; @DatabaseField private String password; public User() { // ORMLite needs a no-arg constructor } public User(String username, String password) { this.username = username; this.password = password; } // Implementing getter and setter methods public String getUserame() { return this.username; } public void setName(String username) { this.username = username; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } }
سورس این فریمورک را میتوان در گیت هاب یافت و مستندات آن در این آدرس قرار دارند.
SugarORM
این فریمورک مختص اندروید طراحی شده است. یادگیری آن بسیار آسان است و به راحتی به یاد میماند. همچنین جداول مورد نیاز را به طور خودکار خواهد ساخت. روابط یک به یک و یک به چند را پشتیبانی میکند و عملیات CURD را با سه متد Save,Delete و Find که البته FindById هم جزء آن است، پیاده سازی میکند.
برای استفاده از این فریمورک نیاز است ابتدا متادیتاهای زیر را به فایل manifest اضافه کنید:
SugarORM
این فریمورک مختص اندروید طراحی شده است. یادگیری آن بسیار آسان است و به راحتی به یاد میماند. همچنین جداول مورد نیاز را به طور خودکار خواهد ساخت. روابط یک به یک و یک به چند را پشتیبانی میکند و عملیات CURD را با سه متد Save,Delete و Find که البته FindById هم جزء آن است، پیاده سازی میکند.
برای استفاده از این فریمورک نیاز است ابتدا متادیتاهای زیر را به فایل manifest اضافه کنید:
<meta-data android:name="DATABASE" android:value="my_database.db" /> <meta-data android:name="VERSION" android:value="1" /> <meta-data android:name="QUERY_LOG" android:value="true" /> <meta-data android:name="DOMAIN_PACKAGE_NAME" android:value="com.my-domain" />
برای تبدیل یک کلاس به جدول هم از کلاس این فریم ورک ارث بری میکنیم:
public class User extends SugarRecord<User> { String username; String password; int age; @Ignore String bio; //this will be ignored by SugarORM public User() { } public User(String username, String password,int age){ this.username = username; this.password = password; this.age = age; } }
باقی عملیات آن از قبیل اضافه کردن یک رکورد جدید یا حذف رکورد(ها) به صورت زیر است:
User johndoe = new User(getContext(),"john.doe","secret",19); johndoe.save(); //ذخیره کاربر جدید در دیتابیس //حذف تمامی کاربرانی که سنشان 19 سال است List<User> nineteens = User.find(User.class,"age = ?",new int[]{19}); foreach(user in nineteens) { user.delete(); }
GreenDAO
موقعیکه بحث کارآیی و سرعت پیش میآید نام GreenDAO هست که میدرخشد. طبق گفتهی سایت رسمی آن این فریمورک میتواند در ثانیه چند هزار موجودیت را اضافه و به روزرسانی و بارگیری نماید. این لیست حاوی برنامههایی است که از این فریمورک استفاده میکنند. جدول زیر مقایسهای است بین این کتابخانه و OrmLight که نشان میدهد 4.5 برابر سریعتر از OrmLight عمل میکند.
موقعیکه بحث کارآیی و سرعت پیش میآید نام GreenDAO هست که میدرخشد. طبق گفتهی سایت رسمی آن این فریمورک میتواند در ثانیه چند هزار موجودیت را اضافه و به روزرسانی و بارگیری نماید. این لیست حاوی برنامههایی است که از این فریمورک استفاده میکنند. جدول زیر مقایسهای است بین این کتابخانه و OrmLight که نشان میدهد 4.5 برابر سریعتر از OrmLight عمل میکند.
غیر از اینها در زمینهی حجم هم حرفهایی برای گفتن دارد. حجم این کتابخانه کمتر از 100 کیلوبایت است که در اندازهی APK اثر چندانی نخواهد داشت.
آموزش راه اندازی آن در اندروید استادیو، سورس آن در گیت هاب و مستندات رسمی آن.
Active Android
این کتابخانه از دو طریق فایل JAR و به شیوه maven قابل استفاده است که میتوانید روش استفادهی از آن را در این لینک ببینید و سورس اصلی آن هم در این آدرس قرار دارد. بعد از اینکه کتابخانه را به پروژه اضافه کردید، دو متادیتای زیر را که به ترتیب نام دیتابیس و ورژن آن هستند، به manifest اضافه کنید:
آموزش راه اندازی آن در اندروید استادیو، سورس آن در گیت هاب و مستندات رسمی آن.
Active Android
این کتابخانه از دو طریق فایل JAR و به شیوه maven قابل استفاده است که میتوانید روش استفادهی از آن را در این لینک ببینید و سورس اصلی آن هم در این آدرس قرار دارد. بعد از اینکه کتابخانه را به پروژه اضافه کردید، دو متادیتای زیر را که به ترتیب نام دیتابیس و ورژن آن هستند، به manifest اضافه کنید:
<meta-data android:name="AA_DB_NAME" android:value="my_database.db" /> <meta-data android:name="AA_DB_VERSION" android:value="1" />
public class MyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActiveAndroid.initialize(this); //ادامه برنامه } }
@Table(name = "User") public class User extends Model { @Column(name = "username") public String username; @Column(name = "password") public String password; public User() { super(); } public User(String username,String password) { super(); this.username = username; this.password = password; } }
ORMDroid
از آن دست کتابخانههایی است که سادگی و کم حجم بودن شعار آنان است و سعی دارند تا حد ممکن همه چیز را خودکار کرده و کمترین کانفیگ را نیاز داشته باشد. حجم فعلی آن حدود 20 کیلوبایت بوده و نمیخواهند از 30 کیلوبایت تجاوز کند.
برای استفادهی از آن ابتدا دو خط زیر را جهت معرفی تنظیمات به manifest اضافه کنید:
<meta-data android:name="ormdroid.database.name" android:value="your_database_name" /> <meta-data android:name="ormdroid.database.visibility" android:value="PRIVATE||WORLD_READABLE||WORLD_WRITEABLE" />
ORMDroidApplication.initialize(someContext);
public class Person extends Entity { public int id; public String name; public String telephone; } //==================== Person p = Entity.query(Person.class).where("id=1").execute(); p.telephone = "555-1234"; p.save(); // یا Person person = Entity.query(Person.class).where(eql("id", id)).execute(); p.telephone = "555-1234"; p.save();
کد بالا دقیقا یادآوری به EF هست ولی حیف که از Linq پشتیبانی نمیشود.
سورس آن در گیت هاب
در اینجا سعی کردیم تعدادی از کتابخانههای محبوب را معرفی کنیم ولی تعداد آن به همین جا ختم نمیشود. ORMهای دیگری نظیر AndRom و سایر ORM هایی که در این لیست معرفی شده اند وجود دارند.
نکته نهایی اینکه خوب میشود دوستانی که از این ORMهای مختص اندروید استفاده کرده اند؛ نظراتشان را در مورد آنها بیان کنند و مزایا و معایب آنها را بیان کنند.
سورس آن در گیت هاب
در اینجا سعی کردیم تعدادی از کتابخانههای محبوب را معرفی کنیم ولی تعداد آن به همین جا ختم نمیشود. ORMهای دیگری نظیر AndRom و سایر ORM هایی که در این لیست معرفی شده اند وجود دارند.
نکته نهایی اینکه خوب میشود دوستانی که از این ORMهای مختص اندروید استفاده کرده اند؛ نظراتشان را در مورد آنها بیان کنند و مزایا و معایب آنها را بیان کنند.
چندین روش برای انجام مقایسه حساس به حروف کوچک و بزرگ (case sensitive) در SQL Server وجود دارد که در ادامه آنها را مرور خواهیم کرد:
ابتدا جدول موقتی زیر را جهت آزمایشات بعدی در نظر بگیرید
CREATE TABLE #tblTest
(
f1 NVARCHAR(50)
)
INSERT INTO #tblTest (f1) VALUES('Test1')
INSERT INTO #tblTest (f1) VALUES('TEST1')
الف) استفاده از collation صحیح
عموما هنگام نصب اس کیوال سرور از collation غیرحساس به کوچکی و بزرگی حروف استفاده میشود و این مورد سبب میشود که پیش فرض ایجاد دیتابیسها نیز به همین صورت باشد (هر چند کاملا قابل کنترل و تنظیم است). به صورت پویا میتوان این collation را در کوئریها نیز اعمال نمود. برای مثال:
SELECT f1 FROM #tblTest WHERE f1 COLLATE SQL_Latin1_General_CP1_CS_AS = 'Test1'
ب) استفاده از تابع BINARY_CHECKSUM اس کیوال سرور (نوعی الگوریتم ویژه، شبیه به امضای دیجیتال و هش کردن اطلاعات است)
SELECT f1 FROM #tblTest WHERE BINARY_CHECKSUM(f1) = BINARY_CHECKSUM('Test1')
SELECT f1 FROM #tblTest WHERE hashbytes('md5',f1) = hashbytes('md5',N'Test1')
SELECT f1 FROM #tblTest WHERE convert(varbinary(50),f1) = convert(varbinary(50),N'Test1')
سلام؛ یک برنامه ساده restful api دارم که وقتی اجرا میشه کاربر باید بتونه فیلدهایی رو پر کنه و ذخیره بشه. و بتونه مجدد اونها رو ببینه.
یعنی منظورم اینه که با دیتابیس در تعامله. این برنامه به صورت asp.net mvc هست (دات کور) نیست و بر اساس توضیحات مایکروسافت برای ساخت ایمیجش باید آدرس ایمیج ویندوز سرور رو در Dockerfile قرار بدم که حدود ۳ گیگ و نیم هست. حالا سوالم این هست این ایمیج ویندوز سرور با این حجم٬ خودش sql server داره دیگه؟ یعنی برنامه من بعد از ایمیج شدن وقتی داخل یک container بالا میاد٬ چطور باید به دیتابیس کانکت بشه؟ با آموزش شما متوجه شدم چطور یک باید دیتابیس در یک container دیگه بالا آورد اما در مورد برنامه خودم نمیتونم درک کنم چطور باید کار کنه. نمیشه هم برنامم هم دیتابیسش در یک containerباشند و کسی که ایمیج برنامم رو بالا میاره بدون نیاز به کار خاصی برنامه رو بتونه صحیح ببینه و کار کنه؟
- How EF6 Enables Mocking DbSets more easily
- Testing with a mocking framework - EF6 onwards
+ شخصا اعتقادی به Unit tests درون حافظهای، در مورد لایه دسترسی به دادهها ندارم. به قسمت «Limitations of EF in-memory test doubles» مراجعه کنید؛ توضیحات خوبی را ارائه دادهاست.
تست درون حافظهی LINQ to Objects با تست واقعی LINQ to Entities که روی یک بانک اطلاعاتی واقعی اجرا میشود، الزاما نتایج یکسانی نخواهد داشت (به دلیل انواع قیود بانک اطلاعاتی، پشتیبانی از SQL خاص تولید شده تا بارگذاری اشیاء مرتبط و غیره) و نتایج مثبت آن به معنای درست کار کردن برنامه در دنیای واقعی نخواهد بود. در اینجا Integration tests بهتر جواب میدهند و نه Unit tests.
- Testing with a mocking framework - EF6 onwards
+ شخصا اعتقادی به Unit tests درون حافظهای، در مورد لایه دسترسی به دادهها ندارم. به قسمت «Limitations of EF in-memory test doubles» مراجعه کنید؛ توضیحات خوبی را ارائه دادهاست.
تست درون حافظهی LINQ to Objects با تست واقعی LINQ to Entities که روی یک بانک اطلاعاتی واقعی اجرا میشود، الزاما نتایج یکسانی نخواهد داشت (به دلیل انواع قیود بانک اطلاعاتی، پشتیبانی از SQL خاص تولید شده تا بارگذاری اشیاء مرتبط و غیره) و نتایج مثبت آن به معنای درست کار کردن برنامه در دنیای واقعی نخواهد بود. در اینجا Integration tests بهتر جواب میدهند و نه Unit tests.
مواقع بسیاری پیش میآید که در زمان کار با یک نرم افزار تحت وب زمان اشکال زدایی پیش میآید که به دلیل موجود بودن داده در حافظه کش برنامه نویس نمیتواند دادههای واقعی را ببیند و دادههای موجود در حافظه کش را مشاهده میکند (بیشتر مواقعی که از طریق بانک اطلاعاتی مستقیما اقدام به حذف و اضافه داده میکنیم) در این بخش یک کلاس آماده کرده ام که همیشه خودم در نرم افزار هایم استفاده میکنم.
شما میتوانید این کلاس را به یک GridView یا کنترلهای دیگر بایند کرده و کلیدهای موجود در حافظه کش را مشاهده کنید، و در صورتی که خواستید یک کلید خاص را از حافظه کش حذف نمایید (البته این کلاس بیشتر برای مدیر نرم فزار کاربرد دارد).
میتوانید فایل مورد نظر را از طریق لینک کلاس کمکی جهت مشاهده آیتمهای موجود در حافظه کش و حذف آنها دانلود نمایید.
در کلاس زیر هر کدام از قسمتها را شرح میدهیم.
موفق وموید باشید
شما میتوانید این کلاس را به یک GridView یا کنترلهای دیگر بایند کرده و کلیدهای موجود در حافظه کش را مشاهده کنید، و در صورتی که خواستید یک کلید خاص را از حافظه کش حذف نمایید (البته این کلاس بیشتر برای مدیر نرم فزار کاربرد دارد).
میتوانید فایل مورد نظر را از طریق لینک کلاس کمکی جهت مشاهده آیتمهای موجود در حافظه کش و حذف آنها دانلود نمایید.
در کلاس زیر هر کدام از قسمتها را شرح میدهیم.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Web; using System.Web.Caching; namespace PWS.BLL { /// <summary> /// کلاس آیتمهای حافظه کش /// </summary> [DataObject(true)] public class CacheItems { #region Constructors (2) /// <summary> /// سازنده اصلی /// </summary> /// <param name="cacheItem">عنوان آیتم ذخیره شده در حافظه کش</param> public CacheItems(String cacheItem) { CacheItem = cacheItem; } /// <summary> /// سازنده پیش فرض /// </summary> public CacheItems(){} #endregion Constructors #region Properties (2) /// <summary> /// کش کانتکست جاری /// </summary> /// <value> /// The cache. /// </value> private static Cache Cache { get {return HttpContext.Current.Cache; } } /// <summary> /// عنوان آیتم ذخیره شده در حافظه کش /// </summary> public String CacheItem{ get; set;} #endregion Properties #region Methods (4) // Public Methods (3) /// <summary> /// لیست تمام آیتمهای ذخیره شده در حافظه کش /// </summary> /// <returns></returns> public List<CacheItems> GetCaches() { var items = new List<CacheItems>(); //بازیابی کل کلیدهای موجود در حافظه کش و اضافه کردن آن به لیست مربوطه var enumerator = Cache.GetEnumerator(); while (enumerator.MoveNext()) { items.Add(new CacheItems(enumerator.Key.ToString())); } return items; } /// <summary> /// حذف آیتم جاری از حافظه کش /// </summary> public void RemoveItemFromCache() { RemoveItemFromCache(CacheItem); } /// <summary> /// حذف کردن یک آیتم از حافظه کش /// </summary> /// <param name="key">کلید ذخیره شده در حافظه کش</param> public static void RemoveItemFromCache(string key) { PurgeCacheItems(key); } // Private Methods (1) /// <summary> /// حذف کردن یک ایتم از حافظه کش با پشوند وارد شده /// </summary> /// <param name="prefix">پیشوندی از کلید موجود در حافظه کش</param> private static void PurgeCacheItems(String prefix) { prefix = prefix.ToLower(); var itemsToRemove = new List<String>(); //لیست آیتمهای موجود در حافظه کش var enumerator = Cache.GetEnumerator(); while (enumerator.MoveNext()) {
//در صورتی که کلید مورد نظر با پارامتر وارد شده شروع شده باشد آن را به یک لیست اضافه میکنیم
if (enumerator.Key.ToString().ToLower().StartsWith(prefix)) itemsToRemove.Add(enumerator.Key.ToString()); } //لیست مورد نظر را پیمایش کرده و گزینههای آن را از حافظه کش حذف میکنیم foreach (var itemToRemove in itemsToRemove) Cache.Remove(itemToRemove); } #endregion Methods } }
مطالب دورهها
مدل سازی دادهها در RavenDB
در مطلب جاری، به صورت اختصاصی، مبحث مدل سازی اطلاعات و رسیدن به مدل ذهنی مرسوم در طراحیهای NoSQL سندگرا را در مقایسه با دنیای Relational، بررسی خواهیم کرد.
تفاوتهای دوره ما با زمانیکه بانکهای اطلاعاتی رابطهای پدیدار شدند
- دنیای بانکهای اطلاعاتی رابطهای برای Write بهینه سازی شدهاند؛ از این جهت که تاریخچه پیدایش آنها به دهه 70 میلادی بر میگردد، زمانیکه برای تهیه سخت دیسکها باید هزینههای گزافی پرداخت میشد. به همین جهت الگوریتمها و روشهای بسیاری در آن دوره ابداع شدند تا ذخیره سازی اطلاعات، حجم کمتری را به خود اختصاص دهند. اینجا است که مباحثی مانند Normalization بوجود آمدند تا تضمین شود که دادهها تنها یکبار ذخیره شده و دوبار در جاهای مختلفی ذخیره نگردند. جهت اطلاع در سال 1980 میلادی، یک سخت دیسک 10 مگابایتی حدود 4000 دلار قیمت داشته است.
- تفاوت مهم دیگر دوره ما با دهههای 70 و 80 میلادی، پدیدار شدن UI و روابط کاربری بسیار پیچیده، در مقایسه با برنامههای خط فرمان یا حداکثر فرمهای بسیار ساده ورود اطلاعات در آن زمان است. برای مثال در دهه 70 میلادی تصور UI ایی مانند صفحه ابتدایی سایت Stack overflow احتمالا به ذهن هم خطور نمیکرده است.
تهیه چنین UI ایی نه تنها از لحاظ طراحی، بلکه از لحاظ تامین دادهها از جداول مختلف نیز بسیار پیچیده است. برای مثال برای رندر صفحه اول سایت استک اورفلو ابتدا باید تعدادی سؤال از جدول سؤالات واکشی شوند. در اینجا در ذیل هر سؤال نام شخص مرتبط را هم مشاهده میکنید. بنابراین اطلاعات نام او، از جدول کاربران نیز باید دریافت گردد. یا در اینجا تعداد رایهای هر سؤال را نیز مشاهده میکنید که به طور قطع اطلاعات آن در جدول دیگری نگه داری میشود. در گوشهای از صفحه، برچسبهای مورد علاقه و در ذیل هر سؤال، برچسبهای اختصاصی هر مطلب نمایش داده شدهاند. تگها نیز در جدولی جداگانه قرار دارند. تمام این قسمتهای مختلف، نیاز به واکشی و رندر حجم بالایی از اطلاعات را دارند.
- تعداد کاربران برنامهها در دهههای 70 و 80 میلادی نیز با دوره ما متفاوت بودهاند. اغلب برنامههای آن دوران تک کاربره طراحی میشدند؛ با بانکهای اطلاعاتی که صرفا جهت کار بر روی یک سیستم طراحی شده بودند. اما برای نمونه سایت استک اور فلویی که مثال زده شده، توسط هزاران و یا شاید میلیونها نفر مورد استفاده قرار میگیرد؛ با توزیع و تقسیم اطلاعات آن بر روی سرورها مختلف.
معرفی مفهوم Unit of change
همین پیچیدگیها سبب شدند تا جهت سادهسازی حل اینگونه مسایل، حرکتی به سمت دنیای NoSQL شروع شود. ایده اصلی مدل سازی دادهها در اینجا کم کردن تعداد اعمالی است که باید جهت رسیدن به یک نتیجه واحد انجام داد. اگر قرار است یک سؤال به همراه تگها، اطلاعات کاربر، رایها و غیره واکشی شوند، چرا باید تعداد اعمال قابل توجهی جهت مراجعه به جداول مختلف مرتبط صورت گیرد؟ چرا تمام این اطلاعات را یکجا نداشته باشیم تا بتوان همگی را در طی یک واکشی به دست آورد و به این ترتیب دیگر نیازی نباشد انواع و اقسام JOINها را به چند ده جدول موجود نوشت؟
اینجا است که مفهومی به نام Unit of change مطرح میشود. در هر واحد تغییر، کلیه اطلاعات مورد نیاز برای رندر یک شیء قرار میگیرند. برای مثال اگر قرار است با شیء محصول کار کنیم، تمام اطلاعات مورد نیاز آنرا اعم از گروهها، نوعها، رنگها و غیره را در طی یک سند بانک اطلاعاتی NoSQL سندگرا، ذخیره میکنیم.
محدودههای تراکنشی یا Transactional boundaries
محدودههای تراکنشی در Domain driven design به Aggregate root نیز معروف است. هر محدود تراکنشی حاوی یک Unit of change قرار گرفته داخل یک سند است. ابتدا بررسی میکنیم که در یک Read به چه نوع اطلاعاتی نیاز داریم و سپس کل اطلاعات مورد نیاز را بدون نوشتن JOIN ایی از جداول دیگر، داخل یک سند قرار میدهیم.
هر محدوده تراکنشی میتواند به محدوده تراکنشی دیگری نیز ارجاع داده باشد. برای مثال در RavenDB شمارههای اسناد، یک سری رشته هستند؛ برخلاف بانکهای اطلاعاتی رابطهای که بیشتر از اعداد برای مشخص سازی Id استفاده میکنند. در این حالت برای ارجاع به یک کاربر فقط کافی است برای مثال مقدار خاصیت کاربر یک سند به "users/1" تنظیم شود. "users/1" نیز یک Id تعریف شده در RavenDB است.
مزیت این روش، سرعت واکشی بسیار بالای دریافت اطلاعات آن است؛ دیگر در اینجا نیازی به JOINهای سنگین به جداول دیگر برای تامین اطلاعات مورد نیاز نیست و همچنین در ساختارهای پیچیدهتری مانند ساختارهای تو در تو، دیگر نیازی به تهیه کوئریهای بازگشتی و استفاده از روشهای پیچیده مرتبط با آنها نیز وجود ندارد و کلیه اطلاعات مورد نظر، به شکل یک شیء JSON داخل یک سند حاضر و آماده برای واکشی در طی یک Read هستند.
به این ترتیب میتوان به سیستمهای مقیاس پذیری رسید. سیستمهایی که با بالا رفتن حجم اطلاعات در حین واکشیهای دادههای مورد نیاز، کند نبوده و بسیار سریع پاسخ میدهند.
Denormalization دادهها
اینجا است که احتمالا ذهن رابطهای تربیت شدهی شما شروع به واکنش میکند! برای مثال اگر نام یک محصول تغییر کرد، چطور؟ اگر آدرس یک مشتری نیاز به ویرایش داشت، چطور؟ چگونه یکپارچگی اطلاعاتی که اکنون به ازای هر سند پراکنده شدهاست، مدیریت میشود؟
زمانیکه به این نوع سؤالات رسیدهایم، یعنی Denormalization رخ داده است. در اینجا سندهایی را داریم که کلیه اطلاعات مورد نیاز خود را یکجا دارند. به این مساله از منظر نگاه به دادهها در طی زمان نیز میتوان پرداخت. به این معنا که صحیح است که آدرس مشتری خاصی امروز تغییر کرده است، اما زمانیکه سندی برای او در سال قبل صادر شده است، واقعا آدرس آن مشتری که سفارشی برایش ارسال شده، دقیقا همان چیزی بوده است که در سند مرتبط، ثبت شده و موجود میباشد. بنابراین سند قبلی با اطلاعات قبلی مشتری در سیستم موجود خواهد بود و اگر سند جدیدی صادر شد، این سند بدیهی است که از اطلاعات امروز مشتری استفاده میکند.
ملاحظات اندازههای دادهها
زمانیکه سندها بسیار بزرگ میشوند چه رخ خواهد داد؟ از لحاظ اندازه دادهها سه نوع سند را میتوان متصور بود:
الف) سندهای محدود، مانند اغلب اطلاعاتی که تعداد فیلدهای مشخصی دارند با تعداد اشیاء مشخصی.
ب) سندهای نامحدود اما با محدودیت طبیعی. برای مثال اطلاعات فرزندان یک شخص را درنظر بگیرید. هرچند این اطلاعات نامحدود هستند، اما به صورت طبیعی میتوان فرض کرد که سقف بالایی آن عموما به 20 نمیرسد!
ج) سندهای نامحدود، مانند سندهایی که آرایهای از اطلاعات را ذخیره میکنند. برای مثال در یک سایت فروشگاه، اطلاعات فروش یک گروه از اجناس خاص را درنظر بگیرید که عموما نامحدود است. اینجا است که باید به اندازه اسناد نیز دقت داشت. برای مدیریت این مساله حداقل از دو روش استفاده میشود:
- محدود کردن تعداد اشیاء. برای مثال در هر سند حداکثر 100 اطلاعات فروش یک محصول بیشتر ثبت نشود. زمانیکه به این حد رسیدیم، یک سند جدید ایجاد شده و Id سند قبلی مثلا "products/1" در سند دوم ذکر خواهد شد.
- محدود کردن تعداد اطلاعات ذخیره شده بر اساس زمان
RavenDB برای مدیریت این مساله، مفهوم Includes را معرفی کرده است. در اینجا با استفاده از متد الحاقی Include، کار زنجیر کردن سندهای مرتبط صورت خواهد گرفت.
یک مثال عملی: مدل سازی دادههای یک بلاگ در RavenDB
پس از این بحث مقدماتی که جهت معرفی ذهنیت مدل سازی دادهها در دنیای غیر رابطهای NoSQL ضروری بود، در ادامه قصد داریم مدلهای دادههای یک بلاگ را سازگار با ساختار بانک اطلاعاتی NoSQL سندگرای RavenDB طراحی کنیم.
در یک بلاگ، تعدادی مطلب، نظر، برچسب (گروههای مطالب) و امثال آن وجود دارند. اگر بخواهیم این اطلاعات را به صورت رابطهای مدل کنیم، به ازای هر کدام از این موجودیتها یک جدول نیاز خواهد بود و برای رندر صفحه اصلی بلاگ، چندین و چند کوئری برای نمایش اطلاعات مطالب، نویسنده(ها)، برچسبها و غیره باید به بانک اطلاعاتی ارسال گردد، که تعدادی از آنها مستقیما بر روی یک جدول اجرا میشوند و تعدادی دیگر نیاز به JOIN دارند.
مشکلاتی که روش رابطهای دارد:
- تعداد اعمالی که باید برای نمایش صفحه اول سایت صورت گیرد، بسیار زیاد است و این مساله با تعداد بالای کاربران از دید مقیاس پذیری سیستم مشکل ساز است.
- دادههای مرتبط در جداول مختلفی پراکندهاند.
- این سیستم برای Write بهینه سازی شده است و نه برای Read. (همان بحث گران بودن سخت دیسکها در دهههای قبل که در ابتدای بحث به آن اشاره شد)
مدل سازی سازگار با دنیای NoSQL یک بلاگ
در اینجا چند کلاس مقدماتی را مشاهده میکنید که تعریف آنها به همین نحو صحیح است و نیاز به جزئیات و یا روابط بیشتری ندارند.
اما کلاس مطالب بلاگ را به چه صورتی طراحی کنیم؟ هر مطلب، دارای تعدادی نظر خواهد بود. اینجا است که بحث unit of change مطرح میشود و درج اطلاعاتی که در طی یک read نیاز است از بانک اطلاعاتی جهت رندر UI واکشی شوند. به این ترتیب به این نتیجه میرسیم که بهتر است کلیه کامنتهای یک مطلب را داخل همان شیء مطلب مرتبط قرار دهیم. از این جهت که یک نظر، خارج از یک مطلب بلاگ دارای مفهوم نیست.
اما این طراحی نیز یک مشکل دارد. درست است که ساختار یک صفحه مطلب، از مطالب وبلاگ به همین نحوی است که توضیح داده شد؛ اما در صفحه اول سایت، هیچگاه کامنتهای مطالب درج نمیشوند. بنابراین نیازی نیست تا تمام کامنتها را داخل یک مطلب ذخیره کرد. به این ترتیب برای نمایش صفحه اول سایت، حجم کمتری از اطلاعات واکشی خواهند شد.
در اینجا ساختار Post و Commentهای بلاگ را مشاهده میکنید. جایی که ذخیره سازی اصلی کامنتها صورت میگیرد در شیء PostComments است. یعنی PostCommentsId شیء Post به یک وهله از شیء PostComments که حاوی کلیه کامنتهای آن مطلب است، اشاره میکند.
به این ترتیب برای نمایش صفحه اول سایت، فقط یک کوئری صادر میشود. برای نمایش یک مطلب و کلیه کامنتهای متناظر با آن دو کوئری صادر خواهند شد.
بنابراین همانطور که مشاهده میکنید، در دنیای NoSQL، طراحی مدلهای دادهای بر اساس «سناریوهای Read» صورت میگیرد و نه صرفا طراحی یک مدل رابطهای بهینه سازی شده برای حالت Write.
سورس کامل ASP.NET MVC این بلاگرا که «راکن بلاگ» نام دارد، از GitHub نویسندگان اصلی RavenDB میتوانید دریافت کنید.
تفاوتهای دوره ما با زمانیکه بانکهای اطلاعاتی رابطهای پدیدار شدند
- دنیای بانکهای اطلاعاتی رابطهای برای Write بهینه سازی شدهاند؛ از این جهت که تاریخچه پیدایش آنها به دهه 70 میلادی بر میگردد، زمانیکه برای تهیه سخت دیسکها باید هزینههای گزافی پرداخت میشد. به همین جهت الگوریتمها و روشهای بسیاری در آن دوره ابداع شدند تا ذخیره سازی اطلاعات، حجم کمتری را به خود اختصاص دهند. اینجا است که مباحثی مانند Normalization بوجود آمدند تا تضمین شود که دادهها تنها یکبار ذخیره شده و دوبار در جاهای مختلفی ذخیره نگردند. جهت اطلاع در سال 1980 میلادی، یک سخت دیسک 10 مگابایتی حدود 4000 دلار قیمت داشته است.
- تفاوت مهم دیگر دوره ما با دهههای 70 و 80 میلادی، پدیدار شدن UI و روابط کاربری بسیار پیچیده، در مقایسه با برنامههای خط فرمان یا حداکثر فرمهای بسیار ساده ورود اطلاعات در آن زمان است. برای مثال در دهه 70 میلادی تصور UI ایی مانند صفحه ابتدایی سایت Stack overflow احتمالا به ذهن هم خطور نمیکرده است.
تهیه چنین UI ایی نه تنها از لحاظ طراحی، بلکه از لحاظ تامین دادهها از جداول مختلف نیز بسیار پیچیده است. برای مثال برای رندر صفحه اول سایت استک اورفلو ابتدا باید تعدادی سؤال از جدول سؤالات واکشی شوند. در اینجا در ذیل هر سؤال نام شخص مرتبط را هم مشاهده میکنید. بنابراین اطلاعات نام او، از جدول کاربران نیز باید دریافت گردد. یا در اینجا تعداد رایهای هر سؤال را نیز مشاهده میکنید که به طور قطع اطلاعات آن در جدول دیگری نگه داری میشود. در گوشهای از صفحه، برچسبهای مورد علاقه و در ذیل هر سؤال، برچسبهای اختصاصی هر مطلب نمایش داده شدهاند. تگها نیز در جدولی جداگانه قرار دارند. تمام این قسمتهای مختلف، نیاز به واکشی و رندر حجم بالایی از اطلاعات را دارند.
- تعداد کاربران برنامهها در دهههای 70 و 80 میلادی نیز با دوره ما متفاوت بودهاند. اغلب برنامههای آن دوران تک کاربره طراحی میشدند؛ با بانکهای اطلاعاتی که صرفا جهت کار بر روی یک سیستم طراحی شده بودند. اما برای نمونه سایت استک اور فلویی که مثال زده شده، توسط هزاران و یا شاید میلیونها نفر مورد استفاده قرار میگیرد؛ با توزیع و تقسیم اطلاعات آن بر روی سرورها مختلف.
معرفی مفهوم Unit of change
همین پیچیدگیها سبب شدند تا جهت سادهسازی حل اینگونه مسایل، حرکتی به سمت دنیای NoSQL شروع شود. ایده اصلی مدل سازی دادهها در اینجا کم کردن تعداد اعمالی است که باید جهت رسیدن به یک نتیجه واحد انجام داد. اگر قرار است یک سؤال به همراه تگها، اطلاعات کاربر، رایها و غیره واکشی شوند، چرا باید تعداد اعمال قابل توجهی جهت مراجعه به جداول مختلف مرتبط صورت گیرد؟ چرا تمام این اطلاعات را یکجا نداشته باشیم تا بتوان همگی را در طی یک واکشی به دست آورد و به این ترتیب دیگر نیازی نباشد انواع و اقسام JOINها را به چند ده جدول موجود نوشت؟
اینجا است که مفهومی به نام Unit of change مطرح میشود. در هر واحد تغییر، کلیه اطلاعات مورد نیاز برای رندر یک شیء قرار میگیرند. برای مثال اگر قرار است با شیء محصول کار کنیم، تمام اطلاعات مورد نیاز آنرا اعم از گروهها، نوعها، رنگها و غیره را در طی یک سند بانک اطلاعاتی NoSQL سندگرا، ذخیره میکنیم.
محدودههای تراکنشی یا Transactional boundaries
محدودههای تراکنشی در Domain driven design به Aggregate root نیز معروف است. هر محدود تراکنشی حاوی یک Unit of change قرار گرفته داخل یک سند است. ابتدا بررسی میکنیم که در یک Read به چه نوع اطلاعاتی نیاز داریم و سپس کل اطلاعات مورد نیاز را بدون نوشتن JOIN ایی از جداول دیگر، داخل یک سند قرار میدهیم.
هر محدوده تراکنشی میتواند به محدوده تراکنشی دیگری نیز ارجاع داده باشد. برای مثال در RavenDB شمارههای اسناد، یک سری رشته هستند؛ برخلاف بانکهای اطلاعاتی رابطهای که بیشتر از اعداد برای مشخص سازی Id استفاده میکنند. در این حالت برای ارجاع به یک کاربر فقط کافی است برای مثال مقدار خاصیت کاربر یک سند به "users/1" تنظیم شود. "users/1" نیز یک Id تعریف شده در RavenDB است.
مزیت این روش، سرعت واکشی بسیار بالای دریافت اطلاعات آن است؛ دیگر در اینجا نیازی به JOINهای سنگین به جداول دیگر برای تامین اطلاعات مورد نیاز نیست و همچنین در ساختارهای پیچیدهتری مانند ساختارهای تو در تو، دیگر نیازی به تهیه کوئریهای بازگشتی و استفاده از روشهای پیچیده مرتبط با آنها نیز وجود ندارد و کلیه اطلاعات مورد نظر، به شکل یک شیء JSON داخل یک سند حاضر و آماده برای واکشی در طی یک Read هستند.
به این ترتیب میتوان به سیستمهای مقیاس پذیری رسید. سیستمهایی که با بالا رفتن حجم اطلاعات در حین واکشیهای دادههای مورد نیاز، کند نبوده و بسیار سریع پاسخ میدهند.
Denormalization دادهها
اینجا است که احتمالا ذهن رابطهای تربیت شدهی شما شروع به واکنش میکند! برای مثال اگر نام یک محصول تغییر کرد، چطور؟ اگر آدرس یک مشتری نیاز به ویرایش داشت، چطور؟ چگونه یکپارچگی اطلاعاتی که اکنون به ازای هر سند پراکنده شدهاست، مدیریت میشود؟
زمانیکه به این نوع سؤالات رسیدهایم، یعنی Denormalization رخ داده است. در اینجا سندهایی را داریم که کلیه اطلاعات مورد نیاز خود را یکجا دارند. به این مساله از منظر نگاه به دادهها در طی زمان نیز میتوان پرداخت. به این معنا که صحیح است که آدرس مشتری خاصی امروز تغییر کرده است، اما زمانیکه سندی برای او در سال قبل صادر شده است، واقعا آدرس آن مشتری که سفارشی برایش ارسال شده، دقیقا همان چیزی بوده است که در سند مرتبط، ثبت شده و موجود میباشد. بنابراین سند قبلی با اطلاعات قبلی مشتری در سیستم موجود خواهد بود و اگر سند جدیدی صادر شد، این سند بدیهی است که از اطلاعات امروز مشتری استفاده میکند.
ملاحظات اندازههای دادهها
زمانیکه سندها بسیار بزرگ میشوند چه رخ خواهد داد؟ از لحاظ اندازه دادهها سه نوع سند را میتوان متصور بود:
الف) سندهای محدود، مانند اغلب اطلاعاتی که تعداد فیلدهای مشخصی دارند با تعداد اشیاء مشخصی.
ب) سندهای نامحدود اما با محدودیت طبیعی. برای مثال اطلاعات فرزندان یک شخص را درنظر بگیرید. هرچند این اطلاعات نامحدود هستند، اما به صورت طبیعی میتوان فرض کرد که سقف بالایی آن عموما به 20 نمیرسد!
ج) سندهای نامحدود، مانند سندهایی که آرایهای از اطلاعات را ذخیره میکنند. برای مثال در یک سایت فروشگاه، اطلاعات فروش یک گروه از اجناس خاص را درنظر بگیرید که عموما نامحدود است. اینجا است که باید به اندازه اسناد نیز دقت داشت. برای مدیریت این مساله حداقل از دو روش استفاده میشود:
- محدود کردن تعداد اشیاء. برای مثال در هر سند حداکثر 100 اطلاعات فروش یک محصول بیشتر ثبت نشود. زمانیکه به این حد رسیدیم، یک سند جدید ایجاد شده و Id سند قبلی مثلا "products/1" در سند دوم ذکر خواهد شد.
- محدود کردن تعداد اطلاعات ذخیره شده بر اساس زمان
RavenDB برای مدیریت این مساله، مفهوم Includes را معرفی کرده است. در اینجا با استفاده از متد الحاقی Include، کار زنجیر کردن سندهای مرتبط صورت خواهد گرفت.
یک مثال عملی: مدل سازی دادههای یک بلاگ در RavenDB
پس از این بحث مقدماتی که جهت معرفی ذهنیت مدل سازی دادهها در دنیای غیر رابطهای NoSQL ضروری بود، در ادامه قصد داریم مدلهای دادههای یک بلاگ را سازگار با ساختار بانک اطلاعاتی NoSQL سندگرای RavenDB طراحی کنیم.
در یک بلاگ، تعدادی مطلب، نظر، برچسب (گروههای مطالب) و امثال آن وجود دارند. اگر بخواهیم این اطلاعات را به صورت رابطهای مدل کنیم، به ازای هر کدام از این موجودیتها یک جدول نیاز خواهد بود و برای رندر صفحه اصلی بلاگ، چندین و چند کوئری برای نمایش اطلاعات مطالب، نویسنده(ها)، برچسبها و غیره باید به بانک اطلاعاتی ارسال گردد، که تعدادی از آنها مستقیما بر روی یک جدول اجرا میشوند و تعدادی دیگر نیاز به JOIN دارند.
مشکلاتی که روش رابطهای دارد:
- تعداد اعمالی که باید برای نمایش صفحه اول سایت صورت گیرد، بسیار زیاد است و این مساله با تعداد بالای کاربران از دید مقیاس پذیری سیستم مشکل ساز است.
- دادههای مرتبط در جداول مختلفی پراکندهاند.
- این سیستم برای Write بهینه سازی شده است و نه برای Read. (همان بحث گران بودن سخت دیسکها در دهههای قبل که در ابتدای بحث به آن اشاره شد)
مدل سازی سازگار با دنیای NoSQL یک بلاگ
در اینجا چند کلاس مقدماتی را مشاهده میکنید که تعریف آنها به همین نحو صحیح است و نیاز به جزئیات و یا روابط بیشتری ندارند.
namespace RavenDBSample01.BlogModels { public class BlogConfig { public string Id { set; get; } public string Title { set; get; } public string Description { set; get; } // ... more items here } public class User { public string Id { set; get; } public string FullName { set; get; } public string Email { set; get; } // ... more items here } }
اما این طراحی نیز یک مشکل دارد. درست است که ساختار یک صفحه مطلب، از مطالب وبلاگ به همین نحوی است که توضیح داده شد؛ اما در صفحه اول سایت، هیچگاه کامنتهای مطالب درج نمیشوند. بنابراین نیازی نیست تا تمام کامنتها را داخل یک مطلب ذخیره کرد. به این ترتیب برای نمایش صفحه اول سایت، حجم کمتری از اطلاعات واکشی خواهند شد.
public class Post { public string Id { set; get; } public string Title { set; get; } public string Body { set; get; } public ICollection<string> Tags { set; get; } public string AuthorId { set; get; } public string PostCommentsId { set; get; } public int CommentsCount { set; get; } } public class Comment { public string Id { set; get; } public string Body { set; get; } public string AuthorName { set; get; } public DateTime CreatedAt { set; get; } } public class PostComments { public List<Comment> Comments { set; get; } public string LastCommentId { set; get; } }
به این ترتیب برای نمایش صفحه اول سایت، فقط یک کوئری صادر میشود. برای نمایش یک مطلب و کلیه کامنتهای متناظر با آن دو کوئری صادر خواهند شد.
بنابراین همانطور که مشاهده میکنید، در دنیای NoSQL، طراحی مدلهای دادهای بر اساس «سناریوهای Read» صورت میگیرد و نه صرفا طراحی یک مدل رابطهای بهینه سازی شده برای حالت Write.
سورس کامل ASP.NET MVC این بلاگرا که «راکن بلاگ» نام دارد، از GitHub نویسندگان اصلی RavenDB میتوانید دریافت کنید.
نظرات مطالب
RavenDB؛ تجربه متفاوت از پایگاه داده
سلام
میخوام یه نرم افزار تحت ویندوز بنویسم که نیاز دارم از بانک اطلاعاتی استفاده کنم از طرفی هم نمیخوام با Sql server و یا access کار کنم چون نیاز به نزم افزار هایی با حجم زیاد هست که برای کاربر دردسر میشه.
میخواستم اگه میشه بفرمایید که به نظرتون از چی استفاده کنم بهتره؟
ممنون
میخوام یه نرم افزار تحت ویندوز بنویسم که نیاز دارم از بانک اطلاعاتی استفاده کنم از طرفی هم نمیخوام با Sql server و یا access کار کنم چون نیاز به نزم افزار هایی با حجم زیاد هست که برای کاربر دردسر میشه.
میخواستم اگه میشه بفرمایید که به نظرتون از چی استفاده کنم بهتره؟
ممنون
نظرات مطالب
EF Code First #1
1 و 3 - در انتهای بحث عرض کردم در قسمتهای بعدی خیلی از موارد رو توضیح خواهم داد. این قسمت اول و فقط یک «مقدمه» ابتدایی بود.
2 - EF با بانکهای اطلاعاتی NoSQL کار نمیکند. ضمنا هستند بانکهای اطلاعاتی NoSQL ایی که برای دات نت نوشته شدهاند و از همان روز اول با کلاسها و LINQ کار میکنید مانند RavenDB . طراحی فوق العادهای داره (^).
استفاده از EF Code first با سایر بانکهای اطلاعاتی بجز مشتقات SQL Server نیز میسر است. برای آنها نیاز به پروایدر مخصوص وجود دارد؛ مثلا: (^)
2 - EF با بانکهای اطلاعاتی NoSQL کار نمیکند. ضمنا هستند بانکهای اطلاعاتی NoSQL ایی که برای دات نت نوشته شدهاند و از همان روز اول با کلاسها و LINQ کار میکنید مانند RavenDB . طراحی فوق العادهای داره (^).
استفاده از EF Code first با سایر بانکهای اطلاعاتی بجز مشتقات SQL Server نیز میسر است. برای آنها نیاز به پروایدر مخصوص وجود دارد؛ مثلا: (^)