مطالب
کار با دیتاتایپ JSON در MySQL - قسمت چهارم
MySQL قادر به ایندکس کردن ستون‌های JSON نمی‌باشد. برای حل این مشکل میتوانیم از generated columnها استفاده کنیم. منظور، ایجاد ستون‌هایی است که مقدارشان به صورت محاسبه شده و براساس ستون‌های دیگر میباشد؛ به عنوان مثال جدول کاربران زیر را در نظر بگیرید:
CREATE TABLE `Users` (
  id int NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  gender ENUM('Male','Female') NOT NULL,
  PRIMARY KEY (`id`)
)
برای کوئری گرفتن full name در حالت معمول میتوانیم از تابع CONCAT استفاده کنیم:
SELECT 
    *, CONCAT(first_name, '', last_name) AS full_name
FROM
    Users;
اما توسط generated columns میتوانیم یک ستون را به جدول کاربران اضافه کنیم که مقدارش براساس دو فیلد first_name و last_name محاسبه و مقدار دهی شود:
ALTER TABLE Users
ADD COLUMN full_name TEXT GENERATED ALWAYS 
AS (CONCAT(first_name, ' ', last_name))
همانطور که مشاهده میکنید از سینتکس GENERATE ALWAYS برای ایجاد generated column استفاده شده‌است. در MySQL دو نوع generated column وجود دارد: STORED و VIRTUAL؛ تفاوت آنها نیز در نحوه ذخیره‌سازی است. در حالت VIRTUAL که حالت پیش‌فرض است، مقادیر ذخیره نمیشوند؛ بلکه به صورت on the fly محاسبه و در خروجی نمایش داده خواهند شد. در حالیکه نوع STORED همانطور که از نامش پیداست، ذخیره خواهند شد؛ در نتیجه قابلیت ایندکس‌گذاری را دارد. برای تعیین نوع ستون نیز سینتکس آن اینگونه خواهد بود:
ALTER TABLE Users
ADD COLUMN full_name TEXT GENERATED ALWAYS 
AS (CONCAT(first_name, ' ', last_name)) STORED

همچنین لازم به ذکر است که حین استفاده از generated columns باید نکات زیر را در نظر داشته باشید:
  • generated columnsها نمیتوانند شامل subqueries, parameters, variables, stored procedure, user-defined functions باشند.
  • بر روی یک ستون generated نمیتوان AUTO_INCREMENT گذاشت یا اینکه از یک ستون AUTO_INCREMENT برای محاسبه generated column استفاده کرد.
  • کلیدهای خارجی‌ای که در generated columnsها استفاده میشوند، قابلیت استفاده از CASCADE, SET NULL, or SET DEFAULT as ON UPDATE or ON DELETE را نخواهند داشت.

در ادامه یک generated column را برای جدول productsMetadata تعیین خواهیم کرد: 
ALTER TABLE productMetadata
ADD COLUMN id INT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.id'))) STORED NOT NULL

بنابراین زمانیکه یک مقدار JSON را ذخیره میکنیم، کلید اصلی از path تعیین شده استخراج شده و به عنوان یک computed column برای این جدول تعیین خواهد شد. در ادامه میتوانید جزئیات تغییر فوق را مشاهده کنید: 

  
اکنون کوئری زیر را در نظر بگیرید که رکوردی با آی‌دی ۱ را بازیابی خواهد کرد:
SELECT data ->> "$.description.shortDescription" FROM productMetadata
WHERE id = 1;
از آنجائیکه هیچ ایندکسی برای این فیلد جدید لحاظ نشده است، MySQL کل ردیف‌ها را برای یافتن id موردنظر جستجو خواهد کرد. این مورد را میتوانید با دستور EXPLAIN نیز مشاهده کنید:


همانطور که مشاهده میکنید مقدار type به ALL تنظیم شده‌است؛ همچنین مقدار rows نیز تعداد ردیف‌های جدول است که در اینجا ۱۳ ردیف دیتا را داریم. قاعدتاً با اضافه شدن دیتای جدید به جدول، جستجو نیز به مراتب کندتر خواهد شد. بنابراین با اضافه کردن ایندکس میتوانیم مشکل این کند بودن را رفع کنیم. به همین جهت در ادامه یک ایندکس را براساس ستون id که یک generated column است ایجاد خواهیم کرد:

