Every time I want to updated my record, I am getting the following error:
"The instance of entity type 'User' cannot be tracked because another
instance of this type with the same key is already being tracked. When
adding new entities, for most key types a unique temporary key value
will be created if no key is set (i.e. if the key property is assigned
the default value for its type). If you are explicitly setting key
values for new entities, ensure they do not collide with existing
entities or temporary values generated for other new entities. When
attaching existing entities, ensure that only one entity instance with
a given key value is attached to the context."
Here is my code:
public void SaveRecipient(Recipient myRecipient)
{
if (myRecipient.RecipientGUID == Guid.Empty)
{
myRecipient.RecipientGUID = Guid.NewGuid();
foreach (ContactMethod tmpCM in myRecipient.ContactMethods)
{
context.Entry(tmpCM.Type).State = EntityState.Unchanged;
}
context.Entry(myRecipient.LastModifiedBy).State = EntityState.Unchanged;
context.Entry(myRecipient.Owner).State = EntityState.Unchanged;
context.Entry(myRecipient.CreatedBy).State = EntityState.Unchanged;
context.Recipients.Add(myRecipient);
}
else
{
var dbRecipient = context.Recipients
.Include(a => a.ContactMethods).ThenInclude(t => t.Type)
.Include(b => b.CreatedBy)
.Include(c => c.LastModifiedBy)
.Include(d => d.Owner).ThenInclude(o => o.Users)
.FirstOrDefault(x => x.RecipientGUID == myRecipient.RecipientGUID);
if (dbRecipient != null)
{
dbRecipient.FirstName = myRecipient.FirstName;
dbRecipient.LastName = myRecipient.LastName;
dbRecipient.Company = myRecipient.Company;
foreach (ContactMethod tmpCM in myRecipient.ContactMethods)
{
var dbCM = dbRecipient.ContactMethods.FirstOrDefault(x => x.ContactMethodGUID == tmpCM.ContactMethodGUID);
if (dbCM != null)
{
dbCM.CountryCode = tmpCM.CountryCode;
dbCM.Identifier = tmpCM.Identifier;
dbCM.IsPreferred = tmpCM.IsPreferred;
}
else
{
dbRecipient.ContactMethods.Add(tmpCM);
}
}
//Only update this if it has changed.
if (dbRecipient.LastModifiedBy.UserGUID != myRecipient.LastModifiedBy.UserGUID)
{
dbRecipient.LastModifiedBy = myRecipient.LastModifiedBy;
}
dbRecipient.LastModifiedOn = myRecipient.LastModifiedOn;
}
}
context.SaveChanges();
}
The relevant classes:
User:
public class User
{
[Key]
public Guid UserGUID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public bool IsSiteAdmin { get; set; }
public bool IsActive { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? LastLogin { get; set; }
}
Recipient:
public class Recipient
{
[Key]
public Guid RecipientGUID { get; set; }
[Required(ErrorMessage = "Please enter a Recipient's First Name.")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Please enter a Recipient's Last Name.")]
public string LastName { get; set; }
public string Company { get; set; }
public UserGroup Owner { get; set; }
public virtual ICollection<ContactMethod> ContactMethods { get; set; }
public User CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public User LastModifiedBy { get; set; }
public DateTime LastModifiedOn { get; set; }
public bool IsActive { get; set; }
}
Contact Methods:
public class ContactMethod
{
[Key]
[HiddenInput(DisplayValue = false)]
public Guid ContactMethodGUID { get; set; }
[ForeignKey("ContactMethodTypeGUID")]
public virtual ContactMethodType Type { get; set; }
public string CountryCode { get; set; }
[Required]
public string Identifier { get; set; }
public bool IsPreferred { get; set; }
[ForeignKey("RecipientGUID")]
public virtual Recipient Owner { get; set; }
}
This issue happens when I want to update a recipient, and it is another user doing the updating. So say user abcd did the last update, but now user zyx updates the record. So the Recipeint.LastUpdatedBy is set to the current Session User. When I do that, I get the above error. I cannot figure out how to get beyond this.
A small note: if I add this:
context.Entry(myRecipient.LastModifiedBy).State = EntityState.Unchanged;
in the if (dbRecipient.LastModifiedBy.UserGUID != myRecipient.LastModifiedBy.UserGUID)
statement, and say user lastmodifiedby is set to user abc. Now User asfg updates this recipient for the first time, it goes through, and LastModifiedBy will be set to user asfg, but say user abc goes back and changes the recipient again, so lastmodifiedby goes back to abc, it fails, with the same error.
this is driving me nuts and I cannot figure it out!!!
I got the answer to this from Arthur Vickers at Microsoft. I wanted to share.
The code that sets the navigation property dbRecipient.LastModifiedBy is setting it to an entity instance that is not being tracked by the context. It seems like in this case the context is already tracking another instance for this same entity--presumably because it was brought in by the query through including the CreatedBy navigation.
EF can't track two instances of the same entity, which is why the exception is thrown, so you will need to give EF additional information here to know what to do. This can be complicated in the general case.
For example: if the tracked instance has properties that have been modified in the other instance.
However, assuming that isn't the case, then you can just lookup the instance that is being tracked and use it instead, For example:
if (dbRecipient.LastModifiedBy.UserGUID != myRecipient.LastModifiedBy.UserGUID)
{
dbRecipient.LastModifiedBy = test.Set<User>().Find(myRecipient.LastModifiedBy.UserGUID);
}
Related
When trying to create a new database entry of type TestForm2 I include the related object Unit Type's ID as a foreign key, except when I perform context.SaveChanges() after adding the new model I get the following SQL exception:
SqlException: Violation of PRIMARY KEY constraint 'PK_dbo.UnitTypes'. Cannot insert duplicate key in object 'dbo.UnitTypes'. The duplicate key value is (2d911331-6083-4bba-a3ad-e50341a7b128). The statement has been terminated.
What this means to me is that it thinks that the foreign entry I'm trying to relate to the new model is instead a new object that it's attempting to insert into the UnitTypes table and failing because it sees an existing entry with the same primary key.
For context (pun not intended), this is my data context, the database model, and the erroring "Create" function.
public class DataContext : IdentityDbContext<ApplicationUser>
{
public DataContext() : base("DefaultConnection")
{
}
public static DataContext Create()
{
return new DataContext();
}
public DbSet<SafetyIncident> SafetyIncidents { get; set; }
public DbSet<ProductionLine> ProductionLines { get; set; }
public DbSet<ProductionOrder> ProductionOrders { get; set; }
public DbSet<SerialOrder> SerialOrder { get; set; }
public DbSet<QualityError> QualityErrors { get; set; }
public DbSet<PSA> PSAs { get; set; }
public DbSet<TestStation> TestStations { get; set; }
public DbSet<ProductionGoal> ProductionGoals { get; set; }
public DbSet<DailyWorkStationCheck> DailyWorkStationChecks { get; set; }
public DbSet<TestForm> TestForms { get; set; }
public DbSet<User> AppUsers { get; set; }
public DbSet<Options> Options { get; set; }
public DbSet<DriveList> DriveSerials { get; set; }
public DbSet<MRPController> MRPControllers { get; set; }
public DbSet<TestOption> TestOptions { get; set; }
public DbSet<UnitType> UnitTypes { get; set; }
public DbSet<UnitTypeMap> UnitTypeMaps { get; set; }
public DbSet<TestForm2> TestForm2s { get; set; }
public DbSet<TestFormSection> TestFormSections { get; set; }
public DbSet<TestFormSectionStep> TestFormSectionSteps { get; set; }
}
public class TestForm2 : BaseEntity
{
public string SerialNumber { get; set; }
public string MaterialNumber { get; set; }
public string UnitTypeId { get; set; }
public UnitType UnitType { get; set; }
public bool UsesStandardOptions { get; set; }
public bool OptionsVerified { get; set; } // This will only be used when UsesStandardOptions is true, otherwise its value doesn't matter
public ICollection<TestOption> AllOptions { get; set; } // List of all options (at time of form creation)
public ICollection<TestOption> Options { get; set; } // The options on a unit
public ICollection<TestFormSection> Sections { get; set; }
}
public FormViewModel Create(FormViewModel vm)
{
using (var context = new DataContext())
{
List<string> optionListStrings = GetOptionListForModelNumber(vm.MaterialNumber); // returns list of option codes
List<TestOption> matchingOptions = context.TestOptions
.Where(optionInDb =>
optionListStrings.Any(trimOption => trimOption == optionInDb.OptionCode)).ToList();
var unitType = context.UnitTypes.FirstOrDefault(x => x.Name == vm.UnitType);
string unitTypeId = unitType.Id;
TestForm2 newForm = new TestForm2
{
// ID & CreatedAt instantiated by Base Entity constructor
SerialNumber = vm.SerialNumber,
MaterialNumber = vm.MaterialNumber,
UnitTypeId = unitType.Id,
UsesStandardOptions = vm.UsesStandardOptions,
OptionsVerified = vm.OptionsVerified,
//AllOptions = context.TestOptions.ToList(),
//Options = matchingOptions,
Sections = vm.Sections,
};
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
context.TestForm2s.Add(newForm);
context.SaveChanges(); // THIS IS WHERE THE SQL EXCEPTION IS HAPPENING
return vm;
}
return null;
}
Lastly, I'm not sure if it's relevant, but a full copy of the related UnitType is viewable as part of newForm only after context.TestForm2s.add(newForm) resolves. This is weird to me since I don't think it should be automatically relating the data object like that.
I haven't been able to try much since everything looks properly configured to me. Please let me know if this is not the case or if I should include any other info.
Found the issue. The vm.Sections was not using viewmodels to contain the section data, so the vm.Sections contained UnitType database models. Since this was instantiated in the controller (before opening the data context in the TestForm2 Create method) EF assumed that these data were new and needed to be added to the UnitType table.
Hope this thread helps someone else running into similar issues.
I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?
I am using Entity Framework code first with fluent API I have an items table with foreign keys from users and units tables
but when I load the table to ObservableCollection then bind it to a datagrid the table normal column load it's data normally into the datagrid excpet for the foreign keys which show nothing but when i insert a break point to see the data inside the ObservableCollection I can see that every thing from Users and Units table is there
private void MainContentsWindow_ContentRendered(object sender, EventArgs e)
{
using (var db2 = new DataContext())
{
var AllItems2 = new ObservableCollection<Model.Items.Item>(db2.Items);
ItemsDataGrid.ItemsSource = AllItems2;
}
}
Users
public class User
{
public User()
{
Id = Guid.NewGuid();
IsActive = false;
}
public Guid Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public UserGroup Group { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Items.Item> Items { get; set; } = new List<Items.Item>();
}
public enum UserGroup
{
Administrator = 1,
User,
Unknown
}
base
public class NormalBaseModel : CommonBase
{
public NormalBaseModel()
{
Id = new Guid();
CreateDate = DateTime.Now;
EditDate = null;
}
public Guid Id { get; set; }
public string Notes { get; set; }
public virtual User CreateBy { get; set; }
public DateTimeOffset? CreateDate { get; set; }
public virtual User EditBy { get; set; }
public DateTimeOffset? EditDate { get; set; }
}
items
public class Item : NormalBaseModel
{
public string NameAr { get; set; }
public string NameEn { get; set; }
public int? ManualId { get; set; }
public string Barcode { get; set; }
public byte?[] Image { get; set; }
public virtual Unit Unit { get; set; }
public string MadeIn { get; set; }
public bool IsSerail { get; set; }
public bool IsExpire{ get; set; }
}
Here is a test project on Github
https://github.com/ahmedpiosol/psychic-parakeet.git
https://imgur.com/a/zimd4
When you load your items via EF it needs to create new instances of User and Item. Behind the scenes, EF will call the constructor for each new instance. Your problem is in your constructors:
public User()
{
Id = Guid.NewGuid(); // <- here
}
Your constructor reassigns a new ID each time an instance is created, this will break the referential integrity and cause all sorts of other problems.
Your code doesn't know the difference between creating a new User and recreating a User instance from the database.
I suggest removing the assignments from inside your constructor and placing this either in a static Create method or place wherever you are creating a new User or Item.
p.s. WPF is irrelevant to your problem here.
Fluent API needs to specify foreign key in code, something like
modelBuilder.Entity<Items>()
.HasRequired(o => o.User)
.WithMany(c => c.Items)
.HasForeignKey(o => o.UserId);
I am facing the same issue as described in this question. Problem: my method GetAllConferences() returns correctly all the conferences from the DB, but when I return the result to the View from the controller return Ok(tripListVm) inly the first collection item is returned to the client. On the otehr side, by setting to null all the FK references (as pointed out in the SO question above) I can return correctly all the entities to the client, however this does not seem to me the proper way of proceeding.
EDIT: the solution was much simpler than I though. In the code below (I leave it in its original form for others to see it) I was not mapping the FK entities inside the ViewModel to Dto objects, but returning the model entity itself. That was the reason why I needed to null those inner references to make it work. By returning all Dtos objects, it works properly.
I have three entities involved with 1-many relationships:
public class Conference
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Venue> Venues { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
}
public class Venue
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public int? ConferenceId { get; set; }
public Trip Conference { get; set; }
public int? LocationId { get; set; }
public City City { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Conference> Conferences { get; set; }
public ICollection<Venue> Venues { get; set; }
}
In the repository, I have a method that returns the conferences and the related entities (City and Venues):
public IEnumerable<Conference> GetAllConferences()
{
return _context.Conferences
.Include(t => t.Venues)
.Include(t => t.City)
.ToList();
}
In the controller I need to use the following code to return all the results:
var conferences = _repository.GetAllConferences();
if (conferences.Any())
{
var conferenceListVm = trips.ToConferenceVmList();
//Without setting the FK references to null, I can return only the first result of the collection
foreach (var vm in conferenceListVm)
{
foreach (var pm in vm.PoinOfInterests)
{
pm.Trip = null;
}
vm.Location.Conferences = null;
vm.Location.Venues = null;
}
return Ok(conferenceListVm);
}
public static ConferenceViewModel ToConferenceVm(this Conference conference)
{
var confVm = new ConferenceViewModel();
confVm.Name = conference.Name;
confVm.City = conference.City;
confVm.Venues = conference.Venues;
return tripVm;
}
public static IEnumerable<ConferenceViewModel> ToConferenceVmList(this IEnumerable<Conference> conferences)
{
return conferences.Select(c => c.ToConferenceVm()).ToList();
}
It's been quite a while since I last used EF. I've never had any problems using it before. Now I'm attempting to insert an object that has a one-many relationship with another object. But in the API call, the collection array of the child object is shown to be empty however the parent object can be seen in the api call of the child object.
I have my models as below:
Conversation Table
public class Conversation
{
public Conversation()
{
this.ChatMessages = new List<ChatMessage>();
this.DeletedConversations = new List<ConversationDeleted>();
}
public int ConversationID { get; set; }
public string toUser { get; set; }
public string FromUser { get; set; }
[InverseProperty("Conversation")]
public ICollection<ChatMessage> ChatMessages { get; set; }
public ICollection<ConversationDeleted> DeletedConversations { get; set; }
public DateTime CreatedAt { get; set; }
public int UserID { get; set; }
}
ChatMessage Table
public class ChatMessage
{
public int ChatMessageID { get; set; }
public string fromUser { get; set; }
public string toUser { get; set; }
public string Message { get; set; }
public bool DeliveryStatus { get; set; }
public DateTime CreatedAt { get; set; }
public Guid UniqueID { get; set; }
public int ConversationID { get; set; }
[ForeignKey("ConversationID")]
public virtual Conversation Conversation { get; set; }
public ICollection<MessageDeleted> MessagesDeleted { get; set; }
public int UserId { get; set; }
}
My Fluent API looks like this:
modelBuilder.Entity<ChatMessage>()
.HasRequired(x => x.Conversation)
.WithMany(x => x.ChatMessages)
.HasForeignKey(x => x.ConversationID);
I'm trying to create a conversation entity and add a chat object to it's collection. I do it like so:
public IHttpActionResult CreateConversation()
{
ChatMessage msg = new ChatMessage { CreatedAt = DateTime.UtcNow, DeliveryStatus = true, fromUser = "annettehiggs", toUser = "terrydriscoll", Message = "Hum tum", UniqueID = Guid.NewGuid(), UserId = 43 };
Conversation conv = new Conversation();
conv.ChatMessages.Add(msg);
conv.CreatedAt = DateTime.UtcNow;
conv.FromUser = "annettehiggs";
conv.toUser = "terrydriscoll";
DataModel db = new DataModel();
db.Conversations.Add(conv);
db.SaveChanges();
return Ok(conv);
}
and this is how I retrieve the conversation object:
public IQueryable<Conversation> GetConversations()
{
return db.Conversations;
}
As a result, ChatMessage API call shows the conversation it's associated to but the Conversation object doesn't show the chat in it's collection. What am I doing wrong here?
The add code is working properly (otherwice you'll not be able to see the new chat message). The problem is with your data retrieval code.
Since your ChatMessage.Conversation property is marked as virtual, most probably it gets lazy loaded, that's why you see it populated.
At the same time, your Conversation.ChatMessages is not virtual, hence you need to explicitly eager load it using the Inlclude method, or depending on your requirements, mark it virtual to get the lazy load behavior like the inverse navigation property.