I am currently working on a new ASP.NET MVC Application using Entity Framwork 6. I have 2 classes which are working together: Account and AccountAddition. Account represents the basic user account and AccountAddition contains for example ICollections of contacts, notifications or the time of last login.
Now I am simply trying to add a new Account to AccountAdditions ICollection Contacts property via curAcc.Addition.Contacts.Add(correspondingAccount)
where correspondingAccount
is the account object to add (both accounts are given in the database).
The problem is that when I am adding the correspondingAccount
to the contacts of the current account, the reference correspondingAccount.Addition
suddently points to the AccountAddition
of the current account.
The problem part:
...
Account curAcc = context.Accounts.FirstOrDefault(p => p.Alias == currentAlias);
if(ModelState.IsValid)
{
var correspondingAccount = context.Accounts.FirstOrDefault(p => p.Alias == addAlias);
if(correspondingAccount != null)
{
curAcc.Addition.Contacts.Add(correspondingAccount);
context.SaveChanges();
...
The code first classes:
public class Account
{
public Account()
{
IsEnabled = true;
}
[Key]
public int ID { get; set; }
public string Alias { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
public byte[] Salt { get; set; }
[ForeignKey("Config")]
public int ConfigID { get; set; }
public virtual CryptoConfig Config { get; set; }
[ForeignKey("Addition")]
public int AdditionID { get; set; }
public virtual AccountAddition Addition { get; set; }
public virtual ICollection<AccountRoleLink> Roles { get; set; }
public bool IsEnabled { get; set; }
}
public class AccountAddition
{
public AccountAddition()
{
CreatedOn = DateTime.Now;
Contacts = new List<Account>();
Notifications = new List<Notification>();
}
[Key]
public int ID { get; set; }
public virtual ICollection<Account> Contacts { get; set; }
public virtual ICollection<Notification> Notifications { get; set; }
public string ContactEmail { get; set; }
public Nullable<DateTime> LastLogin { get; set; }
public Nullable<DateTime> LastFailedLogin { get; set; }
public Nullable<DateTime> CreatedOn { get; set; }
}
More example:
Account curAcc = // Find current account (current context)
Account correspondingAcc = // Get account to add (same context)
curAcc.Addition is Addition with id 1
correspondingAcc.Addition is Addition with id 2
// correspondingAcc gets added via curAcc.Addition.Contacts.add(cor...)
// context saves changes
curAcc.Addition is Addition with id 2 afterwards
correspondingAcc.Addition is Addition with id 2 afterwards
I tried googling and searching in EF tutorials but found no solution. What is wrong?
Thanks for your time
UPDATE:
Ok apparently the provided solution by Ody did not work as expected. I tried removing the virtual keyword from the Notification and Contact collections. The snipped looks now like this:
Account curAcc = GetCurrentAccount();
if(ModelState.IsValid)
{
var correspondingAccount = context.Accounts.FirstOrDefault(p => p.Alias == addAlias);
if(correspondingAccount != null)
{
curAcc.Addition.Contacts.Add(correspondingAccount);
var existingCorrespondingAccount = curAcc.Addition
.Contacts.Where(a => a.Alias == addAlias)
.FirstOrDefault();
if (existingCorrespondingAccount != null)
{
context.Accounts.Add(curAcc);
}
context.SaveChanges();
Right after the Add method is called, the account object in context.Accounts is pointing to the wrong AccountAddition. When I want to add account f2 (AdditionId 2) to account f1 (AdditionId 1), f2's additionId points to 1.
When I call SaveChanges, System.Data.Entity.Infrastructure.DbUpdateException is thrown.
{"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."}
Inner exception is
{"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions."}
I followed the link and tried to catch it using OptimisticConcurrencyException and DbUpdateConcurrencyException, but the ex is DbUpdateException which is not catched by them.
This is frustrating :(
The problem is that whenever you do an add an entity to a related property collection and call SaveChanges()
, it doesn't check to see if the id already exists. It creates a new one anyway. That's how EF works.
To solve your problem, you should check to see if the account already exists in the acc.Contacts before adding it using the ID's manually.
Something like this
Account curAcc = context.Accounts.FirstOrDefault(p => p.Alias == currentAlias);
var correspondingAccount = context.Accounts.FirstOrDefault(p => p.Alias == addAlias);
if (correspondingAccount != null)
{
curAcc.Addition.Contacts.Add(correspondingAccount);
var existingCorrespondingAccount = curAcc.Addition
.Contacts.Where(a => a.Alias == addAlias)
.FirstOrDefault();
if(existingCorrespondingAccount != null)
{
context.Accounts.Add(new Account
{
AdditionID = curAcc.AdditionID,
Alias = curAcc.Alias,
ConfigID = curAcc.ConfigID,
IsEnabled = curAcc.IsEnabled,
Password = curAcc.Password,
Salt = curAcc.Salt
});
}
context.SaveChanges();
}