CREATE INDEX idx_json_data ON productMetadata (id);

اکنون اگر یکبار دیگر کوئری قبلی را اجرا کنیم، خواهیم دید که تعداد rows به ۱ و همچنین type به ref ست شده‌اند:



نظرات مطالب
SQL Injection چیست؟
وقتی شما از linq استفاده کنید نفوذ از طریق sql injection به شدت کاهش پیدا می‌کنه
این لینک رو که توسط آقای نصیری  توضیح داده شده مطالعه کنید
امنیت در LINQ to SQL
نظرات مطالب
افزودن یک DataType جدید برای نگه‌داری تاریخ خورشیدی - 3
SQLCLR types به صورت پیش فرض با فرمت serialized binary value بازگشت داده می‌شوند.
 SELECT id, TestDate.ToString() FROM TestTable;
در ابزارهای کوئری گرفتن ad-hoc مثل SSMS باید یک متد ToString را هم به انتهای نام ستون اضافه کنید تا مقدار نمایشی واضحی حاصل شود.
مطالب
لینک‌های هفته‌ی آخر بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

امنیت

Visual Studio

ASP. Net

طراحی و توسعه وب

اس‌کیوال سرور

سی شارپ

عمومی دات نت

مسایل اجتماعی و انسانی برنامه نویسی

کتاب‌های رایگان جدید

متفرقه
مطالب
تعامل با پایگاه داده با استفاده از EntityFramework در پروژه های F# MVC 4
در پست‌های قبلی (^ و^) با  template و ساخت کنترلر و مدل در پروژه‌های F# MVC آشنا شدید. در این پست به طراحی Repository با استفاده از EntityFramework خواهم پرداخت. در ادامه مثال قبل، برای تامین داده‌های مورد نیاز کنترلر‌ها و نمایش آن‌ها در View نیاز به تعامل با پایگاه داده وجود دارد. در نتیجه با استفاده از الگوی Repository، داده‌های مورد نظر را تامین خواهیم کرد. به صورت پیش فرض با نصب Template جاری (F# MVC4) تمامی اسمبلی‌های مورد نیاز برای استفاده از  در EF در پروژه‌های #F نیز نصب می‌شود.

پیاده سازی DbContext مورد نیاز
برای ساخت DbContext می‌توان به صورت زیر عمل نمود:
namespace FsWeb.Repositories

open System.Data.Entity
open FsWeb.Models

type FsMvcAppEntities() = 
    inherit DbContext("FsMvcAppExample")

    do Database.SetInitializer(new CreateDatabaseIfNotExists<FsMvcAppEntities>())

    [<DefaultValue()>] val mutable books: IDbSet<Guitar>
    member x.Books with get() = x.books and set v = x.books <- v
همان طور که ملاحظه می‌کنید  با ارث بری از کلاس DbContext  و پاس دادن ConnectionString یا نام آن در فایل app.config، به راحتی FsMVCAppEntities ساخته می‌شود که معادل DbContext پروژه مورد نظر است. با استفاده از دستور do متد SetInitializer برای عملیات migration فراخوانی می‌شود. در پایان نیز یک DbSet به نام Books ایجاد کردیم. فقط از نظر syntax با حالت #C آن تفاوت دارد اما روش پیاده سازی مشابه است.

اگر syntax زبان #F برایتان نامفهوم است می‌توانید از این دوره کمک بگیرید.

پیاده سازی کلاس BookRepository
ابتدا به کد‌های زیر دقت کنید:
namespace FsWeb.Repositories

type BooksRepository() =
    member x.GetAll () = 
        use context = new FsMvcAppEntities() 
        query { for g in context.Books do
                select g }
        |> Seq.toList
