EF 4.3 - Why an existing navigation reference property gets inserted again into the database when assigned to parent? - entity-framework-4.3

Issue:
The user creates a new account. One of the required fields is BusinessGroup. BusinessGroup is a navigation reference property. The user select the BusinessGroup from a drop down box, the code searches for the BusinessGroup in the database, retrieves it and assigns it to the Account. Sounds pretty simple, right?
For some reason every time you save a new account it also inserts another BusinessGroup in the database table for BusinessGroups even though it already exists as I retrieved it from the database and assigned it directly yo the account. EF context still thinks it is a new one.
BTW, I use Code-First as I am following TDD approach.
Here is my POCO:
[Table("Account")]
public class Account
{
[HiddenInput]
public int Id { get; set; }
[Required(ErrorMessage = "The Name is required")]
public string Name { get; set; }
public Guid? BusinessGroupId { get; set; }
[Required(ErrorMessage = "The Business Group is required")]
public BusinessGroup BusinessGroup { get; set; }
}
Because I want to override the naming convention of the foreign key that is generated in the database I also specified the foreign key, BusinessGroupId above.
Here is the BusinessGroup POCO:
[Table("BsuinessGroup")]
public class BusinessGroup
{
[HiddenInput]
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
}
Here is the code that fetches the BusinessGroup from the database based on what the user selected from a drop down box, the Id is as GUID (note that this code lives inside an MVC custom model binder so it exposes the ModelBinderContext:
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//see if there is an existing model to update and create one if not
var account = (Account) bindingContext.Model ?? new Account();
//find out if the value provider has a required prefix
bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
var prefix = hasPrefix ? String.Format("{0}.", bindingContext.ModelName) : String.Empty;
_context = bindingContext;
_prefix = prefix;
//map the fields of the model object
account.Id = Convert.ToInt32((GetValue("Id")));
account.Name = (GetValue("Name"));
account.Phone = (GetValue("Phone"));
account.Fax = (GetValue("Fax"));
account.Email = (GetValue("Email"));
account.Website = (GetValue("Website"));
account.Audit = new Audit{Created = DateTime.Now, CreatedBy = "renso"};
//I changed this to assign the ID rather than assign the BusinessGroup itself since that did not work
//and causes a new BusinessGroup to be inserted into the DB on every save of an account,
// this solution seems to work but I am not sure if this is the correct behavior.
**var tempBusinessGroup = ((Controllers.AccountController)controllerContext.Controller).
BusinessGroupRepository.Find(GetGuidValue("BusinessGroup"));
account.BusinessGroupId = tempBusinessGroup.Id;**
return account;
}
private Guid GetGuidValue(string key)
{
var vpr = _context.ValueProvider.GetValue(_prefix + key);
return vpr == null ? new Guid() : new Guid(vpr.AttemptedValue);
}
private string GetValue(string key)
{
var vpr = _context.ValueProvider.GetValue(_prefix + key);
return vpr == null ? null : vpr.AttemptedValue;
}
.............
}
The code in bold above where I assign a value (GUID) to the BusinessGroupId rather than the BusinessGroup seems to work correctly. Is that the intended behavior of EF, a bug or what?
If I changed it to the code below it causes the issue where a new BusinessGroup gets created when I save the new account:
account.BusinessGroup = ((Controllers.AccountController)controllerContext.Controller).
BusinessGroupRepository.Find(GetGuidValue("BusinessGroup"));
Here is my Action:
[HttpPost]
public ActionResult Create(Account account)
{
if (ModelState.IsValid)
{
AccountRepository.InsertOrUpdate(account);
AccountRepository.Save();
return RedirectToAction("List");
}
var viewData = new AccountControllerViewData { Account = account, BusinessGroups = BusinessGroupRepository.All };
return View(viewData);
}
The save also fails, for some reason I need to switch off ValidateOnSaveEnabled=false in the Save on the AccountRepository, not sure why for it to work at all, it complains about the BusinessGroup being missing as I set the BusinessGroupId property of the account:
public void InsertOrUpdate(Account account)
{
if (account.Id == default(Int32))
{
// New entity
_context.Account.Add(account);
}
else
{
// Existing entity
_context.Entry(account).State = EntityState.Modified;
}
}
public void Save()
{
//need the validation switched off or it fails with an error
_context.Configuration.ValidateOnSaveEnabled = false;
_context.SaveChanges();
}
It feels like I am missing something here. I could not find the answer in EF 2ed, Code-First or DbContext books. It seem when you define your own foreign keys in your POCO, then whenever you want to assign a navigation reference to in my example from an Account to a BusinessGroup, you cannot assign to the BusinessGroup directly, you have to asign a value/key to the foreign key, in this example BusinessGroupId. Am I doing something wrong?

