مطالب
یافتن تداخلات Collations در SQL Server

اگر دیتابیس خود را در طی چند سال از یک نگارش به نگارشی دیگر یا از یک سرور به سروری دیگر منتقل کرده باشید، به احتمال زیاد به مشکلات Collations هم برخورده‌اید. یکی از فیلدها Arabic_CI_AS است (بجا مانده از دوران قبل از SQL Server 2008) در یک جدول و در جدولی دیگر فیلدی تازه‌ای با Collation از نوع Persian_100_CI_AS تعریف شده است. Collations نحوه ذخیره سازی و مقایسه رشته‌ها را کنترل می‌کنند. زمانیکه یک جدول جدید را در SQL Server ایجاد می‌کنیم، اگر Collation فیلدها به صورت صریح ذکر نگردند، بر مبنای همان Collation پیش فرض دیتابیس تعریف خواهند شد.
بنابراین اگر پس از استفاده از SQL Server 2008 و تنظیم Collation پیش فرض دیتابیس به Persian_100_CI_AS ، به این موارد دقت نکنیم، دیر یا زود دچار مشکل خواهیم شد.
عملیات مرتب سازی با وجود تداخلات Collations مشکل ساز نمی‌شود (خطایی دریافت نمی‌کنید)، اما ممکن است الزاما صحیح عمل نکند. مشکل از آنجایی آغاز می‌شود که قصد داشته باشیم داده‌ها را مقایسه کنیم یا join ایی بین این دو جدول با فیلدهای ناهمگون از لحاظ Collation ایجاد نمائیم. در این حالت حتما خطاهای تداخل Collation را دریافت کرده و کوئری‌های ما اجرا نخواهند شد.
Cannot resolve collation conflict for equal to operation

یک راه حل این است که در حین join به صورت صریح collation هر دو فیلد ذکر شده را به صورت یکسان ذکر کنیم که بیشتر یک مرهم موقتی است تا راه حل اصولی. برای مثال:
SELECT ID
FROM ItemsTable
INNER JOIN AccountsTable
WHERE ItemsTable.Collation1Col COLLATE DATABASE_DEFAULT
= AccountsTable.Collation2Col COLLATE DATABASE_DEFAULT
راه دیگر این است که مشخص کنیم که Collation کدام فیلدها در دیتابیس با Collation پیش فرض دیتابیس تطابق ندارند. سپس بر اساس این لیست شروع به تغییر Collations نمائیم.
اسکریپت زیر تمام فیلدهای ناهمگون از لحاظ Collation دیتابیس جاری را برای شما لیست خواهد کرد:
DECLARE @defaultCollation NVARCHAR(1000)
SET @defaultCollation = CAST(
DATABASEPROPERTYEX(DB_NAME(), 'Collation') AS NVARCHAR(1000)
)

SELECT C.Table_Name,
Column_Name,
Collation_Name,
@defaultCollation DefaultCollation
FROM Information_Schema.Columns C
INNER JOIN Information_Schema.Tables T
ON C.Table_Name = T.Table_Name
WHERE T.Table_Type = 'Base Table'
AND RTRIM(LTRIM(Collation_Name)) <> RTRIM(LTRIM(@defaultCollation))
AND COLUMNPROPERTY(OBJECT_ID(C.Table_Name), Column_Name, 'IsComputed') = 0
ORDER BY
C.Table_Name,
C.Column_Name
برای مثال جهت تغییر Collation فیلد Serial از جدول tblArchive از نوع nvarchar با طول 200 به Persian_100_CI_AS می‌توان از دستور T-SQL زیر استفاده کرد:
ALTER TABLE [tblArchive] ALTER COLUMN [Serial] nvarchar(200) COLLATE Persian_100_CI_AS not null

اشتراک‌ها
فرم ساز JQuery

کاربردهای این فرم سازها:

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

در سیستم‌های معمول هم مانند نظرسنجی، نحوه کار، موارد مورد نیاز با روند و...

ویژگی‌های پلاگین:(داکیومنت کامل)

1- سادگی

2- پشتیبانی پیشفرض از زبان فارسی

3- ذخیره اطلاعات به صورت JSON و ویرایش آن به راحتی

4- امکان مشاهده پیش نمایش در زمان ویرایش

این فرم ساز هم در نظر داشته باشید لطفا 

فرم ساز JQuery
مطالب
SQL Server CE و ثبت متون طولانی در EF Code first
زمانیکه در EF Code first تعریف خاصیتی به نحو زیر باشد
public string Content { get; set; }
در حین کار با SQL Server به صورت خودکار به nvarchar max نگاشت می‌شود. اما همین تعریف در SQL Server CE به nvarchar 4000 نگاشت خواهد شد؛ چون این بانک اطلاعاتی نوع‌های max دار را پشتیبانی نمی‌کند.
بنابراین اگر هدف، ثبت اطلاعات در فیلدی از نوع ntext در این بانک اطلاعاتی باشد باید به یکی از دو روش زیر عمل کرد:
[MaxLength]
public string Content { get; set; }
بله. فقط کافی است یک MaxLength را بالای خاصیت قرار داد (بدون تعیین طول آن) تا به صورت خودکار در SQL Server CE به ntext نگاشت شود و یا می‌توان نوع ستون را صریحا تعیین کرد:
[Column(TypeName = "ntext")]
public string Text { get; set; }
روش اول بهتر است از این جهت که با بانک‌های اطلاعاتی مختلف سازگاری بهتری دارد. برای مثال نوع ntext در SQL Server کامل، منسوخ شده درنظر گرفته می‌شود اما اگر از ویژگی MaxLength در اینجا استفاده گردد به صورت خودکار به nvarchar max نگاشت خواهد شد و در SQL Server CE به ntext .
بنابراین قید MaxLength بر روی خواصی که قرار است حاوی متونی طولانی باشند، می‌تواند به عنوان یک کار مفید جهت سازگاری با بانک‌های مختلف، به شمار آید.
 