در کد بالا ابتدا تابعی به نام GetAll داریم. در این تابع یک نمونه از DbContext پروژه وهله سازی می‌شود. نکته مهم این است به جای شناسه let از شناسه use استفاده کردم. شناسه use دقیقا معال دستور {}()using در #C است. بعد از اتمام عملیات شی مورد نظر Dispose خواهد شد.
در بخش بعدی بک کوئری از DbSet مورد نظر گرفته می‌شود. این روش Query گرفتن در F# 3.0 مطرح شده است. در نتیجه در نسخه‌های قبلی آن (F# 2.0) اجرای این کوئری باعث خطا می‌شود. اگر قصد دارید با استفاده از F# 2.0 کوئری‌های خود را ایجاد نماید باید به طریق زیر عمل نمایید:
ابتدا از طریق nuget اقدام به نصب package  ذیل نمایید:
FSPowerPack.Linq.Community
سپس در ابتدا Source File خود، فضای نام Microsoft.FSharp.Linq.Query را باز(استفاده از دستور open) کنید. سپس می‌توانید با اندکی تغییر در کوئری قبلی خود، آن را در F# 2.0 اجرا نمایید.
query <@ seq { for g in context.Books -> g } @> |> Seq.toList
حال باید Repository طراحی شده را در کنترلر مورد نظر فراخوانی کرد. اما اگر کمی سلیقه به خرج دهیم به راحتی می‌توان با استفاده از تزریق وابستگی ، BookRepository را در اختیار کنترلر قرار داد. همانند کد ذیل:
[<HandleError>]
type BooksController(repository : BooksRepository) =
    inherit Controller()
    new() = new BooksController(BooksRepository())
    member this.Index () =
        repository.GetAll()
        |> this.View
