I'm trying to learn Entity Framework Code First development with ASP.NET MVC3.
Let's say I have a simple data Model for an Auction and Bids and I'd like to query all the Auctions and their Bids.
I have turned off LazyLoadingEnabled and ProxyCreationEnabled.
Here is the code I have:
public class MiCoreDb2Context : DbContext
{
public MiCoreDb2Context()
: base()
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Auction> Auctions { get; set; }
public DbSet<Bid> Bids { get; set; }
}
public class Auction
{
public int AuctionId { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
}
public class Bid
{
public long BidId { get; set; }
public int AuctionId { get; set; }
[ForeignKeyAttribute("AuctionId")]
public virtual Auction Auction { get; set; }
}
public JsonResult Thing()
{
List<Auction> auctions;
using (var db = new MiCoreDb2Context())
{
var auctions = (from a in db.Auctions.Include("Bids") select a).ToList();
}
return Json(auctions, JsonRequestBehavior.AllowGet);
}
When I load the page, a circular reference occurs. How will I get around this?
When I load the page, a circular reference occurs. How will I get around this?
By using view models (and by the way that's the answer to any question you might have concerning ASP.NET MVC :-)). Ayende Rahien has an excellent series of blog posts on this topic.
Conclusion: absolutely always pass/take view models to/from a view. Absolutely never pass/take models (EF, domain, ...) to/from a view. Once this fundamental rule is being respected you will find out that everything works.
I solved this problem by doing a projection in the Linq to Entities query. This will create anonymous types which can be serialized to json without any circular reference issues.
var result =
from Item in dbContext.SomeEntityCollection
where SomePredicate
select new { Property1 = Item.Property1, Property2 = Item.Property2 };
Return Json(result, JsonRequestBehavior.AllowGet);
BOb
Related
I have the following table structure as shown in the picture. (see: Table structure). Both tables ("Batches" and "Methods") reference to a "Project" table.
When I now create a new Project I would like to get all childs created as well.
Doing so I did the follwoing:
_dbContext.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods).AsNoTracking().FirstOrDefault();
Now the problem is the following:
New Batch and Method instances are created - thus they get a new ID(PK). The referenced Project_Id (FK) is set correct. But in my new Measurement instance only the Batch_Id(FK) is set correct and the Method_Id remains unchanged (has the old value) (see: result).
What I need is that the Measurements.Mehtod_Id is set from the Methods table. Is there any suitable solution for that?
My entities look like the following
public class Project
{
[Key]
public long Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public virtual List<Batch> Batches { get; set; }
public virtual List<Method> Methods { get; set; }
}
public class Batch : BaseObject
{
public Batch()
{
BatchFiles = new List<FileAttachment>();
Measurements = new List<Measurement>();
}
public long Id { get; protected set; }
public long Project_Id { get; set; }
public virtual Project Project { get; set; }
public virtual List<Measurement> Measurements { get; set; }
}
public class Method : BaseObject
{
public Method()
{
Parameters = new List<Parameter>();
}
public long Id { get; protected set; }
public long Project_Id { get; set; }
public virtual Project Project { get; set; }
public virtual List<Measurement> Measurements { get; set; }
}
public class Measurement
{
public int Id { get; protected set; }
[ForeignKey("Batch")]
public long? Batch_Id { get; set; }
[Required]
public virtual Batch Batch { get; set; }
[ForeignKey("Method")]
public long? Method_Id { get; set; }
public virtual Method Method { get; set; }
}
// creation code (just a copy with new IDs for all childs)
Project newProjectVersion = _dbContext.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods)
.AsNoTracking().FirstOrDefault();
_dbContext.Projects.Add(newProjectVersion);
_dbContext.SaveChanges();
Thanks for any help!
The first problem is that your Select statement doesn't connect Measurements to Methods because of the AsNoTracking() addition. Only Projects and Methods are connected because they are explicitly Included off of the Project entity. The Measurements have a Method_id but this is value is not accompanied by a Method in their Method property. You could check that in the debugger if you walk through the object graph (with lazy loading disabled though!). Because of this, when all entities will be Add-ed to the context, EF won't notice that measurements receive new methods.
You could get tempted to fix that by Include-ing Measurement.Method as well:
...
.Include(x => x.Batches.Select(y => y.Measurements.Select(m => m.Method)))
...
Now you'll see that Measurement.Method will be populated everywhere in the object graph.
However, there's a gotcha here. When using AsNoTracking, EF6 doesn't keep track of entities it materialized (duh). This means that for each Measurement it creates a new Method instance, even if an identical Method (by id) was materialized before for another Measurement. (And in this case it will always materialize duplicates, because you already include Project.Methods.)
That's why you can't do this in the quick way with AsNoTracking and Add using one context instance. You'll get an error that EF tries to attach duplicate entities.
You must build the object graph using one context, with tracking, so EF will not materialize duplicates. Then you must Add this object graph to a new context. Which will look like this:
Project project;
using(var db = new MyContext())
{
db.Configuration.ProxyCreationEnabled = false;
project = db.Projects.Where(x => x.Id == prjId)
.Include(x => x.Batches)
.Include(x => x.Batches.Select(y => y.Measurements))
.Include(x => x.Methods).FirstOrDefault();
}
using(var db = new MyContext())
{
db.Projects.Add(project);
db.SaveChages();
}
Three remarks:
Proxy creation is disabled, because you can't attach a proxy to another context without explicitly detaching it first.
No, I didn't forget to include Measurement.Method. All methods are loaded by including them in the Project and now (because of tracking, and assuming that measurement will only have methods of the project they belong to), EF connects them with the Measurements by relationship fixup.
EF-core is smarter here: when adding AsNoTracking it won't track materialized entities, but still, it won't create duplicates either. It seems to have some temporary tracking during the construction of an object graph.
thanks for your answer so far. This works quite fine right now. Unfortunately I noticed that the Measurements entity has another required relationship to a table named 'MeasurementTypes':
[Required]
public virtual MeasurementType MeasurementType { get; set; }
[ForeignKey("MeasurementType")]
public long MeasurementType_Id { get; set; }
In contrast to Batches and Methods these entries must not be copied and the entries already exist in the MeasrementTypes table.
What would be a good way to put the required reference to the Measurements?
I've manay-to-many relationship between two entities: Categories <--> Items
public class CategoryMaster
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual List<SubCategoryMaster> SubCategories { get; set; }
public List<ItemMaster> Items { get; set; }
}
public class ItemMaster
{
public long Id { get; set; }
public string Name { get; set; }
public List<CategoryMaster> Categories { get; set; }
}
Whenever I try to explicit load related items to all/certain categories, it gives me
all related items
related categories to those items
related items to those categories and so on...nested/circular references
db.CategoryMaster
.Include(x=>x.Items)
.Include(x=>x.SubCategories.Select(y=>y.Items))
.ToList();
Hence results in below error while serializing it to JSON on *.cshtml with Json.Encode();
A circular reference was detected while serializing an object of type 'GoGreen.Data.Entities.SubCategoryMaster'.
Since I've disabled the lazy loading at property level, I'm not expecting it to load all nested entities(circular references) at any point of time. Is there a way to load all related level one records i.e. Categories and related items.
Related question - But Iodon't want to go with any of the two ways suggested.
NOTE : I'm more interested in knowing why EF behaves like this. It seems a bug to me.
First approach: you can add attribute above properties you don't want to exclude it from being serialized using [ScriptIgnore], you can create partial class and add your customization if your entities are auto generated
Second approach: Create a Model with only properties you need in your view and select only this model and set your properties
EFcontext.Tabel.include(x=>x...).Select(x=>new MyModel { ... });
One workaround, and please don't kill me :-) After object loading and before serializing, just set the loaded objects which are causing the circular reference to null. I tried it and worked like a charm.
use meta data redirection. figured I would help anyone who stumbled here.
[MetadataType(typeof(CategoryMasterMetadata))]
public partial class CategoryMaster
{
}
public class CategoryMasterMetadata
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public virtual List<SubCategoryMaster> SubCategories { get; set; }
public List<ItemMaster> Items { get; set; }
}
this is my simple DbContext inheriting class:
public class School : DbContext
{
public DbSet<Activity> Activity { get; set; }
public DbSet<Student> Student { get; set; }
public override int SaveChanges()
{
string s = string.Empty;
foreach (var entry in ChangeTracker.Entries<Activity>().Where(a => a.State != EntityState.Unchanged))
s = entry.State.ToString();
foreach (var entry in ChangeTracker.Entries<Student>().Where(a => a.State != EntityState.Unchanged))
s = entry.State.ToString();
return base.SaveChanges();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Configuration.ValidateOnSaveEnabled = false;
Configuration.LazyLoadingEnabled = true;
base.OnModelCreating(modelBuilder);
}
}
these are my entites:
public class Student
{
public int id { get; set; }
public string Name { get; set; }
public string Roll { get; set; }
//naviagtional property
public virtual IList<Activity> Activities { get; set; }
}
public class Activity
{
public int id { get; set; }
public double Maths { get; set; }
public double Science { get; set; }
public double History { get; set; }
//navigational property
public virtual Student Student { get; set; }
}
somewhere in my code i do this:
int studentId = Convert.ToInt32(Request.Form["Student.id"]);
Activity activity = dbContext.Activity.Where(e => e.Student.id == studentId).Single();
activity.Student.Name = Request.Form["Student.Name"];
activity.Student.Roll = Request.Form["Student.Roll"];
activity.Maths = Convert.ToDouble(Request.Form["Maths"]);
activity.Science = Convert.ToDouble(Request.Form["Science"]);
dbContext.SaveChanges();
Everything's normal and fine and works as it should. My question is, by updating activity.Student.Name, how can I detect change in Activity entity and not in Student entity? Is there any support in Entity Framework to detect changes in the parent table (and not in the slave table, where actual change goes though).??
Please help, it will save me a lot of time..
Even though this is an older question I thought I would give an answer anyway. NO you cannot.
The reasoning behind this is that you the programmer, as far as the code example goes is aware what is happening and could act on that (before doing the SaveChanges) to make sure whatever you want to happen is going to happen.
The Student you are changing might also be part of other entities, so would you also want those entities to be notified. An automatic behavior as you suggest would result in very complex notifications begin sent through the model which is (in most cases undesirable).
As #Ladislav Mrnka also indicated youy did not change the activity, but a Student involved in the activity. If the student relation is more than a simple lookup perhaps the model should be changed. Form the sample code given it is hard to see "why" you would need to detect changes made "through" other entities
As far as I did research on defining many to many relations with Code First, I guess that custom constructors in the entity classes are only needed for the purpose of being able to create a new instance of an entity plus the n:m-related entity AT ONCE.
At the moment I have my classes defined like this:
public class Person
{
public Person()
{
Events = new HashSet<Event>();
}
public int PersonId { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Event
{
public Event()
{
Persons = new HashSet<Person>();
}
public int EventId { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
However, if my application will never offer the possibility to create a new Person during creating a new Event, can I simply omit the custom constructor for Events?
public class Event
{
public int EventId { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
Will the many to many relation still work fine?
If you do that you'll get a NullReferenceException when you create a new event and try to add Persons to it.
var #event = new Event();
event.Persons.Add(new Person()); //NullReferenceException here
that is the only reason for that constructor, to initialise the collections.
you can initialise the Persons collection lazily inside the getter on first access but you need to be careful with multithreading.
I am trying to develop a catalog project in ASP.NET MVC 3 and using EF Code first with an existing Database. There is a Categories table in my database that points to itself. For that, I have written the following model class. --"Correct me if the model is wrong"--
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public int? ParentCategoryID { get; set; }
public string CategoryDesc { get; set; }
[ForeignKey("ParentCategoryID")]
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Question : I am unable to understand as to how can i work with this class. While using and passing the following code to the view
var cat = dbStore.Categories.Include("ParentCategory").ToList().
I got this error : Object reference not set to an instance of an object. This is happening because the root category has null ParentCategoryID. Please tell me how will you work with this code or any resource that can help me understand working in such scenarios. Just any sort of code will be helpful that uses the above the model, like displaying a list or a menu or anything, just anything.
Usually what you do is travel from top level categories to bottom level categories. Inorder to do that first you need to define SubCategories collection in your class
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public int? ParentCategoryID { get; set; }
public string CategoryDesc { get; set; }
[ForeignKey("ParentCategoryID")]
public virtual Category ParentCategory { get; set; }
[InverseProperty("ParentCategory")]
public virtual ICollection<Category> SubCategories{ get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Then you retrieve the top level categories
var topCategories = dbStore.Categories
.Where(category => category.ParentCategoryID == null)
.Include(category => category.SubCategories).ToList();
After that you can traverse the hierachey
foreach(var topCategory in topCategories)
{
//use top category
foreach(var subCategory in topCategory.SubCategories)
{
}
}
If you do not have very many categories you can solve this by loading the whole collection of categories. I think EF will handle the fixup for you so all relations are properly populated.
As far as I know there are no SQL'ish databases/ORM's that can handle this scenario well. An approach I often use is to load the whole collection as I said above and then manually fix the relations. But I do think EF will do that for you.
Basically you should do:
var topCategories = dbStore.Categories.ToList().Where(category => category.ParentCategoryID == null);