نظرات مطالب
غیرمعتبر شدن کوکی‌های برنامه‌های ASP.NET Core هاست شده‌ی در IIS پس از ری‌استارت آن
- services.AddDataProtection یعنی همان مقدمه‌ی بحث؛ یا ذخیره سازی کلیدها در حافظه به صورت پیش‌فرض. مابقی بحث جهت دائمی کردن این کلیدها است. البته دائمی کردن هم طول عمری دارد.
- در سرورهای اشتراکی یا از روش «یک نکته‌ی تکمیلی: روش ذخیره سازی کلید موقتی تولید شده در بانک اطلاعاتی بجای حافظه‌ی سرور » استفاده کنید، یا با هاست تماس بگیرید و تنظیم گزینه‌ی 2 یا همان Load user profile به true را به آن‌ها اعلام کنید (چون تنظیمات برنامه‌های ASP.NET Core با نگارش‌های قبلی یکی نیست؛ این یک مورد را هم بهتر است به لیست تنظیمات اولیه‌ی برنامه اضافه کنند).
- در حالت سوم، ذکر Certificate برای رمزنگاری اطلاعات ضروری است؛ در غیراینصورت این کلیدها به صورت معمولی و واضح ذخیره خواهند شد.
مطالب
OpenCVSharp #12
قطعه بندی (segmentation) تصویر با استفاده از الگوریتم watershed

در تصویر ذیل، تصویر یک راه‌رو را مشاهده می‌کنید که توسط ماوس قطعه بندی شده‌است (تصویر اصلی یا سمت چپ). تصویر سمت راست، نسخه‌ی قطعه بندی شده‌ی این تصویر به کمک الگوریتم watershed است.

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


انتخاب نواحی مختلف به کمک ماوس

در اینجا کدهای آغازین مثال بحث جاری را ملاحظه می‌کنید:
var src = new Mat(@"..\..\Images\corridor.jpg", LoadMode.AnyDepth | LoadMode.AnyColor);
var srcCopy = new Mat();
src.CopyTo(srcCopy);
 
var markerMask = new Mat();
Cv2.CvtColor(srcCopy, markerMask, ColorConversion.BgrToGray);
 
var imgGray = new Mat();
Cv2.CvtColor(markerMask, imgGray, ColorConversion.GrayToBgr);
markerMask = new Mat(markerMask.Size(), markerMask.Type(), s: Scalar.All(0));
 
var sourceWindow = new Window("Source (Select areas by mouse and then press space)")
{
    Image = srcCopy
};
 
var previousPoint = new Point(-1, -1);
sourceWindow.OnMouseCallback += (@event, x, y, flags) =>
{
    if (x < 0 || x >= srcCopy.Cols || y < 0 || y >= srcCopy.Rows)
    {
        return;
    }
 
    if (@event == MouseEvent.LButtonUp || !flags.HasFlag(MouseEvent.FlagLButton))
    {
        previousPoint = new Point(-1, -1);
    }
    else if (@event == MouseEvent.LButtonDown)
    {
        previousPoint = new Point(x, y);
    }
    else if (@event == MouseEvent.MouseMove && flags.HasFlag(MouseEvent.FlagLButton))
    {
        var pt = new Point(x, y);
        if (previousPoint.X < 0)
        {
            previousPoint = pt;
        }
 
        Cv2.Line(img: markerMask, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5);
        Cv2.Line(img: srcCopy, pt1: previousPoint, pt2: pt, color: Scalar.All(255), thickness: 5);
        previousPoint = pt;
        sourceWindow.Image = srcCopy;
    }
};
ابتدا تصویر راه‌رو بارگذاری شده‌است. سپس یک نسخه‌ی سیاه و سفید تک کاناله به نام markerMask از آن استخراج می‌شود. از آن برای ترسیم خطوط انتخاب نواحی مختلف تصویر به کمک ماوس استفاده می‌شود. به علاوه متد FindContours که در ادامه معرفی خواهد شد، نیاز به یک تصویر 8 بیتی تک کاناله دارد (به هر یک از اجزای RGB یک کانال گفته می‌شود).
همچنین این نسخه‌ی سیاه و سفید تک کاناله به یک تصویر سه کاناله برای نمایش رنگ‌های قسمت‌های مختلف قطعه بندی شده، تبدیل می‌شود.
سپس پنجره‌ی نمایش تصویر اصلی برنامه ایجاد شده و در اینجا روال رخدادگردان OnMouseCallback آن به صورت inline مقدار دهی شده‌است. در این روال می‌توان مدیریت ماوس را به عهده گرفت و کار نمایش خطوط مختلف را با فشرده شدن و سپس رها شدن کلیک سمت چپ ماوس انجام داد.
خط ترسیم شده بر روی دو تصویر از نوع Mat نمایش داده می‌شود. تصویر srcCopy، همان تصویر نمایش داده شده‌ی در پنجره‌ی اصلی است و تصویر markerMask، بیشتر جنبه‌ی محاسباتی دارد و در متدهای بعدی OpenCV استفاده خواهد شد.


تشخیص کانتورها (Contours) در تصویر

پس از ترسیم نواحی مورد نظر توسط ماوس، یک سری خطوط به هم پیوسته در شکل قابل مشاهده هستند. می‌خواهیم این خطوط را تشخیص داده و سپس از آن‌ها جهت محاسبات قطعه بندی تصویر استفاده کنیم. تشخیص این خطوط متصل، توسط متدی به نام FindContours انجام می‌شود. کانتورها، قسمت‌های خارجی اجزای متصل به هم هستند.
Point[][] contours; //vector<vector<Point>> contours;
HiearchyIndex[] hierarchyIndexes; //vector<Vec4i> hierarchy;
Cv2.FindContours(
    markerMask,
    out contours,
    out hierarchyIndexes,
    mode: ContourRetrieval.CComp,
    method: ContourChain.ApproxSimple);
