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
Related
We have two models vw_fleet and fleet_contact generated by the entity framework. We are using views to retrieve data and we can't define the relationship between those models but when we retrieve data for fleet_contact we need fleet information as well.
public partial class vw_fleet
{
public int account_id { get; set; }
public int fleet_id { get; set; }
public string fleet_name { get; set; }
}
public partial class fleet_contact
{
public int id { get; set; }
public int fleet_id { get; set; }
public string contact { get; set; }
}
We have added a property(Fleet) to fleet_contact
public partial class fleet_contact
{
public vw_fleet Fleet { get; set; }
}
One way to do that is by using join.
using (var context = new EFEntities())
{
return context.fleet_contact.Join(context.vw_fleet, fc => fc.fleet_id, f => f.fleet_id, (fc, f) => new FleetContactModel()
{
fleet_id = fc.fleet_id,
Fleet = f,
contact = fc.contact,
id = fc.id,
}
).ToList();
}
Is there any way to achieve the above solution.
I have two entites with a many-to-many relationship. Company and SearchKeyword.
Here are the models:
class SearchKeyword
{
public int ID { get; set; }
public string Text { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
class Company
{
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}
I am trying to add a SearchKeyword to a company but it won't let me. I tried this:
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
It says Object reference not set to an instance of an object. I am not sure what is null. In inspector I can see c and sk both have full values. I guess I must be missing a fundamental of how the many-to-many relationship works with EF.
What am I doing wrong?
This is because SearchKeywords is null.
Either you can assign a List to it before adding a new instance
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords = new List<SearchKeyword>();
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
Or you can do it constructor method
class Company
{
public Company()
{
SearchKeywords = new List<SearchKeyword>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}
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; }
}
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);
}
}
I have the following classes:
public class Order
{
public Order() { LineItems = new List<OrderLineItem>(); }
public int OrderID { get; set; }
..
public virtual ICollection<OrderLineItem> LineItems { get; set; }
..
}
public class OrderLineItem
{
public int OrderLineItemID { get; set; }
public int ProductID { get; set; }
public virtual Product Product { get; set; }
public int Quantity { get; set; }
}
public class Product
{
public int ProductID { get; set; }
...
}
When I persist Order, I have set the OrderLineItems to the appropriate field. However, after I have saved it to the database with the following code:
public void SaveOrder(Order ord)
{
if (ord.OrderID == 0)
{
context.Orders.Add(ord);
}
else
{
var currentCat = context.Orders.Find(ord.OrderID);
if (currentCat != null)
context.Entry(currentCat).CurrentValues.SetValues(ord);
else
context.Entry(ord).State = EntityState.Modified;
}
context.SaveChanges();
}
The OrderLineItem.Product object is null. It has the appropriate ProductID set, but it doesn't populate the actual product. I have tried a few different mappings similar to the following, but I'm a bit new to entity framework.
modelBuilder.Entity<OrderLineItem>()
.HasRequired(c => c.Product);
Any help would be most appreciated.
I believe you have to iterate through the products and Add() them as well.