PDA

View Full Version : خطا موقع به روزرسانی و تغییر در Navigation Collection Property در Entity Framework 6.4



SajjadKhati
سه شنبه 02 خرداد 1402, 12:58 عصر
سلام دوستان

در Entity Framework 6.4 Code First ، دو موجودیت دارم بصورت زیر :



public class PhoneBookDbContext : DbContext
{
public virtual DbSet<PersonEntity> PersonEntities { get; set; }
}


public class PersonEntity
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<MobileNumberEntity> MobileNumbers { get; set; }
}


public class MobileNumberEntity
{
public int Id{ get; set; }
public string MobileNumber{ get; set; }
public virtual PersonEntity PersonEntity { get; set; }
public int PersonEntityId { get; set; }
}


رابطه ی بین موجودیت "شخص" و "شماره تلفن" ، بصورت یک به چند تعریف شده . یعنی هر شخص میتونه چندین شماره تلفن داشته باشه . اما برای هر شماره تلفن ، محدودیت unique تعریف شده .
مخصوصا اینکه کلید خارجیِ PersonEntityId در جدول "MobileNumberEntity" ، بصورت not null هست (که انگار این قضیه ، به مشکلم ربط داره) .


در کلاس Repository ، متد Edit ام بصورت زیر هست که یک Id ای ای که در دیتابیس موجود هست را میگیره و همچنین در پارامتر دوم اش ، شی ای از همون موجودیتِ ویرایش شده را میگیره و به روزرسانی میکنه .



public void Edit(int entityIdInDatabase, TNonQueryEntity updateEntity)
{
updateEntity = updateEntity ?? throw new ArgumentNullException(nameof(updateEntity),
ExceptionMessage.argumentNullExceptionMessage);


TNonQueryEntity entityInDatabase = this.FindByLazyLoadingMode<TNonQueryEntity>(entityIdInDatabase);
if (entityInDatabase == null)
return;


DbEntityEntry<TNonQueryEntity> nonQueryEntityEntry = this._context.Entry(entityInDatabase);
DbPropertyValues nonQueryEntityPropertyValues = nonQueryEntityEntry?.CurrentValues;
try
{
nonQueryEntityPropertyValues?.SetValues(updateEnti ty);
}
catch (InvalidOperationException operationException)
{
string checkMessage = "is part of the object's key information and cannot be modified";
if (operationException.Message.Contains(checkMessage) == true)
{
string newExceptionMessage = "مقدار پروپرتیِ مربوط به شناسه ، باید برابر با همان مقدار قبلی و یا در واقع ، برابر با همان مقداری " +
"که به عنوان پارامتر اول به این متد ارسال شد (برابر با پارامتر entityIdInDatabase) ، باشد و نمیتواند تغییر داده شود .";


throw new InvalidOperationException(newExceptionMessage, operationException);
}
}
}


متد FindByLazyLoadingMode در همین کلاس Repository (در بدنه ی کد بالا که فراخونی شد) ، یک Id را میگیره و موجودیتِ مربوط به اون را بصورت Lazy Loading برمیگردونه .


=============================


حالا داستان اینه که میخوام شیِ PersonEntity ای که از قبل وجود داشت را ویرایش کنم .
مثلا از قبل ، شیِ PersonEntity ای بصورت زیر ، در پایگاه داده ذخیره شده بود (کد موجودیت EF اش را مینویسم) :



PersonEntity personEntity = new PersonEntity();
personEntity.Id = 1;
personEntity.FirstName = "احمد";
personEntity.LastName = "متوسلیان";
personEntity.MobileNumbers = new List<MobileNumberEntity>();
personEntity.MobileNumbers.Add(new MobileNumberEntity(){Id = 1, MobileNumber = "0123", PersonEntityId = 1});
personEntity.MobileNumbers.Add(new MobileNumberEntity() { Id = 2, MobileNumber = "0911", PersonEntityId = 1 });


که برای شخصی بنام "احمد متوسلیان" با id ئه 1 ، دو تا شماره تلفن ثبت شد که حالا در ادامه قصد ویرایشِ اطلاعات این شخص را دارم (در اینجا ، تمرکزِ تغییر ، فقط روی اطلاعات موجودیتِ شماره تلفن ئه این شخص هست) .
مثلا میخوام شماره تلفن دومی برای شخص "احمد متوسلیان" را از لیست بالا ، حذف کنم (که این کار ، در واقع ویرایش اطلاعات شخص "احمد متوسلیان" محسوب میشه) .