متد FindContours همان تصویر markerMask را که توسط ماوس، قسمت‌های مختلف تصویر را علامتگذاری کرده‌است، دریافت می‌کند. سپس کانتورهای آن را استخراج خواهد کرد. کانتورها در مثال‌های اصلی OpenCV با verctor مشخص شده‌اند. در اینجا (در کتابخانه‌ی OpenCVSharp) آن‌ها را توسط یک آرایه‌ی دو بعدی از نوع Point مشاهده می‌کنید یا شبیه به لیستی از آرایه‌ی نقاط کانتورهای مختلف تشخیص داده شده (هر کانتور، آرایه‌ی از نقاط است). از hierarchyIndexes جهت یافتن و ترسیم این کانتورها در متد DrawContours استفاده می‌شود.
متد FindContours یک تصویر 8 بیتی تک کاناله را دریافت می‌کند. اگر mode آن CCOMP یا FLOODFILL تعریف شود، امکان دریافت یک تصویر 32 بیتی را نیز خواهد داشت.
پارامتر hierarchy آن یک پارامتر اختیاری است که بیانگر اطلاعات topology تصویر است.
توسط پارامتر Mode، نحوه‌ی استخراج کانتور مشخص می‌شود. اگر به external تنظیم شود، تنها کانتورهای خارجی‌ترین قسمت‌ها را تشخیص می‌دهد. اگر مساوی list قرار گیرد، تمام کانتورها را بدون ارتباطی با یکدیگر و بدون تشکیل hierarchy استخراج می‌کند. حالت ccomp تمام کانتورها را استخراج کرده و یک درخت دو سطحی از آن‌ها را تشکیل می‌دهد. در سطح بالایی مرزهای خارجی اجزاء وجود دارند و در سطح دوم مرزهای حفره‌ها مشخص شده‌اند. حالت و مقدار tree به معنای تشکیل یک درخت کامل از کانتورهای یافت شده‌است.
پارامتر method اگر به none تنظیم شود، تمام نقاط کانتور ذخیره خواهند شد و اگر به simple تنظیم شود، قطعه‌های افقی، عمودی و قطری، فشرده شده و تنها نقاط نهایی آن‌ها ذخیره می‌شوند. برای مثال در این حالت یک کانتور مستطیلی، تنها با 4 نقطه ذخیره می‌شود.


ترسیم کانتورهای تشخیص داده شده بر روی تصویر


می‌توان به کمک متد DrawContours، مرزهای کانتورهای یافت شده را ترسیم کرد:
var markers = new Mat(markerMask.Size(), MatType.CV_32S, s: Scalar.All(0));
 
var componentCount = 0;
var contourIndex = 0;
while ((contourIndex >= 0))
{
    Cv2.DrawContours(
        markers,
        contours,
        contourIndex,
        color: Scalar.All(componentCount + 1),
        thickness: -1,
        lineType: LineType.Link8,
        hierarchy: hierarchyIndexes,
        maxLevel: int.MaxValue);
 
    componentCount++;
    contourIndex = hierarchyIndexes[contourIndex].Next;
}
پارامتر اول آن تصویری است که قرار است ترسیمات بر روی آن انجام شوند. پارامتر کانتور، آرایه‌ای است از کانتورهای یافت شده‌ی در قسمت قبل. پارامتر ایندکس مشخص می‌کند که اکنون کدام کانتور باید رسم شود. برای یافتن کانتور بعدی باید از hierarchyIndexes یافت شده‌ی توسط متد FindContours استفاده کرد. خاصیت Next آن، بیانگر ایندکس کانتور بعدی است و اگر مساوی منهای یک شد، کار متوقف می‌شود. مقدار maxLevel مشخص می‌کند که بر اساس پارامتر hierarchyIndexes، چند سطح از کانتورهای به هم مرتبط باید ترسیم شوند. در اینجا چون به حداکثر مقدار Int32 تنظیم شده‌است، تمام این سطوح ترسیم خواهند شد. اگر پارامتر ضخامت به یک عدد منفی تنظیم شود، سطوح داخلی کانتور ترسیم و پر می‌شوند.



اعمال الگوریتم watershed

در مرحله‌ی آخر، تصویر کانتورهای ترسیم شده را به متد Watershed ارسال می‌کنیم. پارامتر اول آن تصویر اصلی است و پارامتر دوم، یک پارامتر ورودی و خروجی محسوب می‌شود و کار قطعه بندی تصویر بر روی آن انجام خواهد شد.
کار الگوریتم watershed، ایزوله سازی اشیاء موجود در تصویر از پس زمینه‌ی آن‌ها است. این الگوریتم، یک تصویر سیاه و سفید را دریافت می‌کند؛ به همراه یک تصویر ویژه به نام marker. تصویر marker کارش مشخص سازی اشیاء، از پس زمینه‌ی آن‌ها است که در اینجا توسط ماوس ترسیم و سپس به کمک یافتن کانتورها و ترسیم آ‌ن‌ها بهینه سازی شده‌است.
var rnd = new Random();
var colorTable = new List<Vec3b>();
for (var i = 0; i < componentCount; i++)
{
    var b = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255);
    var g = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255);
    var r = rnd.Next(0, 255); //Cv2.TheRNG().Uniform(0, 255);
 
    colorTable.Add(new Vec3b((byte)b, (byte)g, (byte)r));
}
 
Cv2.Watershed(src, markers);
 
var watershedImage = new Mat(markers.Size(), MatType.CV_8UC3);
 