Most likely your BusinessGroupRepository and your AccountRepository are having their own context instances. When you load the BusinessGroup from the BusinessGroupRepository it happens in another context than the context in AccountRepository where you save the new account with. For the second context BusinessGroup is an unknown entity and EF will save it as a new entity.
Refactor your code to make sure that both repositories will use the same context instance. The context should not be created within the repositories but created outside and injected into both instead. Alternatively merge the two repositories into one single repository.

Indeed, re-factored the code so that it uses the same DbContext to retrieve the account and business group, that works as expected, Thanks! The question now is that with my repository pattern, I inherit from an abstract class that news up a new context for me, but that still wont solve the problem that I have that each repository has its own context, how did you guys resolve that issue? For example you create a new account, it has a navigation reference for business group, country, state (like PA, NJ), and others, each one has its own repository, for example to maintain lists of countries in the database. An account has a country navigation reference, what pattern should one follow to share the same context, one per http request I would think?

Related

EF Core 5 Duplicating Records in the Many-to-Many Relationship

I'm having trouble implementing the many-to-many relationship using the Entity Framework Core 5 in Visual Studio.
I have the classes:
public class Medico
{
public Medico()
{
this.Especialidades = new HashSet<Especialidade>().ToList();
}
public int Id { get; set; }
public string Nome { get; set; }
public int CRM { get; set; }
public List<Especialidade>Especialidades { get; set; }
public class Especialidade
{
public int Id { get; set; }
public string Descricao { get; set; }
public IList<Medico>Medicos { get; set; }
}
And the Create method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Nome,CRM")] Medico medico)
{
var lstTags = Request.Form["chkTags"];
if (!string.IsNullOrEmpty(lstTags))
{
int[] splTags = lstTags.ToString().Split(',').Select(Int32.Parse).ToArray();
if (splTags.Count() > 0)
{
var medicoEspecialidades = await _context.Especialidades.Where(t => splTags.Contains(t.Id)).ToListAsync();
foreach (var me in medicoEspecialidades)
{
medico.Especialidades.Add(me);
}
}
}
if (ModelState.IsValid)
{
_context.Medicos.Add(medico);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(medico);
}
But when I run Create, it returns me with the following error:
"Cannot insert explicit value for identity column in table 'Especialidades' when IDENTITY_INSERT is set to OFF."
If I turn off the Identity_Insert of the Specialty table in the bank, it even inserts, but duplicates the records in the Specialty table.
I've been researching and trying to find a solution for 2 days now. Can someone who has been through this give me a hand?
The application source code is here: https://www.dropbox.com/s/xn6b95h7amfpuqa/AppCompleta%205.0.rar?dl=1
The approach looks Ok, though I would check to ensure that the medico being passed in does not have any Especialidade somehow coming in from the client as these would be detached entities. The error seems to imply that Medico may have a detached Especialidade in its collection. If the checked values represents everything that should be tracked, then this collection should be cleared and the Especialidade references added.
Do you have any explicit mapping configuration for either of these entities? If not I would highly recommend using one for Many-to-Many relationships as sometimes EF can default to unexpected schema assumptions when working off convention, especially in Code First if that is the case. I would look at your schema carefully to ensure it is matching what would be expected for a Many-To-Many. For example, what is the linking table name for Medico-Especialidade? Is there an entity defined for it in the configuration? This is entirely optional and EF should work it out, however if you do have explicit mapping that might not be configured correctly, tripping up the relationships.
One other detail giving off a smell:
public Medico()
{
this.Especialidades = new HashSet<Especialidade>().ToList();
}
public List<Especialidade>Especialidades { get; set; }
This should be:
public ICollection<Especialidade> { get; set; } = new HashSet<Especialidade>();
EF can work with lists, but when it comes to proxies and the behind the scenes EF is doing with entities it is generally better to declare your collection references as ICollection rather than concrete classes. ToListing a HashSet merely produces a List, so either = new HashSet<Especialidade>() or = new List<Especialidade>() will do. The difference would merely be the behaviour of the collection when you are populating it after "newing" up a Medico, or deserializing one.

MVC3 + Simple Membership: Accessing User Profiles Through Entity Framework

I'm using the SimpleMembership.MVC3 package with my MVC3 application and I want to be able to access users from the table through Entity Framework
In examples for doing this with MVC4, you can simply create a POCO to mirror the User table that's been generated, add your DbSet in your DbContext implementation and then query the DbSet like you normally would, ie: context.Users.
This collection is always returning 0 items for me even though there are rows in the table. What am I doing wrong? Here's what I got so far:
[Table("User")]
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class TestContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
}
In my service:
model.Accounts = context.Users.ToList();
Thanks!
You do not create your a POCO that mirrors the User table in order to access it directly from EF. There is already a POCO created by the Internet template when you created the project, which you can customize as described here. This same article also shows how you can access the user information by accessing EF directly. You do not create your own context, there is one already in place that you use. Here is a code snippet from that article.
var context = new UsersContext();
var username = User.Identity.Name;
var user = context.UserProfiles.SingleOrDefault(u => u.UserName == username);
var email = user.Email;
The article also has links to download the source code that demonstrates the details on how to do this.
I circumvented the membership classes entirely and implemented a pure EF membership system. I leveraged the System.Web.Helpers Crypto helpers to handle password hashing and just create the AuthCookie when needed.

Additional column in query generated by Entity Framework

I am using EF5 and .NET 4.5 targeting an Oracle 11g database through Oracle.ManagedDataAccess.Client. I set up a small table to test and the how it works.
Now here is a weird fact which shows no result on searching the web nor this site. On every query I have a last column like "Extent1"."Text_TextID"!!! This obviously makes Oracle to throw an error Invalid identifier as I have no column with such name nor another object in the database.
This happens no matter how many tables/columns I have and no matter how I name them (if I have several tables all will have this extra column in the query).
Anybody has any idea why this happens??
Sample code below:
//POCO class and mapping
[Table("LO_USERS")]
public class User
{
[Key]
[Column("USER_ID")]
public int UserID { get; set; }
}
//define the context
public class TestContext : DbContext
{
public TestContext():base("OracleConn")
{
}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//replace the annoying dbo schema name. Note: even if I remove this, I still get the extra column in the query
modelBuilder.Entity<User>().ToTable("LO_USERS", "TEST_SCHEMA");
}
//create a new user
using (var db = new TestContext())
{
var user = new User();
db.Users.Add(user);
//here I set a breakpoint
db.SaveChanges();
}
The query as showing by VS2012 at the breakpoint:
SELECT
1 AS "C1",
CAST( "Extent1"."USER_ID" AS number(10,0)) AS "C2",
"Extent1"."Text_TextID" AS "Text_TextID"
FROM "TEST_SCHEMA"."LO_USERS" "Extent1"
Edit:
It is the same with EF6 and DotConnect.
I found it: the problem was I was referencing User class in another class as child object, like
public class Text
{
public virtual ICollection<User> Users { get; set; }
without specifying any foreign key column in user class and EF was trying to set one by its own.
Once I removed the line above the extra column disappeared from the select statement.

Partial model validation in create/update scenarios

I have a model for a User generated by EF from an existing database:
public class User
{
public int Id { get;set; }
public string Name { get;set; }
public string Password { get;set; }
public DateTime Created { get;set; }
public DateTime LastModified { get;set; }
}
I've been then creating a UserModel and applying data annotations to add validation. I use AutoMapper to translate between User and UserModel.
I am now trying to create the views, with the following business rules:
when creating a user, the Created and LastModified fields should not appear in the UI, but they should both be set prior to the model being saved in the repository;
when editing a user, the Created and LastModified fields should not appear in the UI, but the Created field should be left unchanged and the LastModified field should be updated prior to the model being saved in the repository;
when editing a user, if the password field is not touched, then the Password field in the model is not changed; if the password field contains a value, that should be used to update the model.
So how can I achieve this, with as little code duplication as possible? For instance, should I have an EditUserModel and a CreateUserModel, inheriting from a base UserModel which has the fields common to both (Id, Name, Password)? Should either model have any reference to Created/LastModified? And particularly, how can I handle the password change requirement?
This is how I usually deal with this situation. For the view model, I'm using only one, the EditUserModel, since it really doesn't pay off to maintain 3 classes only 1-2 properties are different and in my opinion the view model isn't THAT important, I'm just being pragmatic. In your case the EditUserModel should look something like this
public class EditUserModel
{
public int Id {get;set} //used when modifying user
public string Name {get;set;}
public string Password {get;set;}
public string ConfirmPassword {get;set;} //this is optionally
}
This model gets passed to a controller and I'd personally use a DDD approach (as a mindset) and have let's say a domain model which looks like this (it's based on code i'm actually using)
public class Password
{
public Password(string value,string salt)
{
//you can apply basic validation rules if you want\\
Hash=(salt+value).Sha256(); //a variant of hashing and an extension method
Salt=salt;
}
public string Hash {get;private set;}
public string Salt {get;private set;}
}
public class Member
{
public Member(string name, Password pwd)
{
Name=name;
Password=pwd;
Created= DateTime.Now;
}
public Member(int id,DateTime created,string name,Password p)
{
Id = id;
Created = created;
_name = name;
_password = p;
}
public int Id { get; set; }
private string _name;
public string Name
{
get { return _name; }
set
{
LastModified = DateTime.Now;
_name = value;
}
}
private Password _password;
public Password Password
{
get { return _password; }
set
{
_password = value;
LastModified = DateTime.Now;
}
}
public DateTime Created {get;private set;}
public DateTime LastModified {get;private set;}
}
First time when creating a user
var user= new Member(model.Name,new Password(model.Password,"a salt"));
repository.Save(user);
When updating
var user= repository.GetUser(int id);
user.Name=model.Name;
if (!string.IsNullOrEmpty(model.Password)) user.Password= new Password(model.Password,"a salt");
repository.Save(user);
In the repository (note that I don;t have much experience with EF so this code surely can be optimized)
public void Save(Member user)
{
using (var dc = new DbContext())
{
if (user.Id==0)
{
//do insert, all this can be handled via automapper
var u= new User();
u.Name=user.Name;
u.Password=user.Password.Hash;
u.Created=user.Created;
u.LastModified=user.LastModifed;
dc.Users.Add(u);
dc.SaveChanges();
user.Id=u.Id;
}
else
{
//do edit
var u= dc.Users.First(d=>d.Id==user.Id);
//map values \\
dc.Users.SaveChanges();// EF should detect if something was changed and save only changes
}
}
}
public Member GetUser(int id)
{
//get User from DbContext \\
var m= new Member(id,user.Created,user.Name,new Password(user.Password,"my salt"));
return m;
}
This is hasty code and all can be improved but let me tell why such 'complicated' approach. First of all we have a clearer differention between the layers responsabilities, the view model handles all required to display and to send back data to controller and the domain model is not mingled with the persistence model (EF model). You can change pretty easily the password hashing techqnique and teh creates/last modified stuff is handled where changes really occur. When you start adding more functionality this design will make it much easier for you.
The main goal is clarity and maintainability and dependeing on the usage you can take another approach like updating the user via commands (you send to repository a command to change the name or the password)
Ok i'm finished now :)
For the first 2 questions, the Created and LastModified fields should be defined as readonly in the model or not mapped at all and you could use INSERT and UPDATE triggers in your database to set their values.
Another possibility is to override the SaveChanges method in your data context and invoke the corresponding method on your entity in order to set the values of those properties just before inserting or updating.
when editing a user, if the password field is not touched, then the
Password field in the model is not changed; if the password field
contains a value, that should be used to update the model.
You could try handling this requirement at the mapping layer.

"An object with the same key already exists in the ObjectStateManager..." exception is thrown when setting an entity state to modified

I followed some examples(including such books as "Pro ASP.NET MVC 3" and "Professional ASP.NET MVC 3") to create simple ASP.NET MVC 3 apps using EF 4.1 (since I'm new to these technologies).
I'm using the following repository(single instance of it is used by all action methods of the controller) to access the DB:
public class ProductRepository : IProductRepository
{
private readonly EFDbContext _context = new EFDbContext();
#region Implementation of IProductRepository
....
public void SaveProduct(Product product)
{
if (product.ProductId == 0)
{
_context.Products.Add(product);
}
else
{
_context.Entry(product).State = EntityState.Modified;
}
_context.SaveChanges();
}
....
}
This repository performs updating as it was shown in the examples I used.
Product class:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
In case of updating the product, I'm getting the exception "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key"
I know that the similar questions have been already discussed here but my question is a bit different:
Why this code which was taken from examples is not working (though it looks pretty simple and straightforward)? What wrong might I have done or missed something.
After searching for hours for a solution, I have found one that seems suitable after doing enough reading.
The fix is here:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
Basically, fetch the record from the Context and call:
var currentProduct = _context.Products.Find(product.ProductId);
_context.Entry(currentProduct).CurrentValues.SetValues(product);
This seems like a bad idea and something I've always hated about EF in my previous workings, but cccording to Ladislav Mrnka (who apparnently answers every EF related question on Stackoverflow) in this post:
Entity Framework and Connection Pooling
EF will store a request for an entity internally, so ideally, it will already be there and it won't be making an additional call back to the database.
The root cause of the problem seems to be that once a product is fetched from the Context, the context is keeping track of it and that's what is causing all the trouble. So merging your changes back in is the only way.
Hope that helps.
It looks like you're not updating product.ProductId when the item is saved for the first time. This means that when you come back to save the item again it's adding it to the context again, hence the error.
As the Id will be added by database (I'm assuming it's the autogenerated Id) then you'll need to read your product data back onto the client.
From a Generics standpoint, here's how I have resolve the same problem very recently:
public TEntity Update(TEntity model, bool persist)
{
if (model == null)
{
throw new ArgumentException("Cannot update a null entity.");
}
var updateModel = Get(model.Id);
if (updateModel == null)
{
return model;
}
this.context.Entry<TEntity>(updateModel).CurrentValues.SetValues(model);
this.Save(persist);
return model;
}

Resources