میدونم که این کار را میتونم بجای این روش ، از روش حذف و اضافه و تغییر بصورت مستقیم درون خود موجودیتِ "MobileNumberEntity" انجام بدم و در این صورت ، این خطا که در ادامه میگم را نمیده و به درستی کار میکنه اما میخوام بدونم آیا این کار (حذف و اضافه و تغییر در MobileNumberEntity) ، توسط ویرایش اطلاعاتِ PersonEntity هم میتونه انجام بشه یا نه؟

برای این کار (و این تغییر) هم کد زیر را نوشتم (فعلا حذف در MobileNumberEntity توسط ویرایش PersonEntity) :



using (IRepositoryBase<PersonEntity> repository = new Repository<PersonEntity>(new PhoneBookDbContext()))
{
var entity = repository.FindByLazyLoadingMode<PersonEntity>(1);
var mne = repository.FindByLazyLoadingMode<MobileNumberEntity>(2);
entity.MobileNumbers.Remove(mne);


repository.Edit(entity.Id, entity);
repository.SaveChanges();
}


متد FindByLazyLoadingMode را که توضیح دادم که یک متدی در Repository هست که یک موجودیت را بصورت Lazy Loading توسط شناسه ی اون موجودیت ای که در نوعِ جنریکِ اون متد (نه در نوع جنریکِ کلاس Repository) تعیین میکنیم ، پیدا میکنه و در متغییر entity ذخیره میکنه که در اینجا همون شنا سه ی 1 هست که شخص "احمد متوسلیان" هست.

دومین خط که باز این متد فراخوانی شد اما این بار برای پیدا کردن موجودیتِ "شماره تلفن" ، شماره ی تلفن با مقدار "0911" را که دومین شماره تلفن برای شخص "احمد متوسلیان" هست را پیدا میکنه تا اون را حذف کنه .

سومین خط هم این شماره تلفن پیدا شده را از لیست شماره تلفن های موجود برای اون فرد (entity.MobileNumbers) ، حذف میکنه .

بعدش متد Edit (در کلاس Repository) برای اجرای این عملیات ویرایش فراخوانی میشه .

آخرین خط هم که خطا پرتاب میکنه ، برای ذخیره کردن هست (در واقع متد SaveChange در کلاس DbContext ، خطا پرتاب میکنه) .

==============================

خطا اش هم متن زیر هست :

"
system.invalidoperationexception: 'the operation failed: the relationship could not be changed because one or more of the foreign-key properties is non-nullable. when a change is made to a relationship, the related foreign-key property is set to a null value. if the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.'
"

میگه که ارتباط نمیتونه تغییر کنه چون کلید خارجی ، بصورت not null تعریف شد .
کلید خارجیِ PersonEntityId (که در موجودیتِ MobileNumberEntity هست) ، همونطور که توضیح داده بودم ، بصورت not null تعریف شد ؛ اما عملکرد EF چجوری هه که اگه اشتباه متوجه نشده باشم ، انگار برای حذف یک موجودیتِ ای که بصورت Navigation Property با موجودیتِ دیگه رابطه داره و زمانی که موجودیت والدش که موجودیت شخص هست را میخواین ویرایش کنیم (منظورم حذف یک رکورد از موجودیت شماره تلفن هست) ، انگار نیاز به تغییر در کلیدِ خارجیِ اون موجودیت داره؟!
دیگه حذف کردنِ یک رکورد ، انگار نباید نیاز به تغییر در چیزی و نیاز به تغییر در مقدار کلید خارجی داشته باشه دیگه !

کلا دوستان به نظرتون در این روش ، مشکل از کجاست و میشه کاری کرد که کد بالا درست کار کنه (یعنی وقتی که بخوایم موجودیت شخص بالا را ویرایش کنیم ، میتونیم یک رکوردی از شماره تلفن اش را حذف یا اضافه یا ویرایش کنیم) ؟

تشکر