// paint the watershed image
for (var i = 0; i < markers.Rows; i++)
{
    for (var j = 0; j < markers.Cols; j++)
    {
        var idx = markers.At<int>(i, j);
        if (idx == -1)
        {
            watershedImage.Set(i, j, new Vec3b(255, 255, 255));
        }
        else if (idx <= 0 || idx > componentCount)
        {
            watershedImage.Set(i, j, new Vec3b(0, 0, 0));
        }
        else
        {
            watershedImage.Set(i, j, colorTable[idx - 1]);
        }
    }
}
 
watershedImage = watershedImage * 0.5 + imgGray * 0.5;
Cv2.ImShow("Watershed Transform", watershedImage);
Cv2.WaitKey(1); //do events
متد Cv2.TheRNG یک تولید کننده‌ی اعداد تصادفی توسط OpenCV است و متد Uniform آن شبیه به متد Next کلاس Random دات نت عمل می‌کند. به نظر این کلاس تولید اعداد تصادفی، آنچنان هم تصادفی عمل نمی‌کند. به همین جهت از کلاس Random دات نت استفاده شد. در اینجا به ازای تعداد کانتورهای ترسیم شده، یک رنگ تصادفی تولید شده‌است.
پس از اعمال متد Watershed، هر نقطه‌ی تصویر marker مشخص می‌کند که متعلق به کدام قطعه‌ی تشخیص داده شده‌است. سپس به این نقطه، رنگ آن قطعه را نسبت داده و آن‌را در تصویر جدیدی ترسیم می‌کنیم.
در آخر، پس زمینه، با نواحی تشخیص داده ترکیب شده‌اند (watershedImage * 0.5 + imgGray * 0.5) تا تصویر ابتدای بحث حاصل شود. اگر این ترکیب صورت نگیرد، چنین تصویری حاصل خواهد شد:




کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many
در جهت تکمیل بحث بارگذاری اطلاعات وابسته: اضافه شدن Lazy Loading به نگارش 2.1

برخلاف نگارش‌های پیشین EF، اینبار Lazy loading به صورت پیش‌فرض فعال نیست که در بسیاری از موارد یک مزیت مهم، در جهت بهبود کارآیی برنامه به حساب می‌آید؛ چون پیشتر مدام می‌بایستی توسط ابزارهای profiler، برنامه را بررسی می‌کردیم تا از وجود مشکلی به نام select n+1 مطلع می‌شدیم (lazy loading اشتباه، در جائی که نیازی به آن نبوده و رفت و برگشت بیش از اندازه‌ا‌ی را به بانک اطلاعاتی سبب شده‌است).
برای فعالسازی lazy loading در EF Core 2.1 (اگر واقعا به آن نیاز دارید البته) دو روش وجود دارد:
الف) فعالسازی Lazy loading توسط Proxyها
در این حالت ابتدا نیاز است بسته‌ی نیوگت Microsoft.EntityFrameworkCore.Proxies را نصب کنید. سپس در متد OnConfiguring مربوط به Context برنامه، متد UseLazyLoadingProxies را فراخوانی نمائید:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);
و یا اینکار در فایل آغازین برنامه نیز میسر است:
    .AddDbContext<BloggingContext>(
        b => b.UseLazyLoadingProxies()
              .UseSqlServer(myConnectionString));
اکنون EF Core 2.1 خواص راهبری (navigation properties) را که قابل بازنویسی باشند (همان مباحث AOP و تشکیل پروکسی‌ها)، lazy load می‌کند.
این خواص نیز حتما باید به صورت virtual معرفی شوند تا قابلیت بازنویسی را داشته باشند؛ مانند:
public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public virtual Blog Blog { get; set; }
}
در این مثال با فعال بودن lazy loading، به محض لمس خاصیت Blog، اطلاعات مرتبط با آن از بانک اطلاعاتی واکشی خواهند شد و نه پیش از آن مانند eager loading که تمام اطلاعات وابسته‌ی به یک موجودیت را نیز واکشی می‌کند.
هرچند این قابلیت بارگذاری اطلاعات وابسته در آینده، جذاب به نظر می‌رسد اما در عمل در حین رندر یک گرید و یا بکارگیری حلقه‌ها، چون سبب رفت و برگشت بیش از اندازه‌ای به بانک اطلاعاتی خواهد شد، باید با دقت مورد استفاده قرار گیرد و اساسا استفاده‌ی از آن در برنامه‌های وب توصیه نمی‌شود (با بررسی‌های پروژه‌های بسیاری مشخص شده‌است که این قابلیت ضررش بیشتر از نفعش است).


ب) فعالسازی Lazy loading بدون استفاده از Proxyها
در این حالت نیازی به نصب بسته‌ی AOP جدید تشکیل پروکسی‌ها نیست. در اینجا در کلاس موجودیت خود باید سرویس ILazyLoader را تزریق کنید:
public class Blog
{
    private ICollection<Post> _posts;
public Blog()
    {
    }
private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
    public string Name { get; set; }
public ICollection<Post> Posts
    {
        get => LazyLoader?.Load(this, ref _posts);
        set => _posts = value;
    }
}
public class Post
{
    private Blog _blog;
public Post()
    {
    }
private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }
private ILazyLoader LazyLoader { get; set; }
public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
public Blog Blog
    {
        get => LazyLoader?.Load(this, ref _blog);
        set => _blog = value;
    }
}
در این روش نیازی به virtual معرفی کردن خواص راهبری نیست. اما در این حالت به علت استفاده‌ی از سرویس ILazyLoader، نیاز خواهید داشت تا بسته‌ی نیوگت Microsoft.EntityFrameworkCore.Abstractions را نیز نصب کنید.
نظرات مطالب
EF Code First #7
ممنون از مطلب خوبتان
من رابطه مشتری و آدرس را متوجه نشدم یعنی هر آدرس میتواند برای چند مشتری باشد و آیا کلید جدول آدرس کلید خارجی هم هست اگه ممکنه بیشتر توضیح بدید ممنون
مطالب
تشخیص اصالت ردیف‌های یک بانک اطلاعاتی در EF Core
همیشه فرض بر این است که مدیر سیستم، فردی است امین و درستکار. این شخص/اشخاص کارهای شبکه، پشتیبان‌گیری، نگهداری و امثال آن‌را انجام داده و از سیستم‌ها محافظت می‌کنند. اکنون این سناریوهای واقعی را درنظر بگیرید:
- پس از خداحافظی با شرکتی که در آن کار می‌کردی، شخصی با پوزخند به شما می‌گوید که «می‌دونستی در برنامه‌ی حق و دستمزد شما، بچه‌های ادمین شبکه، دیتابیس برنامه رو مستقیما دستکاری می‌کردند و تعداد ساعات کاری بیشتری رو وارد می‌کردند»؟!
- مسئول فروشی/مسئول پذیرشی که یاد گرفته چطور به صورت مستقیم به بانک اطلاعاتی دسترسی پیدا کند و آمار فروش/پذیرش روز خودش را در بانک اطلاعاتی، با دستکاری مستقیم و خارج از برنامه، کمتر از مقدار واقعی نمایش دهد.
- باز هم مدیر سیستمی/شبکه‌ای که دسترسی مستقیم به بانک اطلاعاتی دارد، در ساعاتی مشخص، کلمه‌ی عبور هش شده‌ی خودش را مستقیما، بجای کلمه‌ی عبور ادمین برنامه در بانک اطلاعاتی وارد کرده و پس از آن ...

