I am getting a foreign key error when trying (probably to do too much) within a single SaveChanges.
Basically I have following 3 poco entities like so.
public class ClinicianAvailability
{
public int ClinicianAvailabilityId { get; set; }
}
public class SurgicalBooking
{
public int SurgicalBookingId
public int? ClinicianAvailabilityId { get; set; }
public bool IsAdhoc { get; set; }
public virtual TheatreBooking TheatreBooking { get; set; }
public virtual ClinicianAvailability ClinicianAvailability { get; set; }
}
public class TheatreBooking
{
public int TheatreBookingId {get;set;}
public virtual SurgicalBooking SurgicalBooking { get; set; }
public virtual ClinicianAvailability ClinicianAvailability { get; private set; }
}
I am basically trying to delete a ClinicianAvailability which has a Foreign Key on the SurgicalBooking. But at the sametime trying to set a new TheatreBooking on the SurgicalBooking.
Like this:
var entity = _clinicianAvailabilityRepository.Find(resourceAvailabilityId);
var surgStub = surgicalBookingRepository.CreateStub(bookingData.ScheduleId);
surgStub.IsAdhoc = true;
surgStub.ClinicianAvailabilityId = null;
surgicalBookingRepository.SetModified(surgStub, new Expression<Func<SurgicalBooking, object>>[] { x => x.IsAdhoc, x => x.ClinicianAvailabilityId });
theatreBookingRepository.Add( new TheatreBooking
{
TheatreBooking Id = theatreBookingRepository.GetNewTempKey(),
TheatreId = associatedTheatreId.Value,
SurgicalBooking = surgStub
});
theatreBookingRepository.Add(TheatreBooking);
_clinicianAvailabilityRepository.Remove(entity);
_clinicianAvailabilityRepository.UnitOfWork.SaveChanges();
So basically after doing this I get a foreign key error on the SurgicalBooking foreign key of ClinicianAvailabilityId. If I take out the adding of the TheatreBooking, it goes in the right order by updating Surgical Booking and then deleting. But with Adding the Theatre Booking it tries to do the delete first and then fails. Any help with this? I've tried to simplify this as much as possible but it's a bit complicated. I'm trying to not do multiple save changes or put it all inside a single transaction because it would be a lot of rework to change all this code.
I have looked in the ChangeTracker and all the items seem to be there in the right order, but it doesn't do it in that order.
I tried to replicate the error with following code, but it completed successfully:
static void CreateAndSeedDatabase()
{
Context context = new Context();
ClinicianAvailability cAvail = new ClinicianAvailability() { };
SurgicalBooking sBooking = new SurgicalBooking() { IsAdhoc = true, ClinicianAvailability = cAvail };
context.SurgicalBookings.Add(sBooking);
context.SaveChanges();
}
static void DeleteUpdateInsert()
{
Context context = new Context();
ClinicianAvailability cAvail = context.ClinicianAvailabilitys.Find(1);
SurgicalBooking sBooking = context.SurgicalBookings.Find(1);
sBooking.IsAdhoc = false;
sBooking.ClinicianAvailability = null;
TheatreBooking tBooking = new TheatreBooking(){SurgicalBooking = sBooking};
context.TheatreBookings.Add(tBooking);
context.ClinicianAvailabilitys.Remove(cAvail);
context.SaveChanges();
}
Context class:
public class Context : DbContext
{
public Context()
{
Database.SetInitializer<Context>(new CreateDatabaseIfNotExists<Context>());
Database.Initialize(true);
}
public DbSet<ClinicianAvailability> ClinicianAvailabilitys { get; set; }
public DbSet<SurgicalBooking> SurgicalBookings { get; set; }
public DbSet<TheatreBooking> TheatreBookings { get; set; }
}
Modified POCO classes:
public class ClinicianAvailability
{
public int ClinicianAvailabilityId { get; set; }
}
public class SurgicalBooking
{
public int SurgicalBookingId { get; set; }
public bool IsAdhoc { get; set; }
public virtual ClinicianAvailability ClinicianAvailability { get; set; }
}
public class TheatreBooking
{
public int TheatreBookingId { get; set; }
public virtual SurgicalBooking SurgicalBooking { get; set; }
public virtual ClinicianAvailability ClinicianAvailability { get; private set; }
}
Related
This question has been asked before and each time the answer has been that the code WAS updating the Id property on an entity. I cannot see how my code is modifying this property.
System.InvalidOperationException: The property 'Id' is part of the object's key information and cannot be modified.
at System.Data.Entity.Core.Objects.EntityEntry.DetectChangesInProperty(Int32 ordinal, Boolean detectOnlyComplexProperties, Boolean detectOnly)
The above error is thrown on the first line where I set my objects Attempt property to IsModified = true seen below.
private static void SaveDocumentCreationToDatabase(Document document)
{
using (MyEntities context = new MyEntities ())
{
context.Database.Log += (x) => dbSaveLog.Debug($"Document Id: {document.Id}. Message:\r\n{x}");
context.Documents.Attach(document); // state = unchanged
// Set document modified properties
context.Entry(document).Property(u => u.Attempt).IsModified = true; // ERROR THROWN HERE
context.Entry(document).Property(u => u.Result).IsModified = true;
context.Entry(document).Property(u => u.NewId).IsModified = true;
context.Entry(document).Property(u => u.PropertiesUploaded).IsModified = true;
// modify some of the navigation properties ... etc
// Debug
// Inspect Change Tracking on this context
// Output all tracked entities that are added or modified, their key, and their modified properties
string logOfModifiedEntities = GetAllModifiedEnitiesFromContext(document, context);
dbSaveLog.Debug($"{logOfModifiedEntities}");
// End debug
context.SaveChanges();
}
}
The document object looks like this:
public class Document
{
public int Id { get; set; }
// Previous system metadata
public string DocumentKey { get; set; }
public string OldParent { get; set; }
public string OldChild { get; set; }
// Migration metadata
public bool MigrationReady { get; set; }
public int LoadPriortity { get; set; }
public int Attempt { get; set; }
public int Result { get; set; }
public bool PropertiesUploaded { get; set; }
[StringLength(14)]
public string NewId { get; set; }
[ForeignKey("CabinetConfiguration")]
public string Cabinet { get; set; }
public string Name { get; set; }
public string Extension { get; set; }
public bool InheritsSecurity { get; set; }
public virtual ICollection<DocumentVersion> DocumentVersions { get; set; }
public virtual ICollection<DocumentLog> DocumentLogs { get; set; }
[InverseProperty("DocumentParent")]
public virtual ICollection<DocumentLinking> DocumentParents { get; set; }
[InverseProperty("DocumentLink")]
public virtual ICollection<DocumentLinking> DocumentLinks { get; set; }
public virtual CabinetConfiguration CabinetConfiguration { get; set; }
public Document()
{
DocumentVersions = new HashSet<DocumentVersion>();
DocumentLogs = new HashSet<DocumentLog>();
}
}
I am using Entity Framework to deal with database, and I have self-referencing model as following:
public class PhysicalObject
{
public PhysicalObject()
{
SubPhysicalObjects = new HashSet<PhysicalObject>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int? ParentId { get; set; }
[StringLength(150)]
public string Title { get; set; }
public virtual PhysicalObject Parent { get; set; }
public virtual ICollection<PhysicalObject> SubPhysicalObjects { get; set; }
}
I was using GraphDiff library to update disconnected graphs but it seems it does not support updating self-referencing graphs.
My question is: what is the best way to update self-referencing graphs using Entity Framework by:
Deleting/Updating existing physicalObjects
Inserting not existing physicalObjects
Let's say I have two entities as followings:
public class PhysicalObject
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int? ParentId { get; set; }
public int StorageRequestId { get; set; }
public string Title { get; set; }
public virtual PhysicalObject Parent { get; set; }
public virtual ICollection<PhysicalObject> SubPhysicalObjects { get; set; }
public virtual StorageRequest StorageRequest { get; set; }
}
public class StorageRequest
{
public StorageRequest()
{
PhysicalObjects = new HashSet<PhysicalObject>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<PhysicalObject> PhysicalObjects { get; set; }
}
Notice that PhysicalObject is self-referencing table .
Not let's update graphs using entity framework:
var oldPhysicalObjects = dbContext.PhysicalObjects.Where(x => x.StorageRequestId== storageRequestId).ToList();
var existingIds = new HashSet<int>();
foreach (var item in newGraphDto.PhysicalObjects.ToList())
{
updateGraph(item, oldPhysicalObjects, dbContext, storageRequestId,existingIds);
}
var posToDelete = oldPhysicalObjects.Where(x => existingIds.All(e => e != x.Id)).ToList();
dbContext.PhysicalObjects.RemoveRange(posToDelete);
dbContext.SaveChanges();
updateGraph method will update every tree of PhysicalObjects recursively and it looks like:
private void updateGraph(PhysicalObjectDto physicalObjectDto, IList<PhysicalObject> oldPhysicalObjects, MyDbContext dbContext, int storageRequestId, HashSet<int> existingIds, PhysicalObject parent = null)
{
if (physicalObjectAddEditDto.Id == 0)
{
PhysicalObject po = new PhysicalObject
{
Id = physicalObjectAddEditDto.Id,
Title = physicalObjectAddEditDto.Title,
StorageRequestId = storageRequestId,
Parent=parent
};
dbContext.PhysicalObjects.Add(po);
parent = po;
}
else
{
var po = oldPhysicalObjects.FirstOrDefault(x => x.Id == physicalObjectAddEditDto.Id);
po.Title = physicalObjectAddEditDto.Title;
po.StorageRequestId = storageRequestId;
po.Parent = parent;
dbContext.Entry(po).CurrentValues.SetValues(po);
parent = po;
}
existingIds.Add(parent.Id);
foreach (var subPhysicalObject in physicalObjectAddEditDto.SubPhysicalObjects)
{
updateGraph(subPhysicalObject, oldPhysicalObjects, dbContext, mailRoomRequestId, existingIds, parent);
}
}
I hope my code will help others to know how to update graph trees of self-referencing table
I'm working on an entity framework project and I'm trying to form a generic structure for adding joins to my table.
class BrowseCONTACTS_BASE : MyBrowseFrm<CONTACTS_BASE>
{
public BrowseCONTACTS_BASE()
{
MyInitialize();
}
public void MyInitialize()
{
//Here I may have more joins
MyAddJoins<ORDERS>("CONTACTS_BASE", "CB_REFNO", "ORDERS", "OR_REFNO", "OR_REFNO");
}
}
On the parent class MyBrowseFrm
partial class MyBrowseFrm<TEntity>:Form where TEntity:class
{
}
I have the following:
MyAddJoins Function that I call from the child class above
protected void MyAddJoins<TTable>(string pParent, string pParentKey, string pChild, string pChildKey,
string pDisplayField) where TTable:class, new()
{
var a = new TTable();
var item = new MyJoins<dynamic>
{
Parent = pParent,
Child = pChild,
ParentKey = pParentKey,
ChildKey = pChildKey,
DisplayField = pDisplayField,
ChildTable = a
};
MyBdJoins.Add(item);
}
//A list to store Joins added from the child table
private List<MyJoins<dynamic>> MyBdJoins;
protected struct MyJoins<TTable> where TTable : class
{
public string Parent;
public string ParentKey;
public string Child;
public string ChildKey;
public string DisplayField;
public TTable ChildTable;
}
Ok this is the part where I'm stuck, The following code will run when I press the search button.
private void MyGenerateQuery()
{
//Here I cast my Context to CONTACTS_BASE
var loContext = (DbSet<TEntity>)Context.GetPropValue(boName);
foreach (var join in MyBdJoins)
{
loContext
.Join(
(DbSet<ORDERS>)Context.GetPropValue(join.Child),
par => par.GetPropValue(join.ParentKey),
chld => chld.GetPropValue(join.ChildKey),
(par, chld) => new { GetPropValue = chld.GetPropValue(join.DisplayField) }
);
}
myGridView1.DataSource = loContext.ToList();
}
The code above at the part where it is:
(DbSet<ORDERS>)Context.GetPropValue(join.Child)
Here what I want to do is:
(DbSet<TTable>)Context.GetPropValue(join.Child)
of course, the above code gives me an error.
Note: in my code TEntity is CONTACTS_BASE and TTable is ORDERS
so how do I cast this object to type TTable
where it is in MyJoins Structure
public TTable ChildTable;
EDIT:
public partial class ORDERS
{
public int OR_REFNO { get; set; }
public string OR_PROD_CODE { get; set; }
public Nullable<DateTime> OR_DATE { get; set; }
public Nullable<int> OR_M_REFNO { get; set; }
public virtual CONTACTS_BASE CONTACTS_BASE { get; set; }
public virtual ORDER_TYPES ORDER_TYPES { get; set; }
}
public partial class CONTACTS_BASE
{
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public CONTACTS_BASE()
{
this.ORDERS = new List<ORDERS>();
}
public int CB_REFNO { get; set; }
public string CB_NAME { get; set; }
public string CB_ID_NO { get; set; }
public string CB_AGE { get; set; }
public Nullable<decimal> CB_TEL_NO { get; set; }
public string CB_EMAIL { get; set; }
public Nullable<DateTime> CB_ENROLL_DATE { get; set; }
public Nullable<DateTime> CB_START_DATE { get; set; }
public Nullable<DateTime> CB_END_DATE { get; set; }
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual IList<ORDERS> ORDERS { get; set; }
}
I have two separate VS Solutions both work fine accessing the databases associated with them.
How ever I need to look up some details from the other database to be displayed in the other solution.
Ive added the second connection string to the web.config and then added the context to my DAL:
namespace RACentral.DAL
{
public class RACentralContext : DbContext
{
public RACentralContext()
: base("RACDev")
{
}
public DbSet<RiskAssessment> RiskAssessments { get; set; }
public DbSet<Hazard> Hazards { get; set; }
public DbSet<PPE> PPEs { get; set; }
public DbSet<RiskAssessmentPPE> RiskAssessmentPPEs { get; set; }
public DbSet<PeopleExposed> PeopleExposeds { get; set; }
public DbSet<RiskAssessmentPeopleExposed> RiskAssessmentPeopleExposeds { get; set; }
public DbSet<RiskAssessmentHazard> RiskAssessmentHazards { get; set; }
public DbSet<ControlMeasure> ControlMeasures { get; set; }
public DbSet<Severity> Severitys { get; set; }
public DbSet<Likelihood> Likelihoods { get; set; }
public DbSet<AddControlMeasure> AddControlMeasures { get; set; }
public DbSet<Type> Types { get; set; }
public DbSet<SubType> SubTypes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
public class PeopleContext : DbContext
{
public PeopleContext()
: base("PeopleContext")
{
}
public DbSet<Person> People { get; set; }
}
}
I get an error in the controller
'Not set to an instance of an object,'
Am trying to access it in my controller as follow:
public class RiskAssessmentController : Controller
{
private RACentralContext db = new RACentralContext();
private PeopleContext Peopledb = new PeopleContext();
public ViewResult StartRA()
{
var user = User.Identity.Name;
string userName = user.Substring(7);
var test = Peopled.Person.FirstOrDefault(x => x.PersonId == 1) //Error here
StartRiskAssessmentViewModel viewModel = new StartRiskAssessmentViewModel
{
RiskAssessment = new RiskAssessment(),
Assessor = userName,
};
return View(viewModel);
}
}
I have two API calls. GetExam and SaveExam. GetExam serializes to JSON which means by the time I go to save, the entity is detached. This isnt a problem, I can go retrieve the entity by its primary key and update its properties manually.
However, when I do so the exam questions get its current collection duplicated. For example, if examToSave.ExamQuestions had a few questions deleted, and a new one added all selectedExam.exam_question are duplicated and the new one is added in. Eg. if 3 questions existed, I deleted 1 and added 4 there will now be 7.
Domain models:
public partial class exam
{
public exam()
{
this.exam_question = new HashSet<exam_question>();
}
public int ID { get; set; }
public string ExamName { get; set; }
public string ExamDesc { get; set; }
public Nullable<decimal> TimeToComplete { get; set; }
public bool AllowBackStep { get; set; }
public bool RandomizeAnswerOrder { get; set; }
public int Attempts { get; set; }
public virtual ICollection<exam_question> exam_question { get; set; }
}
public partial class exam_question
{
public exam_question()
{
this.exam_answer = new HashSet<exam_answer>();
}
public int ID { get; set; }
public int ExamID { get; set; }
public string QuestionText { get; set; }
public bool IsFreeForm { get; set; }
public virtual exam exam { get; set; }
public virtual ICollection<exam_answer> exam_answer { get; set; }
}
public partial class exam_answer
{
public int ID { get; set; }
public string AnswerText { get; set; }
public int QuestionID { get; set; }
public bool IsCorrect { get; set; }
public virtual exam_question exam_question { get; set; }
}
Save method:
[Route("SaveExam")]
[HttpPost]
public IHttpActionResult SaveExam(ExamViewModel examToSave)
{
using (var db = new IntranetEntities())
{
// try to locate the desired exam to update
var selectedExam = db.exams.Where(w => w.ID == examToSave.ID).SingleOrDefault();
if (selectedExam == null)
{
return NotFound();
}
// Redacted business logic
// Map the viewmodel to the domain model
Mapper.CreateMap<ExamAnswerViewModel, exam_answer>();
Mapper.CreateMap<ExamQuestionViewModel, exam_question>().ForMember(dest => dest.exam_answer, opt => opt.MapFrom(src => src.QuestionAnswers));
Mapper.CreateMap<ExamViewModel, exam>().ForMember(dest => dest.exam_question, opt => opt.MapFrom(src => src.ExamQuestions));
var viewmodel = Mapper.Map<exam>(examToSave);
// Update exam properties
selectedExam.ExamName = viewmodel.ExamName;
selectedExam.ExamDesc = viewmodel.ExamDesc;
selectedExam.AllowBackStep = viewmodel.AllowBackStep;
selectedExam.Attempts = viewmodel.Attempts;
selectedExam.RandomizeAnswerOrder = viewmodel.RandomizeAnswerOrder;
selectedExam.exam_question = viewmodel.exam_question; // DUPLICATES PROPS
// Save
db.SaveChanges();
return Ok(examToSave);
}
}