Mahmoud.Afrad
پنج شنبه 04 خرداد 1402, 08:23 صبح
PersonEntityId بصورت int تعریف شده، اگر میخواهید nullable باشه بایست به صورت int? تعریف بشه.

entity.MobileNumbers.Remove(mne);
خط بالا صرفا ارتباط اون شماره تلفن با اون شخص را از بین می برد یعنی PersonEntityId را برای mne نال میکند و اگر nullable نباشه خطای کد شما صادر میشه.
این خط کد ، اون شماره تلفن رو از جدول شماره تلفن ها حذف نخواهد کرد.

SajjadKhati
پنج شنبه 04 خرداد 1402, 14:00 عصر
سلام
خیلی ممنون آقا محمد .
بله درست میگید .

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

اون ارور هم فقط برای زمان حذف کردن اتفاق میافته (یعنی برای زمان ویرایش یا اضافه کردنِ موجودیتِ MobileNumberEntity ، توسط ویرایش کردنِ موجودیت والدش یعنی توسط ویرایش کردن موجودیتِ PersonEntity ، این اتفاق و خطا پیش نمیاد) . که برای رفع این خطا موقع حذف کردن ، علاوه بر حذف از کالکشن ، اون رکورد را از اون موجودیت و جدول هم باید حذف و بعدش ذخیره کنیم که این مشکل حل میشه .

. علاوه بر اون ، بعد از فراخوانی متد SaveChanges ، میتونیم شناسه هایی که به عنوان هر موجودیت اضافه کردیم را از طریق همان شی ، بدست بیاریم .

کد زیر ، ویرایش یک شی و یک رکورد از PersonEntity هست که هم ویرایش مقادیر پروپرتی scalar و هم حذف و اضافه و ویرایش مقادیر و موجودیت های مربوط به Navigation Property ئه مربوط به اون شخص هست .

تشکر ازتون .



using (IRepositoryBase<PersonEntity> repository = new Repository<PersonEntity>(new PhoneBookDbContext()))
{
await repository.InitializeDatabaseAsync();


PersonEntity mainEntity = repository.FindByLazyLoadingMode<PersonEntity>(1);
// ویرایش پروپرتی های scalar برای موجودیت PersonEntity
mainEntity.FirstName = "سردار مقاومت احمد";
mainEntity.LastName = "متوسلیان";


mainEntity.MobileNumbers = mainEntity.MobileNumbers ?? new List<MobileNumberEntity>();
// ویرایش ، شامل حذف و اضافه و ویرایش موجودیت MobileNumberEntity
MobileNumberEntity addMne = new MobileNumberEntity() {MobileNumber = "0112233"};
mainEntity.MobileNumbers.Add(addMne);


MobileNumberEntity deleteMne = mainEntity.MobileNumbers.FirstOrDefault(mobileNumb erEntity => mobileNumberEntity.Id == 7);
mainEntity.MobileNumbers.Remove(deleteMne);
repository.Remove<MobileNumberEntity>(deleteMne);


mainEntity.MobileNumbers.FirstOrDefault(mobileNumb erEntity => mobileNumberEntity.Id == 6).MobileNumber = "987654";


/////////////////////////////
// ویرایش ، شامل حذف و اضافه و ویرایش موجودیت AddressEntity
mainEntity.Addresses = mainEntity.Addresses ?? new List<AddressEntity>();


AddressEntity addAe = new AddressEntity {Province = "تهران", City = "شهر تهران"};
mainEntity.Addresses.Add(addAe);


AddressEntity deleteAe = mainEntity.Addresses.FirstOrDefault(addressEntity => addressEntity.Id == 3);
mainEntity.Addresses.Remove(deleteAe);
if (deleteAe.Persons.Count == 1)
repository.Remove<AddressEntity>(deleteAe);


var editAe = mainEntity.Addresses?.FirstOrDefault(addressEntity => addressEntity.Id == 1);
editAe.Province = "فلسطین";
editAe.City = "شهر غزه";


// انجام ویرایش و ذخیره
repository.Edit(mainEntity.Id, mainEntity);
repository.SaveChanges();


// بازیابی شناسه های مربوط به موجودیت های اضافه شده
int addAeId = addAe.Id;
int addMneId = addMne.Id;
}