این موارد متاسفانه واقعی هستند! اکنون سؤال اینجا است که آیا برنامه‌ی شما قادر است تشخیص دهد رکوردهایی که هم اکنون در بانک اطلاعاتی ثبت شده‌اند، واقعا توسط برنامه و تمام سطوح دسترسی که برای آن طراحی کرده‌اید، به این شکل درآمده‌اند، یا اینکه توسط اشخاصی به صورت مستقیم و با دور زدن کامل برنامه، از طریق management studioهای مختلف، در سیستم وارد و دستکاری شده‌اند؟! در ادامه راه حلی را برای بررسی این مشکل مهم، مرور خواهیم کرد.


چگونه تغییرات رکوردها را در بانک‌های اطلاعاتی ردیابی کنیم؟

روش متداولی که برای بررسی تغییرات رکوردها مورد استفاده قرار می‌گیرد، هش کردن تمام اطلاعات یک ردیف از جدول است و سپس مقایسه‌ی این هش‌ها با هم. علت استفاده‌ی از الگوریتم‌های هش نیز، حداقل به دو علت است:
- با تغییر حتی یک بیت از اطلاعات، مقدار هش تولید شده تغییر می‌کند.
- طول نهایی مقدار هش شده‌ی اطلاعاتی حجیم، بسیار کم است و به راحتی توسط بانک‌های اطلاعاتی، قابل مدیریت و جستجو است.

اگر از SQL Server استفاده می‌کنید، یک چنین قابلیتی را به صورت توکار به همراه دارد:
SELECT
    [Id], 
    (SELECT top 1  * FROM  [AppUsers] FOR XML auto),
    HASHBYTES ('SHA2_256', (SELECT top 1  * FROM  [AppUsers] FOR XML auto)) AS [hash] -- varbinary(n), since 2012
FROM
    [AppUsers]
با این خروجی


کاری که این کوئری انجام می‌دهد شامل دو مرحله است:
الف) کوئری "SELECT top 1 * FROM [AppUsers] FOR XML auto" کاری شبیه به serialization را انجام می‌دهد. همانطور که مشاهده می‌کنید، نام و مقادیر تمام فیلدهای یک ردیف را به صورت یک خروجی XML در می‌آورد. بنابراین دیگر نیازی نیست تا کار تبدیل مقادیر تمام ستون‌های یک ردیف را به عبارتی قابل هش، به صورت دستی انجام دهیم؛ رشته‌ی XML ای آن هم اکنون آماده‌است.
ب) متد HASHBYTES، این خروجی serialized را با الگوریتم SHA2_256، هش می‌کند. الگوریتم‌های SHA2_256 و همچنین SHA2_512، از سال 2012 به بعد به SQL Server اضافه شده‌اند.

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


چگونه تغییرات رکوردها را در بانک‌های اطلاعاتی، توسط EF Core ردیابی کنیم؟

مزیت روش فوق، توکار بودن آن است که کارآیی فوق العاده‌ای را نیز به همراه دارد. اما چون در ادامه قصد داریم از یک ORM استفاده کنیم و ORMها نیز قرار است توانایی کار کردن با انواع و اقسام بانک‌های اطلاعاتی را داشته باشند، دو مرحله‌ی serialization و هش کردن را در کدهای برنامه و با مدیریت EF Core، مستقل از بانک اطلاعاتی خاصی، انجام خواهیم داد.


معرفی موجودیت‌های برنامه

در مثالی که بررسی خواهیم کرد، دو موجودیت Blog و Post تعریف شده‌اند:
using System.Collections.Generic;

namespace EFCoreRowIntegrity
{
    public interface IAuditableEntity
    {
        string Hash { set; get; }
    }

    public static class AuditableShadowProperties
    {
        public static readonly string CreatedDateTime = nameof(CreatedDateTime);
        public static readonly string ModifiedDateTime = nameof(ModifiedDateTime);
    }

    public class Blog : IAuditableEntity
    {
        public int BlogId { get; set; }
        public string Url { get; set; }

        public List<Post> Posts { get; set; }

        public string Hash { get; set; }
    }

