PDA

View Full Version : سوال: مشکل در ثبت اطلاعات در دیتابیس



afshines
پنج شنبه 21 دی 1391, 10:28 صبح
سلام به همه ی دوستان

من دو تا table دارم،اطلاعات مشتری و سفارش اطلاعات مشتری ، تو جدول اطلاعات ثبت میشه و سفارش مشتری هم تو جدول سفارشات برنامه تحت شبکه است، یعنی چند تا pda دارن سفارش میدن ، فیلد id جدول اطلاعات مشتری کلیدیه بین این دوتا جدول relation گذاشتم . وقتی میخوام اطلاعات رو ثبت کنم این احتمال رو میدم که بین سفارشات تداخل ایجاد شه چند تا راه رو امتحان کردم،ولی راه مناسبی نیستند،مثل تراکنش ها(sqltransection)
کسی تو این زمینه اطلاعاتی داره که چطور این اطلاعات رو تو جداول ثبت کنم،که هیچ مشکلی نداشته باشه هر سفارش با id مخصوص به خودش تو جدول ثبت بشه

ممنون و متشکر

nima.sh
پنج شنبه 21 دی 1391, 15:14 عصر
من هم این مشکل رو دارم،از دوستان عزیز کسی هست جواب بده....؟

danialafshari
پنج شنبه 21 دی 1391, 15:24 عصر
اگر نرمال سازی دیتابیس خوب انجام شده باشه و کوئری ها با در نظر گرفتن Relation به درستی نوشته شده باشن این مشکل پیش نمیاد، حداقل که برای من پیش نیومده

nima.sh
پنج شنبه 21 دی 1391, 16:12 عصر
ولی احتمال این هست که مشکلی پیش بیاد،مثلا:
PDA1 اطلاعات خودش رو فرستاد اول تو جدول اطلاعات مشتری ثبت میشه،تو همین زمان PDA2 اطلاعات خودش رو فرستاد،تو جدول سفارش اخرین Identity رو میگره،چون آخرین identity برای PDA2 پس میاد اطلاعات مشتری PDA1 رو میزنه به حساب مشتری PDA2،احتمال بروز این جور مشکلات هست،من به دنبال یه راه حل منطقی و مناسب هستم...

nima.sh
پنج شنبه 21 دی 1391, 21:37 عصر
دوستان کسی نیست،راهنمایی کنه....؟

nima.sh
جمعه 22 دی 1391, 07:41 صبح
دوستان عزیز،فکر نمیکنم این مسئله انقدر پیچیده باشه،یه راهنمایی کوچیک میخوام،یعنی هیچ کس با چنین مشکلی برخورد نکرده....؟

RED-C0DE
جمعه 22 دی 1391, 08:35 صبح
چه تداخلی دقیقا؟؟ مثال بزنید

