I'm using Web API 2 and Entity Framework 6 and Identity 2
I have product model which relates to an ApplicationUser model, where I create Product, I get an error:
Additional information: An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
My model:
public class Product {
public int id { get; set; }
public string url { get; set; }
public string name {get;set}
public ApplicationUser user { get; set; }
}
My create code:
public IHttpActionResult PostProduct(Product product) {
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(new ApplicationDbContext()));
product.user = userManager.FindById(User.Identity.GetUserId());
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
db.Products.Add(product);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = product.id }, product);
}
What dbcontext does "db" refer to?
I would guess that you probably have more than one dbcontext and you are trying to retrieve data from one and persist it on another.
Maybe you can change the code to create just one dbcontext per http request and reuse that context during your http post request.
Maybe change:
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(new ApplicationDbContext()));
to:
ApplicationUserManager userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
This will let you save your product but you should avoid sending back an EF object, or at least switch off lazy loading.
To clarify a bit, don't send product back in the return as it contains properties which are EF proxies for lazy loading. This will cause at best, a lot more data than you want going back when you serialise the product object and at worst - the error you describe in the comments.
Related
I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:
Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
public class Person
{
public int PersonId { get; set; }
public string Personname { get; set }
public ICollection Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string Line1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person Person { get; set; }
public ICollection<Feature> Features { get; set; }
}
// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
public int FeatureId { get; set; }
public string Featurename { get; set; }
public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
public void UpdateCandidate(Person newPerson)
{
Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
.....
.....
.....
}
Error:
Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Kindly suggest.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.
What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var person = dbContext.People.Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
I am trying to separate my domain model (which is responsible for my business logic) with my data model which is what EF 7 uses to build my schema.
I am running into a problem with regards to persisting changes made to my domain model to my database
So for example given that I have the Data Model PersonTable and the Domain model Person:
public class PersonTable
{
public virtual Guid Id { get; set; }
public virtual String Name { get; set; }
public virtual String Surname { get; set; }
}
public class Person
{
public virtual Guid Id { get; set; }
public virtual String Name { get; set; }
public virtual String Surname { get; set; }
//Other Domain Methods and Constructors...
}
And I want to persist my domain changes to my database by doing this:
public void Update(Person p)
{
var person = new PersonTable
{
Id = p.Id,
Name = p.Name,
Surname = p.Surname
}
PersonDbContext.Update(person);
PersonDbContext.SaveChanges();
}
When I try to apply these changes I get an InvalidOperationException saying
"The instance of entity type 'Tables.PersonTable' cannot be tracked because another instance of this type with the same key is already being tracked. For new entities consider using an IIdentityGenerator to generate unique key values."
I assume this has something to do with entity tracking but how does this work in EF 7? I want to be able to apply this pattern so that I can seperate these two models.
// Before updating we have fetch original data row from database.
// So we need to use AsNoTracking().
var originalPerson = PersonDbContext.Person.AsNoTracking().FirstOrDefault(p => p.Id == id);
and at time of save we need to use following code.
PersonDbContext.Entry(originalPerson).Context.Update(newPersonDetail);
The error occurs when your instance of PersonDbContext is already tracking an instance of Person with the same Id.
To debug this, inspect what you have tracking already in PersonDbContext.ChangeTracker.Entries(). I expect you will discover you already have an entry for PersonTable.Id that matches the one you are attempting to update.
To ensure you don't get these conflicts, don't reuse PersonDbContext across threads or multiple HTTP requests (if this is a web app.)
I was facing the same error. I found the answer in this other thread here
https://stackoverflow.com/a/31840042/1716912. I changed my repository DI to be scoped from being a singleton.
I want to use EF behind my WCF service to fetch data and display it to the client. I need the following suggestions:
Do I need to have the same interface for all the views (e.g. students, teachers etc.) or do I need to have a different interface and service for every table (or view)
Do I need to generate the database calls within my service (.svc) or some other architecture is preferred?
public Student[] GetAllStudents()
{
//database generation code here
}
How can I use EF code-first approach to generate database. I know that for an MVC app, you need to set the initializer in Global.asax or in web.config but I am not sure how it's called in this case. My model looks like this:
[DataContract]
public class Student
{
[DataMember]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[DataMember]
public string Type { get; set; }
[DataMember]
public string Subject { get; set; }
[DataMember]
public string Description { get; set; }
}
What you really should do is break up your system in to more separate layers. Instead of having a WCF call that directly queries the database, create a "buisness logic" layer that translates the information that the WCF call provides you to what the EF call needs to know. This is called a N-Tier application
public class SchoolAPI : ISchoolAPI
{
private DataAccessLayer _dal = new DataAccessLayer();
public Student[] GetAllStudents()
{
return _dal.GetStudents(null, null);
}
public Student[] GetAllScienceStudents()
{
return _dal.GetStudents(null, DataAccessLayer.ScienceStudentType);
}
}
private class DataAccessLayer
{
public static readonly ScienceStudentType = //...
public Student[] GetStudents(string subject, string type)
{
using(var ctx = new SchoolContext())
{
IQueryable<Student> studentQuery = ctx.Students;
if(subject != null)
studentQuery = studentQuery.Where(s=>s.Subject == subject);
if(type != null)
studentQuery = studentQuery.Where(s=>s.Type == type);
return studentQuery.ToArray();
}
}
}
The caller of the WCF call does not need to know what the string ScienceStudentType is, all it cares about is that it gets the science students. By seperating the business logic from the database call the caller of your service no longer needs to know.
For EF it will initialize on the first time the framework goes out to "touch" the database and detects that it is not there if it is set up to do so. This is done in the constructor of SchoolContext but is getting a little too broad for this answer. I recommend finding a tutorial on EF and get it working in a simple test enviorment without WCF (maybe a simple console app that just calls GetStudents() then move in in to a WCF environment.
I am trying to create a RESTful web service that returns a list of products using ASP.NET MVC4 Web API. Here is my controller class
public class ProductController : ApiController
{
public IEnumerable<Product> GetProducts()
{
WebCatalogContext dbcontext = DatabaseConfig.Instance.Context;
List<Product> plist = dbcontext.Products.ToList();
return plist;
}
}
When I run my service and call the following URL from my browser :/api/Product, I get System.Runtime.Serialization.SerializationException. I looked into my plist object and there is no problem with it.
Here is my data model:
[DataContract(Name = "p")]
[Serializable]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DataMember(Name = "id")]
public int Id { get; set; }
[Required, MaxLength(50)]
[DataMember(Name = "ti")]
public string Title { get; set; }
[Required]
[DataMember(Name = "de")]
public string Description { get; set; }
[Required]
[DataMember(Name = "ph")]
public string PhotoURL { get; set; }
[DataMember(Name = "ca")]
public virtual ProductCategory Category { get; set; }
}
[DataContract(Name="pc")]
[Serializable]
public class ProductCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[DataMember(Name="id")]
public int Id { get; set; }
[DataMember(Name="nm")]
public string Name { get; set; }
}
When I remove the reference to ProductCategory from my Product class, all things work just fine. But, when I include it I get the following exception.
Type 'System.Data.Entity.DynamicProxies.Product_664E9A0AA1F165A26C342B508BFFF1279FD3FE059285225BDA19F407A29A9CAD' with data contract name 'Product_664E9A0AA1F165A26C342B508BFFF1279FD3FE059285225BDA19F407A29A9CAD:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
Any idea about what I am missing?
Regards
Entity Framework has wrapped your POCO with an EF Proxy POCO so it can perform lazy loading - this uses the Virtual attribute to create a 'lazy-loadable' navigation property. I expect that is where the serialization error comes from.
You could make a new class and map the POCO to that - then pass the DTO style class from the controller. I've never returned an EF object directly from the API (I always map to some something else) so I don't know another option.
EF POCO to DTO (data transfer object) is relatively painless if you use a tool like http://valueinjecter.codeplex.com/ or http://automapper.org/
To support Lazy Loading for navigation properties which is declared as virtual, EF will generate the proxies for any models which have navigation properties which leads to this kind of exception.
For very simple application, you can use model from EF as DTOs (if having no navigation properties), but for complex application, you should do separate and differ between DTOs and domain models. It should not be mapping 1:1 between DTO and domain model.
Therefore, in your case, you create more DTO model for Web API layer, it will be fine.
Ok, I'm pretty sure its just a matter of learning... but I have a very normalized db i'm working with so when I save to my product tbl I also have a productDollar tble and so on...
my question is in silverlight everything is async so How do I save a product get back its new id and use that as the productDollar.productID fk
so far with my other saves i just use the submitOperation in the callback of the submitchanges
and in there i check for iscompleted and do the next save and so on... and chain them together like that.
but I have 500 products I need to save (all at once)
so doing a foreach around my product object won't work because of the wonderful async
So what am I missing??? any help or pointers would be GREATLY appreciated
WCF RIA Services had this situation in mind when it was created. You can easily do it all in one SubmitChanges request and in one database transaction (depending on your DB and/or ORM). However, if you provide some more information about your objects (POCO, EF, etc.), you'll get a better answer.
That said, I'll take a wild guess at your objects as defined on the server.
public class Product
{
[Key]
public int? ProductID { get; set; }
// ... more properties ...
[Association("Product-ProductDollars", "ProductID", "ProductID", IsForeignKey = false)]
[Include]
[Composition]
public ICollection<ProductDollar> ProductDollars { get; set; }
}
public class ProductDollar
{
[Key]
public int? ProductDollarID { get; set; }
public int? ProductID { get; set; }
// ... more properties ...
[Association("Product-ProductDollars", "ProductID", "ProductID", IsForeignKey = true)]
[Include]
public Product Product { get; set; }
}
And your DomainService looks something like
public class ProductDomainService : DomainService
{
public IQueryable<Product> GetProducts()
{
// Get data from the DB
}
public void InsertProduct(Product product)
{
// Insert the Product into the database
// Depending on how your objects get in the DB, the ProductID will be set
// and later returned to the client
}
public void InsertProductDollar(ProductDollar productDollar)
{
// Insert the ProductDollar in the DB
}
// Leaving out the Update and Delete methods
}
Now, on your client, you'll have code that creates and adds these entities.
var context = new ProductDomainContext();
var product = new Product();
context.Products.Add(product);
product.ProductDollars.Add(new ProductDollar());
product.ProductDollars.Add(new ProductDollar());
context.SubmitChanges();
This results in one request sent to the DomainService. However, WCF RIA splits this ChangeSet containing the 3 inserts into 3 calls to your DomainService methods:
InsertProduct(Product product)
InsertProductDollar(ProductDollar productDollar)
InsertProductDollar(ProductDollar productDollar)
If your DomainService performs all inserts in one transaction, the ProductID can be correctly managed by your ORM.