    public class Post : IAuditableEntity
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }

        public string Hash { get; set; }
    }
}
- در اینجا اینترفیس IAuditableEntity را نیز مشاهده می‌کنید که دارای یک خاصیت Hash است. تمام موجودیت‌هایی که قرار است دارای فیلد هش باشند، نیاز است این اینترفیس را پیاده سازی کنند؛ مانند دو موجودیت Blog و Post. در ادامه مقدار خاصیت هش را به صورت خودکار توسط سیستم Tracking، محاسبه و به روز رسانی می‌کنیم.
- به علاوه جهت تکمیل بحث، دو خاصیت سایه‌ای نیز تعریف شده‌اند تا بررسی کنیم که آیا هش این‌ها نیز درست محاسبه می‌شود یا خیر.
- علت اینکه خاصیت Hash، سایه‌ای تعریف نشد، سهولت دسترسی و بالا بردن کارآیی آن بود.



معرفی ظرفی برای نگهداری نام خواص و مقادیر متناظر با یک موجودیت

در ادامه دو کلاس AuditEntry و AuditProperty را مشاهده می‌کنید:
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.ChangeTracking;

namespace EFCoreRowIntegrity
{
    public class AuditEntry
    {
        public EntityEntry EntityEntry { set; get; }
        public IList<AuditProperty> AuditProperties { set; get; } = new List<AuditProperty>();

        public AuditEntry() { }

        public AuditEntry(EntityEntry entry)
        {
            EntityEntry = entry;
        }
    }

    public class AuditProperty
    {
        public string Name { set; get; }
        public object Value { set; get; }

        public bool IsTemporary { set; get; }
        public PropertyEntry PropertyEntry { set; get; }

        public AuditProperty() { }

        public AuditProperty(string name, object value, bool isTemporary, PropertyEntry property)
        {
            Name = name;
            Value = value;
            IsTemporary = isTemporary;
            PropertyEntry = property;
        }
    }
}
زمانیکه توسط سیستم Tracking، موجودیت‌های اضافه شده و یا ویرایش شده را استخراج می‌کنیم، AuditEntry همان موجودیت در حال بررسی است که دارای تعدادی خاصیت یا AuditProperty می‌باشد. این‌ها را توسط دو کلاس فوق برای عملیات بعدی، ذخیره و نگهداری می‌کنیم.


معرفی روشی برای هش کردن مقادیر یک شیء

زمانیکه توسط سیستم Tracking، در حال کاربر بر روی موجودیت‌های اضافه شده و یا ویرایش شده هستیم، می‌خواهیم فیلد هش آن‌ها را نیز به صورت خودکار ویرایش و مقدار دهی کنیم. کلاس زیر، منطق ارائه دهنده‌ی این مقدار هش را بیان می‌کند:
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Newtonsoft.Json;

namespace EFCoreRowIntegrity
{
    public static class HashingExtensions
    {
        public static string GenerateObjectHash(this object @object)
        {
            if (@object == null)
            {
                return string.Empty;
            }

            var jsonData = JsonConvert.SerializeObject(@object, Formatting.Indented);
            using (var hashAlgorithm = new SHA256CryptoServiceProvider())
            {
                var byteValue = Encoding.UTF8.GetBytes(jsonData);
                var byteHash = hashAlgorithm.ComputeHash(byteValue);
                return Convert.ToBase64String(byteHash);
            }
        }

        public static string GenerateEntityEntryHash(this EntityEntry entry, string propertyToIgnore)
        {
            var auditEntry = new Dictionary<string, object>();
            foreach (var property in entry.Properties)
            {
                var propertyName = property.Metadata.Name;
                if (propertyName == propertyToIgnore)
                {
                    continue;
                }
                auditEntry[propertyName] = property.CurrentValue;
            }
            return auditEntry.GenerateObjectHash();
        }

        public static string GenerateEntityHash<TEntity>(this DbContext context, TEntity entity, string propertyToIgnore)
        {
            return context.Entry(entity).GenerateEntityEntryHash(propertyToIgnore);
        }
    }
}
- در اینجا توسط متد JsonConvert.SerializeObject کتابخانه‌ی Newtonsoft.Json، شیء موجودیت را تبدیل به یک رشته‌ی JSON کرده و توسط الگوریتم SHA256 هش می‌کنیم. در آخر هم این مقدار را به صورت Base64 ارائه می‌دهیم.
- نکته‌ی مهم: ما نمی‌خواهیم تمام خواص یک موجودیت را هش کنیم. برای مثال اگر موجودیتی دارای چندین رابطه با جداول دیگری بود، ما مقادیر این‌ها را هش نمی‌کنیم (چون رکوردهای متناظر با آن‌ها در جداول خودشان می‌توانند دارای فیلد هش مخصوصی باشند). بنابراین یک Dictionary را از خواص و مقادیر متناظر با آن‌ها تشکیل داده و این Dictionary را تبدیل به JSON می‌کنیم.
- همچنین در این بین، مقدار خود فیلد Hash یک شیء نیز نباید در هش محاسبه شده، حضور داشته باشد. به همین جهت پارامتر propertyToIgnore را مشاهده می‌کنید.


معرفی Context برنامه که کار هش کردن خودکار موجودیت‌ها را انجام می‌دهد

اکنون نوبت استفاده از تنظیمات انجام شده‌ی تا این مرحله‌است:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Logging;

namespace EFCoreRowIntegrity
{
    public class BloggingContext : DbContext
    {
        public BloggingContext()
        { }

        public BloggingContext(DbContextOptions options)
            : base(options)
        { }

        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.EnableSensitiveDataLogging();
                var path = Path.Combine(Directory.GetCurrentDirectory(), "app_data", "EFCore.RowIntegrity.mdf");
                optionsBuilder.UseSqlServer($"Server=(localdb)\\mssqllocaldb;Database=EFCore.RowIntegrity;AttachDbFilename={path};Trusted_Connection=True;");
                optionsBuilder.UseLoggerFactory(new LoggerFactory().AddConsole((message, logLevel) =>
                logLevel == LogLevel.Debug &&
                           message.StartsWith("Microsoft.EntityFrameworkCore.Database.Command")));
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            foreach (var entityType in modelBuilder.Model
                                                   .GetEntityTypes()
                                                   .Where(e => typeof(IAuditableEntity)
                                                   .IsAssignableFrom(e.ClrType)))
            {
                modelBuilder.Entity(entityType.ClrType)
                            .Property<DateTimeOffset?>(AuditableShadowProperties.CreatedDateTime);
                modelBuilder.Entity(entityType.ClrType)
                            .Property<DateTimeOffset?>(AuditableShadowProperties.ModifiedDateTime);
            }
        }