اگه مشکلتون مسئله همزمانی باشه (ک در برنامه ای ک تا حد خیلی زیادی خوب نوشته شده باشه سمت کار با دیتابیس ، فقط در مسئله های شلوغ این احتمال وجود داره ولی یک برنامه ای که استفاده ی ناصحیحی از connection و مفاهیم بانک اطلاعاتی کرده باشه این مسئله ب وفور می تونه رخ بده حتی در یک سناریوی معمولی چند کاربره) می تونین هندلش کنید .
در اینصورت با استفاده از (TimeStamp (RowVersion می تونین اینکارو انجام بدین. در واقع می تونین هندل کنید این وضعیت رو و براش تصمیم بگیرین.

سوال دیگه اینکه لایه ی کار با دیتابیستون به چ صورت نوشته شده؟ ado.net ? L2S ? EF? ...

danialafshari
جمعه 22 دی 1391, 09:46 صبح
اگر بحث هم زمانی و برخورد اطلاعات است می تونید از نخ ها (Thread) استفاده کنید

nima.sh
جمعه 22 دی 1391, 10:05 صبح
چه تداخلی دقیقا؟؟ مثال بزنید

اگه مشکلتون مسئله همزمانی باشه (ک در برنامه ای ک تا حد خیلی زیادی خوب نوشته شده باشه سمت کار با دیتابیس ، فقط در مسئله های شلوغ این احتمال وجود داره ولی یک برنامه ای که استفاده ی ناصحیحی از connection و مفاهیم بانک اطلاعاتی کرده باشه این مسئله ب وفور می تونه رخ بده حتی در یک سناریوی معمولی چند کاربره) می تونین هندلش کنید .
در اینصورت با استفاده از (TimeStamp (RowVersion می تونین اینکارو انجام بدین. در واقع می تونین هندل کنید این وضعیت رو و براش تصمیم بگیرین.

سوال دیگه اینکه لایه ی کار با دیتابیستون به چ صورت نوشته شده؟ ado.net ? L2S ? EF? ...

ممنون دوست عزیز...
با store procedure کار میکنم،مشکل اصلی من همین همزمانیه،احتمال زیادی میدم که چنین مشکلی پیش بیاد،چون برنامه چند کاربر ه است....
در حقیقت میخوام یه صف ایجاد کنم که تمام سفارشات از سمت کلاینت به سرور باشه،اگه یه مثال کوچیک هم بزنید ممنون میشم....

RED-C0DE
جمعه 22 دی 1391, 20:31 عصر
این مسئله همزمانی رو باید در موردش مطلب بخونید تا جزییاتش دستتون بیاد در ادامه کار مشکلی نخورین..

فرض کنید جدول زیر رو داریم:
http://img4up.com/up2/32232741103736773711.jpg

CREATE TABLE [dbo].[MyTable2](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[ItsRowVersion] [timestamp] NOT NULL,
CONSTRAINT [PK_MyTable2] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO


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


CREATE PROCEDURE [dbo].[usp_UpdateMyTable]
@pId INT,
@pName nvarchar(50),

@pItsRowVersion Timestamp
AS
BEGIN
--------------------------------------------------------------------
UPDATE MyTable
SET
Name = @pName
WHERE Id = @pId
-- علاوه بر مقايسه کليد اصلي جدول، مقدار يونيک
-- itsRowVersion
-- هم چک مي شه تا با پارامتر وردي - ک از سمت کلاينت اومده- يکي باشه
AND ItsRowVersion= @pItsRowVersion

-- اگه نتيجه ي اجراي دستور آپديت ،سطري رو تغيير داده يعني کار موفقيت اميز بوده
-- اگه سطري بدرستي اپديت شده باشه مقدار متغير سراسري @@ROWCOUNT >= 1
-- خواهد شد. ک در اينصورت بخش پايين اجرا نمي شه ديگه و نيازي ب هندل کردن
-- ConcurrencyConflict نمي باشد
--------------------------------------------------------------------


-- اگر ک اپديت اتفاق نيفتاده باشه، بايد اين مشکل همزماني رو بسته ب سياست کاريمون هندل کنيم
IF @@ROWCOUNT=0
BEGIN

-- ممکنه اصلا اين موجوديت رو يکي ديگه حذف کرده باشه
SELECT Name FROM MyTable WHERE Id=@pId
IF @@ROWCOUNT=0
BEGIN
-- اگه موجوديت مورد نظر يافت نشد يعني کاربري اون رو الان حذف کرده
-- مثلا مي تونين RaiseError
-- کنيد اينجا تا سمت برنامه کلاينت بقيه کار رو هندل کنيد.مثلا يک پيام ب کاربر نشون بدين
PRINT N'موجوديت مورد نظر توسط کاربر ديگري حذف شده'
-- RAISERROR ( ... )
END

-- کاربر ديگه اي رکورد مورد نظر رو اپديت کرده همزمان. باز هم بسته به سياست کاري تون مي تونين اقدام کنيد
ELSE
BEGIN
PRINT N'موجوديت مورد نظر توسط کاربر ديگري تغيير کرده'
-- RAISERROR ( ... )
END

END
END

GO


حالا فرض کنید همچین دیتایی رو در جدول داریم :


Id Name ItsRowVersion
1 ali1 0x00000000000007D5
2 reza1 0x00000000000007D6


(مقادیر مربوط ب ستون از جنس timestamp بصورت باینری خود-افزاینده هستن، ک خود dbms بعد از هر تغییری در رکورد ، مقدار ستون از جنس timestamp رو هم بروز می کنه و این مقدار یونیک هست)


حالا فرض کنیم شما در یک فرم، گریدی دارین و اینها رو دارین نمایش می دین.کاربر A سطر 1 رو واسه ویرایش انتخاب می کنه و فرم ویرایش باز می شه.
در این لحظه مقدار ستون ItsRowVersion=0x00000000000007D5 هست.
فرم ویرایش کماکان باز هست.
در همین حین کاربر B هم همین کار رو انجام می ده و موجودیت رو واسه ویرایش انتخاب می کنه.
هنوز هم مقدار ستون ItsRowVersion=0x00000000000007D5 هست. (چون تغییری در مقادیر ستونهای این سطر در سمت بانک اتفاق نیفتاده)

حالا کاربر B مقدار ستون name رو ب ali2 تغییر می ده و ذخیره می کنه (هنوز کاربر A فرم ویرایشش باز هست)
هنگام ذخیره اتفاق زیر می افته:
سمت برنامه کلاینت (فارغ از هر مدلی ک لایه رابط بانک اطلاعاتیتون رو نوشتین) موجودیتمون در حافظه داریم، روال مورد نظر (usp_UpdateMyTable) باید با مقادیر مناسب صدا زده شود ک برای موجودیت با id =1 این می شه :

EXEC dbo.usp_UpdateMyTable 1 , 'ali2' , 0x00000000000007D5


و در روال ما همانطور ک بالا دیدیم ، هنگام اپدیت چک می شه ک مقدار ItsRowVersion با پارامتر ورودی یکی باشد :

WHERE Id = @pId
-- علاوه بر مقايسه کليد اصلي جدول، مقدار يونيک
-- itsRowVersion
-- هم چک مي شه تا با پارامتر وردي - ک از سمت کلاينت اومده- يکي باشه
AND ItsRowVersion= @pItsRowVersion


در این لحظه مقدار ItsRowVersion = 0x00000000000007D5 و مقدار پارامتر ورودی روال ما هم @pItsRowVersion = 0x00000000000007D5 و یعنی برابر هستن و عمل اپدیت با موفقیت اتفاق می افته و از روال خارج می شه.

بعد از اپدیت این سطر ، مقدار ItsRowVersion هم توسط dbms بروز می شه ک نشون می ده مقدار سطر تغییر کرده:

Id Name ItsRowVersion
1 ali2 0x00000000000007D7
2 reza1 0x00000000000007D6


اوکی، کاربر B کارشو انجام داد و برنامه رو می بنده در حالی ک کاربر A فرم ویرایشش بازه هنوز. کاربر A کلید ذخیره رو می زنه و دستور زیر سمت بانک فرستاده می شه:

EXEC dbo.usp_UpdateMyTable 1 , 'ali2' , 0x00000000000007D5


چون موجودیت ali1 با id=1 در حافظه بوده، مقادیرش از اون لحظه هنوز تغییری نکرده. سمت بانک در روال ما، هنگام بررسی موجود بودن (و معتبر بودن) این موجودیت ، مقدار ItsRowVersion =0x00000000000007D7 و مقدار پارامتر ورودی ما، @pItsRowVersion =0x00000000000007D5 می باشد، ک برابر نیستن و Concurrency Conflict اتفاق می افته. در اون 2 3 تا if else هم بررسی های لازم انجام می شه و می تونین مثلا ب سمت برنامه کلاینت RAISERROR کنین تا سمت .net هندل کنید این قضیه رو.

afshines
جمعه 22 دی 1391, 21:23 عصر
ممنون دوست عزیز

میشه در مورد این دو خط یه خورده بیشتر توضیح بدید:

CONSTRAINT [PK_MyTable2] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

یه سوال دیگه:سفارش رو دارم از گرید ویو میخونم،با خوندن هر سطر اطلاعات به store procedure ارسال میشه

اینجوری مشکلی پیش نمیاد...؟

باز هم ممنون از راهنماییت....

RED-C0DE
جمعه 22 دی 1391, 23:21 عصر
ممنون دوست عزیز

میشه در مورد این دو خط یه خورده بیشتر توضیح بدید:

CONSTRAINT [PK_MyTable2] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

یه سوال دیگه:سفارش رو دارم از گرید ویو میخونم،با خوندن هر سطر اطلاعات به store procedure ارسال میشه

اینجوری مشکلی پیش نمیاد...؟

باز هم ممنون از راهنماییت....

این بخشی از اسکریپت تولید شده ی خود sql server هست ک جدول زیر رو تولید می کنه:
http://img4up.com/up2/32232741103736773711.jpg
این 2 خط هم برای set کردن برخی از ویژگیهای جدول استفاده می شه ک هر کدوم از اونا داستان خودشو داره.

منظورتون رو هم متوجه نشدم از اینکه "با خوندن هر سطر" اطلاعات ب sp ارسال می شه.
کد این بخش از کارتون رو بذارین چ من چ بقیه اگه بتونن کمک کنن

nima.sh
شنبه 23 دی 1391, 11:24 صبح
ممنون دوست عزیز....
در حقیت ما به این صورت داریم کار میکنیم....:
اول اطلاعات مشتری رو از برنامه میخونیم میذاریم داخل جدول مشتری،بعد از اون تمامی سفارشات اون مشتری داخل گریدویویه،با یه حلقه مثل for تک تک سطرها رو میخونیم میفرستیم برای sp،حالا مشکل اینجاست که چطور همزمانی رخ نده،یعنی اطلاعات مشتری با ID 1 با اطلاعات سفارش با ID 1 باشه،چون 2تا دستور insert هست احتمال میدیم که نداخل پیش بیاد،امیدوارم منظورم رو به درستی بیان کرده باشم....
باز هم ممنون از راهنماییتون...

nima.sh
شنبه 23 دی 1391, 23:06 عصر
دوستان اگه کسی اطلاعاتی داره،خواهشا اینجا بیان کنند،خیلی نیاز داریم....
از همگی ممنون....

plus
یک شنبه 24 دی 1391, 01:25 صبح
من دقیقا متوجه سناریویی که میگین نشدم، اگه مشکل، در گرفتن ID ی رکورد درج شده (بعد از درج) هست، شما Procedureتون رو که فکر میکنید ممکنه مشکل پیش بیاد رو بگذارید.اگه درست فکر کرده باشین (که فکر نمیکنم اینطور باشه)، دو راه کلی به نظرم میرسه.یکی اینکه همونطور که گفتین دستورات رو در SP در TRANSACTION بگذارین، یعنی مشکل در در سطح دیتابیس حل کنید، و یا راه دوم که پر درسر تر هست، میتونید Business Logic رو از کل نرم افزار جدا کنید، یک سرویس بنویسید که فقط روی سرور اجرا بشه و با Business Logic ارتباط داشته باشه، توی اون سرویس، همزمانی درخواست ها (مثلا درج) رو هندل کنید (با استفاده از صف و یا...) و همه کلاینت ها با استفاده از سرویس، با دیتابیس ارتباط برقرار کنن.راه طولانیی هست، ولی مساله همزمانی هم، مساله پیچیده ایه.

Mahmoud.Afrad
یک شنبه 24 دی 1391, 04:49 صبح
ممنون دوست عزیز....
در حقیت ما به این صورت داریم کار میکنیم....:
اول اطلاعات مشتری رو از برنامه میخونیم میذاریم داخل جدول مشتری،بعد از اون تمامی سفارشات اون مشتری داخل گریدویویه،با یه حلقه مثل for تک تک سطرها رو میخونیم میفرستیم برای sp،حالا مشکل اینجاست که چطور همزمانی رخ نده،یعنی اطلاعات مشتری با ID 1 با اطلاعات سفارش با ID 1 باشه،چون 2تا دستور insert هست احتمال میدیم که نداخل پیش بیاد،امیدوارم منظورم رو به درستی بیان کرده باشم....
باز هم ممنون از راهنماییتون...

اشکال کار شما در بدست آوردن id ثبت شده هست. احتمالا id آخرین رکورد رو بدست میارید که در این برنامه که چند کاربره هست مشکل بوجود میاد. باید راهی را پیش بگیرید که id ثبت شده برای یک مشتری درست بدست بیاد.

راه حل اینه که در دیتابیس فیلد id جدول را از نوع uniqueidentifier بگیرید و id مشتری را سمت سی شارپ خودتون تولید کنید. در این حالت دیگه مشکلی بوجود نمیاد چون id را خودتون تولید کردید نه دیتابیس.
برای تولید مقدار id هم از Guid.NewGuid() استفاده کنید.
مثال:

bool isRegisterd;
Guid id;
string name;

private void button5_Click(object sender, EventArgs e)
{
id = Guid.NewGuid(); // تولید کلید اصلی
name = textBox1.Text;

cmd = new SqlCommand("insert into tbl(id,name) values(@id,@name)", con);
cmd.Parameters.AddWithValue("@id", id);
cmd.Parameters.AddWithValue("@name", name);
try
{
if (con.State == System.Data.ConnectionState.Closed)
{
con.Open();
}
cmd.ExecuteNonQuery();
isRegisterd = true; // ثبت موفقیت آمیز
}
catch (Exception ex)
{
isRegisterd = false; // ثبت بدون موفقیت
MessageBox.Show(ex.Message);
}
finally
{
if (con.State != System.Data.ConnectionState.Closed)
{
con.Close();
}
}

البته باید مطمئن شد مشتری ثبت شده به همین دلیل از متغیر isRegisterd استفاده کردم، در صورتی که isRegisterd برابر true بود میتونید سفارشات را ثبت کنید. در اینصورت متغیر id ، آیدی مشتری است.

if (isRegisterd)
{
// ثبت سفارشات
// با استفاده از متغیر
// id
}

nima.sh
یک شنبه 24 دی 1391, 09:48 صبح
اشکال کار شما در بدست آوردن id ثبت شده هست. احتمالا id آخرین رکورد رو بدست میارید که در این برنامه که چند کاربره هست مشکل بوجود میاد. باید راهی را پیش بگیرید که id ثبت شده برای یک مشتری درست بدست بیاد.

راه حل اینه که در دیتابیس فیلد id جدول را از نوع uniqueidentifier بگیرید و id مشتری را سمت سی شارپ خودتون تولید کنید. در این حالت دیگه مشکلی بوجود نمیاد چون id را خودتون تولید کردید نه دیتابیس.
برای تولید مقدار id هم از Guid.NewGuid() استفاده کنید.
مثال:

bool isRegisterd;
Guid id;
string name;

private void button5_Click(object sender, EventArgs e)
{
id = Guid.NewGuid(); // تولید کلید اصلی
name = textBox1.Text;

cmd = new SqlCommand("insert into tbl(id,name) values(@id,@name)", con);
cmd.Parameters.AddWithValue("@id", id);
cmd.Parameters.AddWithValue("@name", name);
try
{
if (con.State == System.Data.ConnectionState.Closed)
{
con.Open();
}
cmd.ExecuteNonQuery();
isRegisterd = true; // ثبت موفقیت آمیز
}
catch (Exception ex)
{
isRegisterd = false; // ثبت بدون موفقیت
MessageBox.Show(ex.Message);
}
finally
{
if (con.State != System.Data.ConnectionState.Closed)
{
con.Close();
}
}

البته باید مطمئن شد مشتری ثبت شده به همین دلیل از متغیر isRegisterd استفاده کردم، در صورتی که isRegisterd برابر true بود میتونید سفارشات را ثبت کنید. در اینصورت متغیر id ، آیدی مشتری است.

if (isRegisterd)
{
// ثبت سفارشات
// با استفاده از متغیر
// id
}

ممنون از پاسختون....
مشکل با یاری شما حل شد....
فقط یه سوال:احتمال این هست که رشته های تکراری بده....؟
منظورم اینه که مثلا امروز یه سفارش ثبت شد،با یه رشته از نوع Guid ،هفته بعد هم یه سفارش ثبت بشه،احتمال این هست که دوباره همون رشته رو بده....؟
باز هم ممنون...