EF one-to-many retrieves foreign key but not object - c#

There are 2 classes
Event
public class Event
{
public Guid? UserID { get; set; }
[ForeignKey("UserID")]
public virtual User User { get; set; }
...
User
public class User
{
public Guid UserId { get; set; }
// Not used in this example, but just thought they might be related to problem
private List<Event> _attendedEvents;
public virtual ICollection<Event> AttendedEvents
{
get { return _attendedEvents ?? (_attendedEvents = new List<Event>()); }
set {
if (value == null)
_attendedEvents = new List<Event>();
else
_attendedEvents = new List<Event>(value);
}
}
public virtual ICollection<Event> HostedEvents { get; set; }
...
EventConfiguration
HasOptional<User>(s => s.User)
.WithMany(s => s.HostedEvents)
.HasForeignKey(s => s.UserID);
The thing I'm trying to do is
Add User to Repository
Add Event(which has same User inside it) to Repository
Save Changes
Retrieve Event back from repository
Everything kind of works, except when I retrieve Event back it has null User, however UserId is valid and points to User i created earlier.
Here's how I'm doing it
// Creates just the User object with specified UserName
var user = ObjectHelpers.CreateUser("ServiceTestUser");
// Adds to Repository + Saves Changes
_client.AddUser(user);
// Find it again to have generated Id and kind of test if it was added
user = _client.FindUserByEmail(user.Email);
// Create Event object and assign specified user object to it
// At this point #event has User set to above one and UserID null
var #event = ObjectHelpers.CreateEvent(user);
// Attach User from Event + Add Event to repository + Save Changes
_client.AddEvent(#event);
// Get it back to Check if everything went fine
// At this point #event has User set to null and valid UserID
#event = _client.GetEventByTitle(#event.EventTitle);

By default EF will not read related entities. And this behavior is really useful. If not, whenever you tried to read an entity from the DB, you'd read that entity, and all the probably very big, tree of related entities.
You must read realted entities:
explicitly, by using .Include()
or implicitly, by using lazy loading, which means accessing the navigation property provided the DbContext hasnot been disposed
Example of Include():
DbCtx.Events.First(ev => ev.Title == "title").Include(ev => ev.User);
For more information on including related entities see this: Loading Related Entities

Related

Can't update child entities in my controller with entity framework core

Having these two entities, I fetch them, map them to viwemodels/dtos before I pass them to the UI.
I also had to ignore reference loop handling, in my startup.cs file, to map them to DTO's correctly.
public class Matter
{
public int Id { get; set; }
public ICollection<MatterExposure> Exposures { get; set; }
// other properties
}
public class MatterExposure
{
public int Id { get; set; }
public Matter Matter { get; set; }
// other properties
}
When I save the form (which includes a table of 'MatterExposure's) in the UI I pass everything back to the controller to be saved. INFO - not saving child entities 'MatterExposure' yet in below controller call and it works fine!
[HttpPut("{id}")]
public async Task<IActionResult> UpdateData(string id, MatterForClaimDetailedDto generalMatterDto)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var matter = await _autoRepo.GetMatter(id);
// fill some matter data and add a child then save and it works fine
if (await _autoRepo.SaveAll())
return NoContent();
}
public class MatterForClaimDetailedDto
{
public int Id { get; set; }
public GeneralMatterDto MatterData { get; set; }
public ICollection<MatterExposure> Exposures { get; set; }
// other properties
}
Now I want to add the update of MatterExposure entities, as I could have made changes to them in the UI. So I try to use UpdateRange like this
[HttpPut("{id}")]
public async Task<IActionResult> UpdateData(string id, MatterForClaimDetailedDto generalMatterDto)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var matter = await _autoRepo.GetMatter(id);
matter.EditedDate = DateTime.Now;
matter.FirstName = generalMatterDto.FirstName;
matter.LastName = generalMatterDto.LastName;
_autoRepo.UpdateRange<List<MatterExposure>>(generalMatterDto.Exposures.ToList());
await _autoRepo.SaveAll()
}
public void UpdateRange<T>(T entity) where T : class
{
_autoContext.UpdateRange(entity);
}
But on calling UpdateRange I get this exception message:
"The entity type 'List MatterExposure' was not found. Ensure that the entity type has been added to the model."
In my context I have this:
public DbSet<MatterExposure> MatterExposure { get; set; }
I then tried below with no luck
public DbSet<List<MatterExposure>> MatterExposure { get; set; }
I thought I would try updating each individual 'MatterExposure' entity to see if that would change anything. So I tried removing the UpdateRange call and tried with individual 'MatterExposure' entities
foreach(var exposure in generalMatterDto.Exposures) {
_autoRepo.Update<MatterExposure>(exposure);
}
// in my repo I have this with different things I tried
public void Update<T>(T entity) where T : class
{
// _autoContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
//_autoContext.Entry(entity).State = EntityState.Detached;
_autoContext.Update(entity);
// _autoContext.ChangeTracker.
}
On the first loop through each 'MatterExposure' Update call to the repo I get this exception
"The instance of entity type 'MatterExposure' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."
After the exception above I tried I put the loop at the top of the controller method to see if the other entity stuff was interfering.
// at top of controler method before the other entity actions are performed
foreach(var exposure in generalMatterDto.Exposures) {
_autoRepo.Update<MatterExposure>(exposure);
}
And moving the for loop to the top of the controller, runs through the 1st iteration but then fails on the second giving me the same error message again
"The instance of entity type 'MatterExposure' cannot be tracked because
another instance with the same key value for {'Id'} is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."
QUESTION - am I not updating the child entities correctly or is it something else?

LINQ/Code first: how do I delete a child object's reference from the parent object?

I have 'UserProfile' and 'Executive' objects on a 1:1 relationship using Repository pattern. UserProfiles are required/part of the system, and executives are based on UserProfiles.
Here's my UserProfile:(pertinent part)
public int? ExecutiveId { get; set; }
[ForeignKey("ExecutiveId")]
public virtual Executive Executive { get; set; }
Here's my Executive: (pertinent part)
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public virtual UserProfile UserProfile { get; set; }
Here's my FluentApi:(since the executive is optional and requires a UserProfile)
modelBuilder.Entity<Executive>()
.HasRequired(x => x.UserProfile)
.WithOptional(s => s.Executive)
.WillCascadeOnDelete(false); <-- I've tried 'true' here as well
Now when I try deleting it, and look in SQL Mgr, I see the UserProfile for a particular executive I've deleted is still an integer and not NULL as I expect... Here's my code to delete:
var executive = _executiveRepository.GetExecutiveById(id);
// remove pic/content
_contentRepository.DeleteContent(executive.ProfilePictureContent.ContentGuid);
// remove mappings
_executiveSectionMappingRepository.DeleteExecutiveSectionMappingRange(executive.ExecutiveSectionMappings);
// remove executive
_executiveRepository.DeleteExecutive(id);
// remove reference from User also as EF isn't doing this the way it's setup now.
var u = _userRepository
.GetUserProfiles()
.FirstOrDefault(x => x.ExecutiveId == executive.UserProfileId);
if (u != null)
{
u.ExecutiveId = null;<-- these don't work !!
u.Executive = null; <-- not working
}
// save
_contentRepository.Save();
_executiveSectionMappingRepository.Save();
_executiveRepository.Save();
_userRepository.Save();
what am I missing?
I've not work with Code first, but it looks like your saving your Repositories, but not setting that entity to be modified.
Meaning you're updating the userProfile's object in memory, but not setting your repo to use that object. {unless i'm mistaken with Code First}, but it seems you need to do something along the lines of:
_userRepository.UpdateExistingUser(u);
Then call _userRepository.Save();

Entity Framework Code First deleting one to zero/one child from parent

I have created a simple one to zero/one relationship inside of code first. The code below works in that I can have a Person instance and optionally have an Account and its modeled fine in the database.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Account Account { get; set; }
}
public class Account
{
public int Id { get; set; }
public string Location { get; set; }
public virtual Person Owner { get; set; }
}
//Mapping
modelBuilder.Entity<Person>().HasOptional(x => x.Account).WithRequired(x => x.Owner);
What I would like to do is to be able to delete the optional child from the parent. I would expect this to work.
using (Context ctx = new Context())
{
var personToDeleteFrom = ctx.Persons.Single(x => x.Id == <personid>);
personToDeleteFrom.Account = null;
ctx.SaveChanges();
}
However, the child object of the relationship is simply left in the database. Is there a way to make this work? If not, what is the best practice for handling this type of relationship?
You aren't actually removing the child data just by setting the navigation property equal to null. You need to actually delete the data to get it to go away.
Just change the setting of the null to a Remove on the Accounts collection instead.
using (Context ctx = new Context())
{
var personToDeleteFrom = ctx.Persons.Single(x => x.Id == <personid>);
ctx.Accounts.Remove(personToDeleteFrom.Account);
ctx.SaveChanges();
}
This will remove the Account.
This is due to the behavior of how Entity Framework handles 1:1 relationships. EF doesn't actually add foreign key fields in the database as they are unnecessary. Instead it just maintains the relationship that the primary key for an Account always equals the primary key for the associated Person.
You can see this behavior arise if you attempt to do the following.
using (Context ctx = new Context())
{
var person = ctx.Persons.Single(x => x.Id == <personid>);
person.Account = null;
ctx.SaveChanges();
person.Account = new Account();
ctx.SaveChanges();
}
This will throw a System.Date.Core.Entity.UpdateException as it attempts to add an entry to the Accounts table with a primary key set to <personid> when one already exists.
As such, nulling out the navigation property doesn't actually do anything. The relationship is maintained by keeping the primary keys of each entity in sync. To actually remove the Account you need to delete it from the table.

MVC/EF - How to add an entity association

I have an Entity in EF called Advertiser and another called Client. Advertisers have a association field called Client, which is selected from a dropdownlist. I want to know how to save this association to the database. The approach I've used is to find the Client object (by using the Id) and then assign this Client object to the Advertiser.Client navigation property. I hoped that by then Adding this Advertiser property, I'd have added an Advertiser that is associated with an existing Entity. However this was not the result. Instead, a new Client record also got added to the table. How do I fix this?
Full explanation and code bits are below...
public class Advertiser
{
public int Id { get; set; }
public string Name { get; set; }
//Navigation Properties
public virtual Client Client { get; set; }
}
And another called Client
public class Client
{
public Client()
{
Advertisers = new List<Advertiser>();
}
public int Id { get; set; }
public string Name { get; set; }
// Navigation Properties
public virtual ICollection<Advertiser> Advertisers { get; set; }
}
A bunch of clients are added to the database in a separate view. When the user lands on the Advertiser views, they have the create option. What I want the create to do is allow the user to pick a client from a drop down list containing all clients. I want this advertiser to then be associated with that client.
This is the code for the controller:
//
// POST: /Advertiser/Create
[HttpPost]
public ActionResult Create(Advertiser advertiser,int Id)
{
if (ModelState.IsValid)
{
advertiser.Client = clientRepo.Retrieve(Id); // Finds and returns a Client object
System.Diagnostics.Debug.WriteLine("Saving Advertiser, with int Id = {0} and Client.Id = {1}", Id, advertiser.Client.Id);
repo.Create(advertiser);
repo.SaveChanges();
return RedirectToAction("Index");
}
return View(advertiser);
}
The Advertiser view populates a dropdownlist with all the Clients and then returns the Id for the currently selected Client.
<div class="editor-field">
#{
var clients = new Repository<Client>().FetchAll();
var clientLists = new SelectList(clients, "Id", "Name");
}
#Html.DropDownList("Id", clientLists)
</div>
Now, this view correctly returns the correct Id. The Debug.Writeline also confirms that the correct Id is being passed back. The problem lies in what happens after that...
Instead of inserting a new Advertiser that is associated with the existing Client entity, what it does is, it first inserts an Advertiser, and then inserts a copy of the Client entity to the database. This results in duplicate Clients that differ only in primary key (Id),
I know this can be solved by exposing the foreign key and passing the foreign key instead of finding and referencing the appropriate Client to the the Advertiser.Client property. But if possible I'd prefer to do this without exposing foreign keys. Is there some way this can be done? ... i.e. What am I doing wrong?
If what goes on in the Repository class could be useful to answer this question, I've added it below:
public OperationStatus Create(TEntity item)
{
OperationStatus status = new OperationStatus {Status = true};
var value = DataContext.Set<TEntity>().Add(item);
if(value == null)
{
status = null;
}
return status;
}
public TEntity Retrieve(int id)
{
return DataContext.Set<TEntity>().Find(id);
}
Add a [Key] Attribute to the ID property on both Client and Advertiser