در کد‌های بالا ابتدا وابستگی به BookRepository در سازنده BookController تعیین شد. سپس با استفاده از سازنده پیش فرض، یک وهله از وابستگی مورد نظر ایجاد و در اختیار سازنده کنترلر قرار گرفت(همانند استفاده از کلمه this در سازنده کلاس‌های #C). با فراخوانی تابع GetAll  داده‌های مورد نظر از database تامین خواهد شد.

نکته : تنظیمات مروط به ConnectionString را فراموش نکنید:
<add name="FsMvcAppExample"
     connectionString="YOUR CONNECTION STRING"
     providerName="System.Data.SqlClient" />
موفق باشید.

مطالب
سرورهای متصل شده‌ی SQL Server و مبحث تراکنش‌ها

یکی از قابلیت‌های جالب SQL Server در یک شبکه محلی امکان link و اتصال آن‌ها به یکدیگر است. به این صورت امکان کوئری گرفتن (و یا اعمال متداول SQL ایی) از دو یا چند سرور مختلف با دستورات T-SQL میسر می‌شود؛ به نحوی که حس یکپارچگی دیتابیس‌های این سرورها را حین کوئری نوشتن خواهیم داشت.
برای مثال فرض کنید دو سرور SQL1 و SQL2 را در شبکه داریم. می‌خواهیم در سرور SQL1 اتصالی را به سرور SQL2 ایجاد کنیم.

USE master

EXEC sp_addlinkedserver
'SQL2',
N'SQL Server'

sp_addlinkedsrvlogin @useself='false ', @rmtsrvname = 'SQL2',
@rmtuser = 'sa',
@rmtpassword = 'pass#'

دستورات T-SQL فوق کار ثبت یک liked server جدید و اعمال مشخصات کاربری که توسط آن قرار است به سرور SQL2 دسترسی داشت، انجام می‌دهند.
اکنون جهت بررسی این اتصال در سرور SQL1 کوئری زیر را اجرا می‌کنیم:

select * from sql2.faxManager.dbo.tblErja

که نحوه‌ی فراخوانی جدول مورد نظر باید به صورت Server.DatabaseName.dbo.TableName در آن رعایت شود.
تا اینجا همه چیز خوب است. مشکل از زمانی شروع می‌شود که بخواهیم تراکنش‌ها را نیز دخالت دهیم و اصولی کار کنیم. برای مثال:

begin distributed tran
select * from sql2.faxManager.dbo.tblErja
commit tran

خطایی که در ویندوز سرور 2003 با آخرین به روز رسانی‌ها ظاهر می‌شود به صورت زیر است:

The operation could not be performed because OLE DB provider for linked server was unable to begin a distributed transaction.
OLE DB provider for linked server returned message "The partner transaction manager has disabled its support for remote/network transactions.".


به صورت پیش فرض این نوع تراکنش‌های توزیع شده غیرفعال هستند مگر اینکه فعال شوند و روش حل مشکل نیز به صورت زیر می‌باشد:
قبل از هر کاری به کنسول سرویس‌های ویندوز مراجعه کرده و از در حال اجرا بودن سرویس Distribute Transaction Coordinator اطمینان حاصل کنید.
سپس به قسمت زیر مراجعه نمائید:
Control Panel > Administrative Tools > Component Services


نود مربوط به Component Service را گشوده و سپس بر روی My Computer کلیک راست کرده و گزینه‌ی خواص را انتخاب کنید.
در صفحه‌ی بازه شده به برگه‌ی MSTDC مراجعه کرده و بر روی دکمه‌ی Security Configuration کلیک نمائید.
اکنون تنظیمات آن‌را مطابق شکل زیر تغییر دهید.


این تنظیم باید بر روی هر دو سرور SQL1 و SQL2 انجام شود.

پس از این تغییرات که شامل راه اندازی مجدد سرویس Distribute Transaction Coordinator نیز خواهد شد، مشکل خطای فوق برطرف شده و امکان استفاده از تراکنش‌ها در linked servers نیز میسر می‌شود.

مشکل دیگری که به آن برخوردم خطای زیر است:

Unable to start a nested transaction for OLE DB provider for linked server . A nested transaction was required because the XACT_ABORT option was set to OFF.
OLE DB provider for linked server returned message "Cannot start more transactions on this session.".


برای حل این مشکل یک سطر زیر را باید به ابتدای کوئری خود اضافه کرد که جزو الزامات تراکنش‌های توزیع شده است و به این صورت از rollback کامل تمامی دستورات موجود فراخوانی شده T-SQL در صورت بروز کوچکترین خطایی اطمینان حاصل می‌کند:
SET XACT_ABORT ON


برای مطالعه بیشتر:
MSDTC Troubleshooting

نظرات مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
- جدول نظرات اگر با موجودیتی به نام کاربر سر و کار دارد، این خاصیت عموما به صورت virtual تعریف می‌شود یعنی lazy loading قرار است رخ دهد. به عبارتی با ToList اولیه اگر از متدی مانند Include استفاده نشده باشد (برای eager loading اطلاعات وابسته)، به هیچ عنوان خواص lazy بارگذاری نخواهند شد. شاید عنوان کنید که من با استفاده از امکانات دیباگر VS.NET اگر نود مربوط به User رو باز کنم، اطلاعاتش هست. پاسخ این است که بله. دقیقا در همین لحظه که نود رو باز کردید یک کوئری برای دریافت اطلاعات یوزر به سرور ارسال شده و نه پیش از آن. البته می‌شود lazy loading را در EF کلا خاموش کرد یا حتی به صورت مقطعی با استفاده از متدی مانند AsNoTracking. اما حالت پیش فرض دقیقا چیزی است که عنوان شد.
بنابراین درحالت فعلی تمام فیلدهای وابسته از بانک اطلاعاتی استخراج نمی‌شوند مگر اینکه lazy loading را به نحوی تبدیل به eager loading کرده باشید.
- ضمنا در حالت فعلی اگر دقت کرده باشید پیش از ToList اول یک سه نقطه گذاشته شده است. یعنی اینجا می‌تونید where بنویسید. می‌تونید Select بنویسید و به صورت اختصاصی یک سری خاصیت مشخص رو انتخاب کنید و خیلی از کارهای دیگر.
مطالب
اتصال به SQL از راه دور (Remote) و یا به یک سرور در شبکه

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

1. کلاینت (Client): منظور از کلاینت کامپیوتری است که میخواهد به سرور متصل گردد و از SQL کامپیوتر سرور خدماتی را دریافت نماید.

2. سرور (Server): کامپیوتری است که میخواهیم به آن متصل شویم و داده‌ها را بصورت متمرکز بر روی آن ذخیره و بازیابی نماییم.

به دو روش می‌توان به سرور متصل شد:

1. Windows Authentication
در این روش جهت اتصال به بانک اطلاعاتی، کامپیوتر مبدا یا Client باید عضو شبکه ای باشد کهServer در آن وجود دارد. در واقع برای شبکه هایی استفاده می‌شوند که دارای Domain می باشند وClient به عنوان یک کاربر شناخته شده در سرور تعریف شده است.

2. SQL Authentication
در این روش کلاینت به عنوان یک کاربر یا Login در SQL تعریف شده است و دارای نام کاربری و رمز عبور می‌باشد.

جهت اتصال از راه دور به یک سرور دارای SQL، باید تنظیمات زیر را برای کامپیوتر سرور انجام دهیم:

1. به SQL Server کامپیوتر سرور متصل شوید.

2. در پنجره Object Explorer بر روی نام سرور (اولین آیتم موجود در لیست) کلیک راست کنید و گزینهProperties را انتخاب نمایید.

3. در پنجره ظاهر شده (Server Properties) و در قسمت Select a page (سمت چپ پنجره) بر رویSecurity کلیک کنید.

4. در سمت راست پنجره گزینه SQL Server and Windows Authentication mode را انتخاب کنید.

5. دکمه OK را انتخاب کنید. پنجره پیغامی مبنی بر Restart کردن سرور نمایش داده می‌شود. این پنجره را تایید کنید.

6. مجددا بر روی نام سرور کلیک راست کنید و گزینه Restart را انتخاب نموده و در پیغام ظاهر شده Yesرا انتخاب نمایید.

تا به اینجا سرور آماده پذیرش اتصال از راه دور بصورت SQL Authentication می باشد. حال نوبت به تعریف یک Login می باشد تا توسط این Login بتوانید به سرور از راه دور متصل شوید. مراحل زیر را برای تعریفLogin دنبال کنید:

1. در پنجره Object Explorer به مسیر Security > Logins بروید.

2. بر روی پوشه Logins کلیک راست نموده و گزینه New Login… را انتخاب نمایید.

3. در پنجره ظاهر شده در بخش Login name نامی را به کاربر اختصاص دهید. (به عنوان مثال user1)

4. گزینه SQL Server authentication را انتخاب نموده و در بخش Password و Confirm password رمز عبوری را به این کاربر اختصاص دهید. (به عنوان مثال abc123)

5. گزینه Enforce password policy را از حالت انتخاب خارج کنید تا رمز عبور را از قید سیاستهای رمزگذاری ویندوز خارج کنید.

6. در قسمت Select a page (سمت چپ پنجره) بر روی Server Roles کلیک کنید.

7. در سمت راست پنجره گزینه sysadmin یا هر نوع دسترسی دیگری را که مایل هستید انتخاب نمایید.
توجه: با انتخاب sysadmin کاربر ایجاد شده به کل سرور و بانک‌های اطلاعاتی دسترسی کامل یاAdmin دارد. اگر نمیخواهید کاربر چنین دسترسی داشته باشد، در بخش فوق فقط گزینه public انتخاب شده باشد.

8. در قسمت Select a page (سمت چپ پنجره) بر روی User Mapping کلیک کنید. در این بخش نحوه دسترسی کاربر را به بانکهای اطلاعاتی موجود، مشخص می‌کنیم.

9. در سمت راست پنجره و در بخش Users mapped to this login یک یا چند بانک اطلاعاتی را که میخواهید توسط این Login قابل دسترسی باشند را انتخاب نمایید.

10. پس از انتخاب هر بانک اطلاعاتی، در قسمت پایین (Database role membership for:) نوع دسترسی کاربر به آن Database را انتخاب کنید. در اینجا من db_owner را انتخاب می‌کنم تا کاربر دسترسی کامل به بانک اطلاعاتی انتخاب شده را داشته باشد.

11. دکمه OK را انتخاب کنید تا Login مورد نظر ساخته شود.

حالا می‌توانید از راه دور و حتی از روی خود سرور با کاربر ایجاد شده به سرور متصل شوید. برای این منظور SQL را Disconnect نمایید و یا یکبار SQL Server Management Studio (SSMS) را ببندید و دوباره اجرا نمایید. در پنجره Connect to Server اطلاعات زیر را وارد نمایید:

Server name :نام یا IP سرور (به عنوان مثال 192.168.0.1)

Authentication: انتخاب گزینه SQL Server Authentication

Login: طبق مثال user1

Password: طبق مثال abc123

پس از ورود با مشخصات فوق فقط می‌توانید به بانک اطلاعاتی دسترسی باشید که در قسمت User Mapping انتخاب کرده بودید. اگر sysadmin را انتخاب کرده باشید به تمامی بانک‌های اطلاعاتی موجود دسترسی دارید.


برخی مشکلات اتصال از راه دور

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

1. به مسیر Start > All Programs > Microsoft SQL Server 2008/2005 > Configuration Tools > SQL Server Configuration Manager مراجعه کنید و موارد زیر را بررسی نمایید:

1.1. بر روی SQL Server Services کلیک کنید و در سمت راست پنجره بررسی کنید که ستون Stateمربوط به SQL Server Browser و SQL Server در وضعیت Running باشد.

1.2. بر روی آیتم‌های زیر مجموعه SQL Server Network Configuration کلیک کنید و در سمت راست پنجره بررسی کنید که آیتم های Shared Memory، Named Pipes و TCP/IP در وضعیت Enabled باشند.

1.3. بر روی آیتم SQL Native Client Configuration > Client Protocols  کلیک کنید و در سمت راست پنجره بررسی کنید که آیتم های Shared Memory، Named Pipes و TCP/IP در وضعیت Enabled باشند.

2. بررسی کنید که فایروال سیستم سرور غیر فعال باشد و یا SQL Server به برنامه های Trust فایروال اضافه شده باشد. 

نظرات مطالب
React 16x - قسمت 19 - کار با فرم‌ها - بخش 2 - اعتبارسنجی ورودی‌های کاربران
validateProperty یک خاصیت را تعیین اعتبار می‌کند، اما برای تطابق پسوردها، نیاز به تعیین اعتبار دو خاصیت را با هم دارد. به همین جهت برای مثال می‌توان پارامتر چهارمی را به نام correspond، در اینجا اضافه کرد تا فیلد متناظر با آن‌را هم مشخص کند:
  renderInput(name, label, type = "text", correspond) {
    const { data, errors } = this.state;
    return (
      <Input
        name={name}
        type={type}
        label={label}
        value={data[name]}
        onChange={this.handleChange}
        error={errors[name]}
        correspond={correspond}
      />
    );
  }
بعد برای نمونه، تعریف فرم ثبت نام، به صورت زیر تغییر می‌کند که در آن confirmPassword هم اضافه شده و فیلد متناظر با آن، password است:
        <form onSubmit={this.handleSubmit}>
          {this.renderInput("username", "Username")}
          {this.renderInput("password", "Password", "password")}
          {this.renderInput(
            "confirmPassword",
            "Confirm Password",
            "password",
            "password"
          )}
          {this.renderInput("name", "Name")}
          {this.renderButton("Register")}
        </form>
در این حالت، داده‌های فرم و اعتبارسنجی‌های آن، به صورت زیر تعریف می‌شوند:
  state = {
    data: { username: "", password: "", name: "", confirmPassword: "" },
    errors: {},
  };

  schema = {
    username: Joi.string()
      .required()
      .email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "info"] } })
      .label("Username"),
    password: Joi.string().required().min(5).label("Password"),
    name: Joi.string().required().label("Name"),
    confirmPassword: Joi.any().valid(Joi.ref("password")).required().messages({
      "any.only": "با رمز عبور مطابقت ندارد",
    }),
  };
و در آخر متد validateProperty بر اساس attribute سفارشی اضافه شده به نام correspond، در صورت وجود و تعریف آن، یک خاصیت پویا را به شیء داده‌های ورودی و همچنین شیء schema اضافه می‌کند که این‌ها سبب خواهند شد تا اینبار اعتبارسنجی بر روی دو فیلد صورت گیرد:
  validateProperty = ({ name, value, attributes }) => {
    const userInputObject = { [name]: value };
    const schemaMap = { [name]: this.schema[name] };

    if (attributes.correspond) {
      const correspondFieldName = attributes.correspond.value;
      userInputObject[correspondFieldName] = this.state.data[
        correspondFieldName
      ];
      schemaMap[correspondFieldName] = this.schema[correspondFieldName];
    }

    const propertySchema = Joi.object(schemaMap);
    const { error } = propertySchema.validate(userInputObject, {
      abortEarly: true,
    });
    return error ? error.details[0].message : null;
  };