        public override int SaveChanges()
        {
            var auditEntries = OnBeforeSaveChanges();
            var result = base.SaveChanges();
            OnAfterSaveChanges(auditEntries);
            return result;
        }

        private IList<AuditEntry> OnBeforeSaveChanges()
        {
            var auditEntries = new List<AuditEntry>();

            foreach (var entry in ChangeTracker.Entries<IAuditableEntity>())
            {
                if (entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
                {
                    continue;
                }

                var auditEntry = new AuditEntry(entry);
                auditEntries.Add(auditEntry);

                var now = DateTimeOffset.UtcNow;

                foreach (var property in entry.Properties)
                {
                    var propertyName = property.Metadata.Name;
                    if (propertyName == nameof(IAuditableEntity.Hash))
                    {
                        continue;
                    }

                    if (property.IsTemporary)
                    {
                        // It's an auto-generated value and should be retrieved from the DB after calling the base.SaveChanges().
                        auditEntry.AuditProperties.Add(new AuditProperty(propertyName, null, true, property));
                        continue;
                    }

                    switch (entry.State)
                    {
                        case EntityState.Added:
                            entry.Property(AuditableShadowProperties.CreatedDateTime).CurrentValue = now;
                            auditEntry.AuditProperties.Add(new AuditProperty(propertyName, property.CurrentValue, false, property));
                            break;
                        case EntityState.Modified:
                            auditEntry.AuditProperties.Add(new AuditProperty(propertyName, property.CurrentValue, false, property));
                            entry.Property(AuditableShadowProperties.ModifiedDateTime).CurrentValue = now;
                            break;
                    }
                }
            }

            return auditEntries;
        }

        private void OnAfterSaveChanges(IList<AuditEntry> auditEntries)
        {
            foreach (var auditEntry in auditEntries)
            {
                foreach (var auditProperty in auditEntry.AuditProperties.Where(x => x.IsTemporary))
                {
                    // Now we have the auto-generated value from the DB.
                    auditProperty.Value = auditProperty.PropertyEntry.CurrentValue;
                    auditProperty.IsTemporary = false;
                }
                auditEntry.EntityEntry.Property(nameof(IAuditableEntity.Hash)).CurrentValue =
                    auditEntry.AuditProperties.ToDictionary(x => x.Name, x => x.Value).GenerateObjectHash();
            }
            base.SaveChanges();
        }
    }
}
در اینجا اصل کار، در متد بازنویسی شده‌ی SaveChanges انجام می‌شود:
public override int SaveChanges()
{
    var auditEntries = OnBeforeSaveChanges();
    var result = base.SaveChanges();
    OnAfterSaveChanges(auditEntries);
    return result;
}
در متد OnBeforeSaveChanges، تمام موجودیت‌های تغییر کرده‌ی از نوع IAuditableEntity را که دارای فیلد هش هستند، یافته و نام خاصیت و مقدار متناظر با آن‌ها را در ظرف‌های AuditEntry که پیشتر معرفی شدند، ذخیره می‌کنیم. هنوز در این مرحله کار هش کردن را انجام نخواهیم داد. علت را می‌توانید در بررسی خواص موقتی مشاهده کنید:
if (property.IsTemporary)
{
   // It's an auto-generated value and should be retrieved from the DB after calling the base.SaveChanges().
   auditEntry.AuditProperties.Add(new AuditProperty(propertyName, null, true, property));
   continue;
}
خواص موقتی، عموما تولید شده‌ی توسط دیتابیس هستند. برای مثال زمانیکه یک Id عددی خود افزاینده را به عنوان کلید اصلی جدول معرفی می‌کنید، مقدار آن پس از فراخوانی متد base.SaveChanges، از بانک اطلاعاتی دریافت شده و در اختیار برنامه قرار می‌گیرد. به همین جهت است که نیاز داریم لیست این خواص و مقادیر را یکبار پیش از base.SaveChanges ذخیره کنیم و پس از آن، خواص موقتی را که اکنون دارای مقدار هستند، مقدار دهی کرده و سپس هش نهایی شیء را محاسبه کنیم. اگر پیش از base.SaveChanges این هش را محاسبه کنیم، برای مثال حاوی مقدار Id شیء، نخواهد بود.

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


روش بررسی اصالت یک موجودیت

در متد زیر، روش محاسبه‌ی هش واقعی یک موجودیت دریافت شده‌ی از بانک اطلاعاتی را توسط متد الحاقی GenerateEntityHash مشاهده می‌کنید. اگر این هش واقعی (بر اساس مقادیر فعلی این ردیف که حتی ممکن است به صورت دستی و خارج از برنامه تغییر کرده باشد)، با مقدار Hash ثبت شده‌ی پیشین در آن ردیف یکی بود، اصالت این ردیف تائید خواهد شد:
private static void CheckRow1IsAuthentic()
{
    using (var context = new BloggingContext())
    {
        var blog1 = context.Blogs.Single(x => x.BlogId == 1);
        var entityHash = context.GenerateEntityHash(blog1, propertyToIgnore: nameof(IAuditableEntity.Hash));
        var dbRowHash = blog1.Hash;
        Console.WriteLine($"entityHash: {entityHash}\ndbRowHash:  {dbRowHash}");
        if (entityHash == dbRowHash)
        {
            Console.WriteLine("This row is authentic!");
        }
        else
        {
            Console.WriteLine("This row is tampered outside of the application!");
        }
    }
}
یک نمونه خروجی آن به صورت زیر است:
entityHash: P110cYquWpoaZuTpCWaqBn6HPSGdoQdmaAN05s1zYqo=
dbRowHash: P110cYquWpoaZuTpCWaqBn6HPSGdoQdmaAN05s1zYqo=
This row is authentic!