Lots of problems (foreign key or no rows were updated errors) when trying to save complex objects in EF code first

I have a pretty deep object hierarchy in my application, and I am having trouble saving the entities. Depending on the order I do things, I either one of two errors:
[OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.]
or
[DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.]
Here is the classes I am working with:
public class SpecialEquipment : Entity
{
public Equipment Equipment { get; set; }
public virtual ICollection<AutoclaveValidation> Validations { get; set; }
}
public class Equipment : Entity
{
public string Model { get; set; }
public string SerialNumber { get; set; }
public Location Location { get; set; }
public EquipmentType EquipmentType { get; set; }
public ICollection<Identifier> Identifiers { get; set; }
}
public class Identifier : Entity
{
public IdentifierType Type { get; set; }
public string Value { get; set; }
}
public class Location : Entity
{
public Building Building { get; set; }
public string Room { get; set; }
}
What I was trying to do was populate one SpecialEquipment object based on form inputs and already existing objects in the database and then save the special equipment to push all changes through, it looks like this:
Building building = buildingService.GetExistingOrNew(viewModel.BuildingCode)); //checks to see if building exists already, if not, create it, save it, and re-query
Location location = locationService.GetExistingOrNew(viewModel.Room, building); //checks to see if location exists already, if not, create it, save it, and re-query
EquipmentType equipmentType = equipmentTypeService.GetOne(x => x.Name == EquipmentTypeConstants.Names.Special);
Equipment equipment = new Equipment{ EquipmentType = equipmentType, Location = location };
equipment.Identifiers = new Collection<Identifier>();
foreach (FormIdentifier formIdentifier in identifiers)
{
FormIdentifier fIdentifier = formIdentifier;
IdentifierType identifierType = identifierTypeService.GetOne(x => x.Id == fIdentifier.Key);
equipment.Identifiers.Add(new Identifier { Type = identifierType, Value = fIdentifier.Value });
}
EntityServiceFactory.GetService<EquipmentService>().Save(equipment);
SpecialEquipment specialEquipment = new SpecialEquipment();
specialEquipment.Equipment = equipment;
specialEquipmentService.Save(specialEquipment);
This code returns Store update, insert, or delete statement affected an unexpected number of rows (0). If I comment out the foreach identifiers OR put the foreach identifiers after the equipment save and then call equipment save after the loop the code works. If I comment out the foreach identifiers and the save equipment line, I get : The INSERT statement conflicted with the FOREIGN KEY constraint "SpeicalEquipment_Equipment". The conflict occurred in database "xxx", table "dbo.Equipments", column 'Id'.
So how can I make these errors not occur but still save my object? Is there a better way to do this? Also I don't like saving my equipment object, then associating/saving my identifiers and/or then my special equipment object because if there is an error occurring between those steps I will have orphaned data. Can someone help?
I should mention a few things that aren't inheritly clear from code, but were some answers I saw for similar questions:
My framework stores the context in the HttpContext, so all the service methods I am using in my API are using the same context in this block of code. So all objects are coming from/being stored in one context.
My Entity constructor populates ID anytime a new object is created, no entities have a blank primary key.
Edit: At the request of comments:
My .Save method calls Insert or Update depending on if the entity exists or not (in this example insert is called since the specialEquipment is new):
public void Insert(TClass entity)
{
if (Context.Entry(entity).State == EntityState.Detached)
{
Context.Set<TClass>().Attach(entity);
}
Context.Set<TClass>().Add(entity);
Context.SaveChanges();
}
public void Update(TClass entity)
{
DbEntityEntry<TClass> oldEntry = Context.Entry(entity);
if (oldEntry.State == EntityState.Detached)
{
Context.Set<TClass>().Attach(oldEntry.Entity);
}
oldEntry.CurrentValues.SetValues(entity);
//oldEntry.State = EntityState.Modified;
Context.SaveChanges();
}
GetExistingOrNew for Building and location both are identical in logic:
public Location GetExistingOrNew(string room, Building building)
{
Location location = GetOne(x => x.Building.Code == building.Code && x.Room == room);
if(location == null)
{
location = new Location {Building = building, Room = room};
Save(location);
location = GetOne(x => x.Building.Code == building.Code && x.Room == room);
}
return location;
}
Get one just passes that where predicate to the context in my repository with singleOrDefault. I am using a Service Layer/Repository Layer/Object Layer format for my framework.
Your Insert method does not seem to be correct:
public void Insert(TClass entity)
{
if (Context.Entry(entity).State == EntityState.Detached)
Context.Set<TClass>().Attach(entity);
Context.Set<TClass>().Add(entity);
Context.SaveChanges();
}
specialEquipment is a new entity and the related specialEquipment.Equipment as well (you are creating both with new)
Look what happens if you pass in the specialEquipment into the Insert method:
specialEquipment is detached because it is new
So, you attach it to the context
Attach attaches specialEquipment and the related specialEquipment.Equipment as well because both were detached from the context
Both are in state Unchanged now.
Now you add specialEquipment: This changes the state of specialEquipment to Added but not the state of specialEquipment.Equipment, it is still Unchanged.
Now you call SaveChanges: EF creates an INSERT for the added entity specialEquipment. But because specialEquipment.Equipment is in state Unchanged, it doesn't INSERT this entity, it just sets the foreign key in specialEquipment
But this FK value doesn't exist (because specialEquipment.Equipment is actually new as well)
Result: You get the FK constraint violation.
You are trying to fix the problem with calling Save for the equipment but you have the same problem with the new identifiers which will finally throw an exception.
I think your code should work if you add the specialEquipment (as the root of the object graph) at the end once to the context - without attaching it, so that the whole graph of new objects gets added, basically just:
context.Set<SpecialEquipment>().Add(specialEquipment);
context.SaveChanges();
(BTW: Your Update also doesn't look correct, you are just copying every property of entity to itself. The context won't detect any change and SaveChanges won't write any UPDATE statement to the database.)
My guess? It can't have an ID if you haven't saved it and that's the root of the problem (since it works if you save first).
Pop everything in a transaction, so if anything goes wrong all is rolled back. Then you don't have orphans.
See http://msdn.microsoft.com/en-us/library/bb738523.aspx for how to use transactions with EF.

Categories