Entity Framework and RESTful WebAPI - possible circular reference - entity-framework

Here is a simplified version of my model:
public class User {
public int UserID { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
}
I have a controller that I'd like to return a User as well as some summary information about their recipes. The scaffolded controller code looks like this:
var user = await _context.Users.SingleOrDefaultAsync(m => m.UserID == id);
It works fine. Now I try to add the Recipes, and it breaks:
var user = await _context.Users.Include(u => u.Recipes).SingleOrDefaultAsync(m => m.UserID == id);
My web browser starts to render the JSON, and it flickers and I get a message in the browser saying the connection has been reset.
My Theory - I believe that the parent (User) renders, which exposes the child (Recipe) which contains a reference to the parent (User), which contains a collection of the child (Recipe) and so on which is causing an infinite loop. Here's why I think this is happening:
The Visual Studio debugger allows me to navigate the properties in that way infinitely.
If I comment out the Recipe.User property, it works fine.
What I've tried
I tried to just include the data from Recipe that I need using Entity Framework projection (I'm attempting to not include Recipe.User). I tried to only include Recipe.RecipeName... but when I try to use projection to create an anonymous type like this:
var user = await _context.Users.Include(u => u.Recipes.Select(r => new { r.RecipeName })).SingleOrDefaultAsync(m => m.UserID == id);
I receive this error:
InvalidOperationException: The property expression 'u => {from Recipe r in u.Recipes select new <>f__AnonymousType1`1(RecipeName = [r].RecipeName)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.
What is the solution? Can I project with different syntax? Am I going about this all wrong?

Consider using POCOs for serialization rather than doubly-linked entity classes:
public class UserPOCO {
public int UserID { get; set; }
public string FirstName { get; set; }
public ICollection<RecipePOCO> Recipes { get; set; }
}
public class RecipePOCO {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
}
Copy the entity contents to the corresponding POCO and then return those POCO objects as the JSON result. The removal of the User property via usage of the RecipePOCO class will remove the circular reference.

I can propose you 3 options.
U sing [JsonIgnore] on property, but it will work on every use of Recipe class, so when you would like to just return Recipe class you won't have User in it.
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
[JsonIgnore]
public virtual User User { get; set; }
}
You can this solution to stop reference loop in all jsons https://stackoverflow.com/a/42522643/3355459
Last option is to create class (ViewModel) that will only have properties that you want send to the browser, and map your result to it. It is propably best from security reason.

Related

One-to-Many EF .NET Core Relationship Not Working

I have a .net core api application which includes EF to retrieve data. I have set up a data context and I can map tables from the db fine. When I try and set up a relationship though I am always getting a null back for the nested object.
I have an 'Opportunity' class which contains an ICollection of 'Notes'
public class Opportunity
{
public int Id { get; set; }
public string Name { get; set; }
...
public decimal FinalDealProfit { get; set; }
public ICollection<CRMNote> CRMNotes { get; set; }
}
and a Note class that references the opportunity:
public class CRMNote
{
public int Id { get; set; }
public int OpportunityId { get; set; }
public string Note { get; set; }
public string User { get; set; }
public DateTime DateTime { get; set; }
public string FilePath { get; set; }
public Opportunity Opportunity { get; set; }
}
In my context class have the following set up:
modelBuilder.Entity<Opportunity>(entity =>
{
entity.ToTable("CRM_Opportunity");
entity.HasMany<CRMNote>(n => n.CRMNotes)
.WithOne(t => t.Opportunity)
.HasForeignKey(k => k.OpportunityId);
});
and I have also been mapping the Note class:
modelBuilder.Entity<CRMNote>(entity =>
{
entity.ToTable("CRM_Note");
//entity.HasOne<Opportunity>(t => t.Opportunity)
// .WithMany(p => p.CRMNotes)
// .HasForeignKey(k => k.OpportunityId);
});
as you can see I have been playing around with how to connect the entities together.
Whenever I retrieve the opportunity though the notes array is always null. I have tried putting an empty constructor on the Opportunity class:
public Opportunity()
{
CRMNotes = new List<CRMNote>();
}
but this just means I get an empty array rather than a null.
I can't see what I have missed. I have checked the docs for it:
https://www.entityframeworktutorial.net/efcore/one-to-many-conventions-entity-framework-core.aspx
but clearly I have missed something. Any help greatly appreciated as this should be an easy task but something is clearly eluding me.
There are three common O/RM patterns used to load related data
Eager loading,
Explicit loading
and
Lazy loading
For example, in eager loading you can use:
var opportunities=context.opportunities.Include(opportunity=>opportunity.CRMNotes).ToList()

Asp.net core 2.0 The instance of entity type 'X' cannot be tracked because another instance with the key value 'Id:x' is already being tracked

I have been working on a shop site project, using asp.net core spa templates provided with the latest VS2017, and have come across an issue that I haven't had before, possibly because until now my apps were quite simple!
I know what the problem is and where, I just can't fix it. I have a product model which has a collection of "Attributes" and a collection of "Variations" (different colour size, etc) and those variations also have attributes, so if the same Attribute shows up in the Variation (VAttributes), as is already in the main "Attributes" I get the error
InvalidOperationException: The instance of entity type
'ProductAttribute' cannot be tracked because another instance with the
key value 'Id:2' is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached.
The best answer I found was here : https://stackoverflow.com/a/19695833/6749293
Unfortunately, even with the above check I got the error, I even tried making a list of attached attributes, and if the vattribute matched one of the items in the list, I didn't attach it. In fact I found that even if I don't attach (_context.attach()) any of the vAttributes, it still throws the error!.
Here's the code in question:
public async Task<Product> Create(Product product)
{
try
{
foreach (var variation in product.Variations)
{
foreach (var vAttr in variation.VAttributes)
{
bool isDetached = _context.Entry(vAttr).State == EntityState.Detached;
if (isDetached)
_context.Attach(vAttr);
}
}
foreach (var attribute in product.Attributes)
{
bool isDetached = _context.Entry(attribute).State == EntityState.Detached;
if (isDetached)
_context.Attach(attribute);
}
foreach (var category in product.Categories)
{
_context.Attach(category);
_context.Attach(category).Collection(x => x.Children);
}
_context.Products.Add(product);
await Save();
return product;
}
catch (Exception)
{
throw;
}
}
The models for the 3 objects are as follows:
public class Product
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string StockRef { get; set; }
public DateTime? LastModified { get; set; }
//image needed
public ICollection<ProductCategory> Categories { get; set; }
public ICollection<ProductAttribute> Attributes { get; set; }
public ICollection<ProductVariation> Variations { get; set; }
public Product()
{
Attributes = new List<ProductAttribute>();
Variations = new List<ProductVariation>();
Categories = new List<ProductCategory>();
}
}
Variation:
public class ProductVariation
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime? LastModified { get; set; }
public virtual ICollection<ProductAttribute> VAttributes { get; set; }
//needs images
public decimal VPrice { get; set; }
public string VStockRef { get; set; }
}
Finally the Attribute:
public class ProductAttribute
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("AttributeCategory")]
public int AttributeCategoryId { get; set; }
public virtual AttributeCategory AttributeCategory { get; set; }
}
Most help I found when searching was more related to having repo's injected as singletons, or HttpPut methods where the code had check for existence omitting the .AsNoTracking() or it was a mistake that they had the second instance in some way, where I am aware of the second instance, I just don't know how to prevent it from being tracked!
EDIT: I found that adding a foreign key on the ProductVariation model to the Product that was being created failed as it was only a temp key!? anyway removed it from the variation model, so have updated my code. Also thought I'd add one of my earler failed attempts, that led to all of the foreach loops.
_context.AttachRange(product.Attributes);
_context.AttachRange(product.Categories);
_context.AttachRange(product.Variations);
_context.Add(product);
I believe you can allow EF to handle the tracking.
public virtual bool Create(T item)
{
try
{
_context.Add(item);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
This allows for you to save the entire object structure without worring about attaching items.
var newProduct = new Product();
newProduct.Categories.Add(cat);
newProduct.Attributes.Add(att);
newProduct.Variations.Add(vari);
Create(newProduct);

How to get Entity from DB including navigation properties and child list total amount

I have next entity
public class Objective
{
public virtual UserInfo AssignedUser { get; set; }
public int? AssignedUserID { get; set; }
public string ObjectiveText { get; set; }
public virtual ICollection<ObjectiveTask> Tasks { get; set; }
public virtual UserInfo User { get; set; }
public int UserID { get; set; }
}
One objective could has one Assigned User and one User but many Tasks.
After getting Entity from DB I map it to DTO class which looks like this
public class ObjectiveListViewModel
{
public string AssignedString { get; set; }
public string ObjectiveText { get; set; }
public int TasksCount { get; set; }
public string UserContactName { get; set; }
}
Mapping settings doesn't meter
When I do this with query like this
(from objective in context.Set<Objective>() select objective)
.Include(o => o.User)
.Include(o => o.AssignedUser)
.ToListAsync();
Everything works cool - User and Assigned User properties are loaded and no need do extra query to DB to get their data.
But I need return objectives with tasks amount.
To do this I've created a generic class
public class EntitySubCount<TEntity>
{
public TEntity Entity { get; set; }
public int GroupCount { get; set; }
}
And use it in this way
(from objective in context.Set<Objective>() select objective)
.Include(o => o.User)
.Include(o => o.AssignedUser)
.Select(o=> new EntitySubCount<Objective> {
Entity = o,
GroupCount = o.Tasks.Count })
.ToListAsync();
But User and Assigned User properties are not loaded and it require additional query to DB to get their data.
I understand that it because lazy loading.
The question is - how I can get from DB my Entity with loaded nav. properties and with count of Tasks at once?
Thank you for any help
You are close. No need for the includes if you are projecting. In this case I project to an anonymous type, but you could create a ViewModel class to project to if desired.
var objectiveList = context.Objectives
.Select(o => new
{
Entity = o,
// or you could just pick the properties:
ObjectiveText = o.ObjectiveText,
User = o.User,
AssignedUser = o.AssignedUser,
GroupCount = o.Tasks.Count
}).ToList();
EDIT: I see you already have a ViewModel(DTO). You might be looking for something like this:
var objectiveList = context.Objectives
.Select(o => new ObjectiveListViewModel
{
AssignedString = o.AssignedUser.Name,
ObjectiveText = o.ObjectiveText,
TasksCount = o.Tasks.Count
UserContactName = o.User.Name
}).ToList();

Asp.net MVC 4 Code First Return Specific Fields from Navigation Property

Been stuck on this for a while so i thought i would ask. I am sure there is something simple i am missing here. Trying to learn Asp.net mvc 4 on my own by building a simple app.
Here is the model:
public class Category
{
public int Id { get; set; }
[Required]
[StringLength(32)]
public string Name { get; set; }
//public virtual ICollection<Note> Notes { get; set; }
private ICollection<Note> notes;
public ICollection<Note> Notes
{
get
{
return this.notes ?? (this.notes = new List<Note>());
}
}
}
public class Note
{
public int Id { get; set; }
[Required]
public string Content { get; set; }
[Required]
[StringLength(128)]
public string Topic { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual IEnumerable<Comment> Comments { get; set; }
public virtual ICollection<Tag> Tags {get; set;}
public Note()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Note> Notes { get; set; }
public Tag()
{
Notes = new HashSet<Note>();
}
}
I call this method in a repository from the controller:
public IQueryable<Note> GetAll()
{
var query = _db.Notes.Include(x => x.Category).Include(y => y.Tags);
return query;
}
On the home controller i am trying to return a list of all the notes and wanted to include the category name that it belongs to as well as the tags that go with the note. At first the did not show up so i read some tutorials about eager loading and figured out how to get them to show.
However, my method is not that efficient. The mini-profiler is barking at me for duplicate queries because the navigation properties for category and tags are sending queries for the notes again. IS there any way to just return the properties i need for the category and tag objects?
I have tried several methods with no luck. I was hoping i could do something like this:
var query = _db.Notes.Include(x => x.Category.Name).Include(y => y.Tags.Name);
But i get an error: Cannot convert lambda expression to type 'string' because it is not a delegate type
I have seen that error before that was caused by some missing using statements so i already double checked that.
Any suggestions? Thanks for the help

How Do I Resolve "A specified Include path is not valid"?

I have a parent-child relationship setup that is fairly basic. The end result is that I want to be able to return the resulting tables as JSON through ASP.NET MVC WebAPI. I am using Entity Framework 5.0 beta 2.
I can demonstrate the error I'm running into with a simple example. Given the classes Category and Product with the corresponding data context:
public class Category
{
public int CategoryId { get; set; }
public string Title { get; set; }
public virtual IEnumerable<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string Title { get; set; }
public virtual Category Category { get; set; }
public virtual int CategoryId { get; set; }
}
public class ProductDataContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
When I try to run a query that includes the products I get the following error:
A specified Include path is not valid. The EntityType 'FooAndBar.Category'
does not declare a navigation property with the name 'Products'.
The statement to fetch is pretty straightforward:
var everything = dc.Categories
.Include(c => c.Products);
What is the correct way to setup the columns and/or the query so that the Products are included with the Categories?
Child collection properties must be declared as anICollection<T>, not anIEnumerable<T>.
Also, you do not need to explicitly add a CategoryId field to the child class; EF will create that automatically in the database.