اکنون بانک اطلاعاتی را خارج از برنامه، مستقیما دستکاری می‌کنیم و برای مثال Url اولین ردیف را تغییر می‌دهیم:


در ادامه یکبار دیگر برنامه را اجرا خواهیم کرد:
entityHash: tdiZhKMJRnROGLLam1WpldA0fy/CbjJaR2Y2jNU9izk=
dbRowHash: P110cYquWpoaZuTpCWaqBn6HPSGdoQdmaAN05s1zYqo=
This row is tampered outside of the application!
همانطور که مشاهده می‌کنید، هش واقعی جدید، با هش ثبت شده‌ی در ردیف، یکی نیست؛ که بیانگر ویرایش مستقیم این ردیف می‌باشد.
به علاوه باید درنظر داشت، محاسبه‌ی این هش بدون خود برنامه، کار ساده‌ای نیست. به همین جهت به روز رسانی دستی آن تقریبا غیرممکن است؛ خصوصا اگر متد GenerateObjectHash، کمی با پیچ و تاب بیشتری نیز تهیه شود.


چگونه وضعیت اصالت تعدادی ردیف را بررسی کنیم؟

مثال قبل، در مورد روش بررسی اصالت یک تک ردیف بود. کوئری زیر روش محاسبه‌ی فیلد جدید IsAuthentic را در بین لیستی از ردیف‌ها نمایش می‌دهد:
var blogs = (from blog in context.Blogs.ToList() // Note: this `ToList()` is necessary here for having Shadow properties values, otherwise they will considered `null`.
             let computedHash = context.GenerateEntityHash(blog, nameof(IAuditableEntity.Hash))
             select new
             {
               blog.BlogId,
               blog.Url,
               RowHash = blog.Hash,
               ComputedHash = computedHash,
               IsAuthentic = blog.Hash == computedHash
             }).ToList();


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: EFCoreRowIntegrity.zip
مطالب
آموزش مفاهیم Data Warehouse

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

روش ساخت MDB  :

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

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

 

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

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

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

1. Data Mining

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

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

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

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

Customer Relationship Management (CRM) :

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

Operational Data Store (ODS) :

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

Master Data Management (MDM)  :

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

1. transaction data

2. master data

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

تصویر Single DDS :

تصویر NDS + DDS :

تصویر ODS + DDS :

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

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

مطالب
آموزش مهندسی نرم افزار و UML - جلسه دوم
جلسه دوم :

در جلسه پیش در مورد اینکه چرا یک بسته نرم افزاری را باید به عنوان یک سیستم در نظر بگیریم  صحبت کردیم در این جلسه به بررسی سیستم‌های اطلاعاتی می‌پردازیم.
قبل از اینکه به بررسی سیستم ‌های اطلاعاتی بپردازیم به چند مفهوم  می‌پردازیم که برای تعریف سیستم‌های اطلاعاتی به آن‌ها نیازمندیم.

· داده – Data : داده خام پردازش نشده ای که از نظر سیستم مفهومی ندارد.
· اطلاعات  - Information : داده‌های پردازش شده ای که از نظر سیستم دارای مفهوم خاصی می‌باشند.
· Knowledge : مانند Information  دارای مفهوم خاصی هستند اما کمی ساخت یافته‌تر گشته و دارای معنی بیشتری هستند و اغلب در سیستم‌های خبره به کار می‌روند.

تعریف سیستم‌های اطلاعاتی (Information Service)

سیستم هایی هستند که ورودی آنها اطلاعات خام پردازش نشده (Data) و خروجی آنها Information  می‌باشد ؛ عمل اصلی این سیستم‌ها پردازش اطلاعات است .


انواع سیستم‌های اطلاعاتی :

1.   TPS  (Transaction Processing Systems)
عملکرد اصلی TPS ‌ها پردازش اطلاعات است.

2.   MIS (Management Information Services)
اطلاعات را برای مدیران سطح بالا پردازش می‌کنند و آ ن‌ها را در تصمیم گیری‌ها یاری می‌دهند.

در ادامه به بررسی مشکلات سیستم‌های اطلاعاتی یا همان بسته‌های نرم افزاری خواهیم پرداخت و راهکاری را که IT برای فایق آمدن به این مشکلات بیان کرده اند را شرح خواهم داد.


برخی مشکلات توسعه سیستم‌های اطلاعاتی (IS) :

1.   قیمت پیشنهادی از سوی کارفرما
2.   زمان تحویل سیستم غیر معقول باشد
3.   هزینه استقرار سیستم بالا باشد
4.   تغییر نیازمندی ها
5.   کمبود تجربه و تخصص نیروی فنی
6.   غیر ممکن بودن پیاده سازی یک سیستم از لحاظ تکنیکی
7.   اندازه گیری میزان حرکت پروژه در راستای هدف خود
8.   پروژه تا چه اندازه نیازمندی‌های کاربران را پاسخ می‌دهد
9.   سختی کار با سیستم
10. سیستم از ورود اطلاعات نامعتبر جلوگیری نکند
11. پیغام‌های خطای نامناسب
12. Help نامناسب
13. غیر قابل اعتماد بودن عملیات‌های سیستم
14. زمان پاسخ گویی نامناسب
15.  ...
 

قبل از اینکه به بیان راهکار IT  در این رابطه بپردازیم به تعریفی کوتاه از آن توجه کنید.

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

در ادامه به بررسی متدولوژی خواهیم پرداخت.