I'm building a simple app to learn Entity Framework, roughly following this and this tutorials.
I've successfully code-first created my tables, I can query them and see the expected data and the correct keys for my one-to-many relationship with my seeded data.
public class Organization
{
public Organization()
{
Members = new HashSet<Member>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Member> Members { get; set; }
}
public class Member
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public string Name { get; set; }
public string Note { get; set; }
public Guid OrganizationID { get; set; }
public virtual Organization Organization { get; set; }
}
public class OrganizationMemberContext : DbContext
{
public OrganizationMemberContext(DbContextOptions<OrganizationMemberContext> options) : base(options)
{
}
public DbSet<Organization> Organizations { get; set; }
public DbSet<Member> Members { get; set; }
}
I continued on to create a scaffolded controller for the Organization model using the OrganizationMemberContext, and that works beautifully. CRUD operations appear to be operating as expected.
I then wanted to display each organization's list of members under that organization on the index page. However... what I think should work returns no related data.
public async Task<IActionResult> Index()
{
_context.Organizations.Include(x => x.Members);
return View(await _context.Organizations.ToListAsync());
}
I get the list of Organizations, but each Organization's Members property is empty in the Model sent to the page (confirmed watching locals in VS).
Again, the seeded data is being inserted correctly: I can see in the DB where Organization.ID values precisely match Member.OrganizationID values as expected.
Original code
public async Task<IActionResult> Index()
{
_context.Organizations.Include(x => x.Members); // use include with no additional queries
return View(await _context.Organizations.ToListAsync()); // made a new query - it knows nothing about include part - Lazy loading still in effect
}
Should be:
public async Task<IActionResult> Index()
{
return View(await _context.Organizations.Include(x => x.Members)ToListAsync()); // query see include and add this information in the result
}
Related
I have read that InMemory Database with EF Core has limitations and was considering moving to sqlite for development, however, I wanted to know if this behavior is a limitation of InMemory Database or if it's me. I have tried reading the documentation but can't find material explicitly mentioning this. I have the following:
// Startup
services.AddDbContext<StudentResultsContext>(opt =>
opt.UseInMemoryDatabase("StudentResultsList"));
// context
public class StudentResultsContext : DbContext
{
public StudentResultsContext(DbContextOptions<StudentResultsContext> options)
: base(options)
{
}
public DbSet<StudentResults> StudentResultsList { get; set; }
}
// classes
public class StudentResults
{
public long ID { get; set; }
public long ParentId { get; set; }
public string Name { get; set; }
public ICollection<ExamScores> Results { get; set; }
}
public class ExamScores
{
public long ID{ get; set; }
public long StudentId { get; set; }
public Exam ExamType { get; set; }
public double Score { get; set; }
}
// controller
[HttpPost]
public async Task<ActionResult<StudentResults>> PostStudentResults(StudentResults studentResults)
{
_context.StudentResultsList.Add(studentResults);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetStudentResults), new { id = studentResults.ID }, studentResults);
}
Results is saved as null in the database up even though the return from post claims they were created, like so
The post
What comes back from get
Is this something I did or a problem with InMemory Databse?
I guess you don't do anything to load related data and thus you get null for Results.
Try to get saved entity with context.StudentResultsList.Include(s => s.Results).
Check Loading Related Data for other strategies.
I'm new to programming and development and I'm learning, and this is one of my learning projects. I've been trying to get around this in various ways, but when I try to add a new Vehicle Model to a specific Vehicle Make, the Id column doesn't automatically increment, but tries to overwrite the first Id.
I tried working around Data annotations, which I think are correct, I tried manually adding values to the database via queries, and it works perfectly. Tried deleting the db and migrations, changing the annotations again and nothing works. The only thing I can be doing wrong is the code itself, probably somewhere in the controller or service layer.
VehicleMake Class:
public class VehicleMake
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Display(Name = "Make Name")]
public string Name { get; set; }
[Required]
[Display(Name = "Abbreviation")]
public string Abrv { get; set; }
[Display(Name = "Models")]
public virtual IEnumerable<VehicleModel> VehicleModels { get; set; }
}
VehicleModel Class:
public class VehicleModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public int MakeId { get; set; }
public virtual VehicleMake Make { get; set; }
[Required]
[Display(Name = "Model Name")]
public string Name { get; set; }
[Required]
[Display(Name="Abbreviation")]
public string Abrv { get; set; }
}
Controller for Vehicle Model:
[HttpPost]
public IActionResult Create(int Id, VehicleModel newModel)
{
if (ModelState.IsValid)
{
newModel.MakeId = Id;
_model.Add(newModel);
return RedirectToAction("Index");
}
return View(newModel);
}
Service for adding new model:
public void Add(VehicleModel newModel)
{
_context.Add(newModel);
_context.SaveChanges();
}
Here is the value it is trying to add to the db and of course gives an error
https://imgur.com/pL9EruF
What am I doing wrong?
Why are you passing an id to a create action in the first place? You should just have:
[HttpPost]
public async Task<IActionResult> Create(VehicleModel newModel)
{
if (!ModelState.IsValid)
return View(newModel);
_context.Add(newModel);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Note:
I'm showing the minimal code here. If you want to factor out the call to Add and SaveChangesAsync to a service, that's fine.
Use the async methods when working with EF Core. ASP.NET Core and EF Core are both async all the way. The sync methods only exist for serving rare edge-case scenarios where you can't use async for some reason, and all they do is block on the async methods.
I'm using EF Core with ASP Core 2.0. Using latest Identity framework. I get this exception on page All.
InvalidOperationException: The property 'User' is not a navigation property of entity type 'Gallery'. The 'Include(string)' method can only be used with a '.' separated list of navigation property names.
ApplicationUser looks like:
public class ApplicationUser : IdentityUser<Guid>
{
public ICollection<Gallery> Galleries { get; set; }
}
Entity Gallery looks like:
public class Gallery
{
public int Id { get; set; }
public Guid UserId { get; set; }
public string Title { get; set; }
public int? ArticleId { get; set; }
public string Photos { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Article Article { get; set; }
public ApplicationUser User { get; set; }
[NotMapped]
public List<string> PhotosList
{
get { return Photos?.Split('|').ToList(); }
set { Photos = string.Join("|", value); }
}
}
Controller for View looks like:
public async Task<IActionResult> All()
{
var databaseContext = db.Galleries.Include(x => x.Article).Include(x => x.User);
return View(await databaseContext.ToListAsync());
}
I have no idea why it dont crash on Article..
Database is up-to-date.
add a ForeignKey attribute
using System.ComponentModel.DataAnnotations.Schema;
...
[ForeignKey("Article")]
public int? ArticleId { get; set; }
[ForeignKey("User")]
public Guid UserId { get; set; }
You can also put the attribute on the navigation property
[ForeignKey("UserId")]
public ApplicationUser User { get; set; }
Also, make sure your dbContext inherits from IdentityDbContext<ApplicationUser, ...>
You can run into this if you manually add extra properties to Models.
To troubleshoot it, run SQL Profiler and capture the RAW SQL, execute the SQL against the database and see why the query doesn't work, ie which property 'x' is not a navigation property of entity type 'y'.
Then go to the model and remove the extra property you added manually.
PS: If you don't have a SQL dB you can use another profiler. Alternatively just check the diffs in source control.
Let's say I have three entities declared:
Product
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int StoreId { get; set; }
public Store Store { get; set; }
public List<Category> Categories { get; set; }
}
Store
public class Store
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}
Category
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}
There's a one-to-many relationship between Store and Product, and a many-to-many between Product and Category.
I'm trying to create a new Product. In the controller, I declare:
public ViewResult Create()
{
ViewBag.StoreId = new SelectList(db.Stores, "Id", "Name");
return View();
}
[HttpPost]
public ViewResult Create(Product product)
{
db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
}
Then in the view, I just simply add this:
#Html.DropDownList("StoreId")
And the dropdown of all stores shows properly to the user. On the POST create, Entity Framework is smart enough to ignore Product.StoreId when saving the product and instead uses it to associate the product with an existing store in the DB. This all works really great and is almost magical in how easy it is.
My question is, is it possible to apply the same out-of-the-box behavior for many-to-many relationships as well? I tried creating a new Product.CategoryIds List, and then used the same pattern:
Controller
ViewBag.CategoryIds = new SelectList(db.Categories, "Id", "Name");
View
#Html.ListBox("CategoryIds")
This will display the categories in a multi-select listbox properly. However, when I save the product this time, the categories do not get automatically associated with the product, and the products end up not having any categories.
Does anyone know how to get this working properly?
What I'm trying to do is fairly simple. I have two classes:
public class TownRecord
{
public int Id { get; set; }
public string ShortName { get; set; }
public string FileName { get; set; }
public string tags { get; set; }
public virtual TownRecordType RecordType { get; set; }
public DateTime? DateScanned { get; set; }
public DateTime? RecordDate { get; set; }
[StringLength(4000)]
public string Comments { get; set; }
public string UploadedBy { get; set; }
}
public class TownRecordType
{
public int Id { get; set; }
public string RecordType { get; set; }
public virtual ICollection<TownRecord> TownRecords {get; set; }
}
When I want to update the RecordType property on the TownRecord class, I find that the association fails to update. No exception is thrown but the update is not performed:
[HttpPost]
public ActionResult Edit(int id, TownRecord tr, FormCollection collection)
{
TownRecordType newRecType = _ctx.TownRecordTypes.Find(Int32.Parse(collection["RecordType"]));
tr.RecordType = newRecType;
_ctx.Entry(tr).State = EntityState.Modified;
_ctx.SaveChanges();
return RedirectToAction("List");
}
NOTE: I removed my error handling for clarity...
I've seen a question similar to this here but I'm not getting it. This is probably a really foolish rookie mistake but I've StackOverflowing and Googling for several hours and getting nowhere. Any help is greatly appreciated.
This doesn't work because you are using independent association. Relation between TownRecord and TownRecordType is not part of town record's entry so changing state to modified doesn't say anything about state of relation. That is the real meaning of "independent" - it has its own entry but for unknown reason it is hard to get it in DbContext API (EF 4.1). Proposed way is using Foreign key association instead of independent association. To change your association to foreign key you must do this:
public class TownRecord
{
public int Id { get; set; }
...
[ForeignKey("RecordType")]
public int RecordTypeId { get; set; }
public virtual TownRecordType RecordType { get; set; }
...
}
You will change your code to:
[HttpPost]
public ActionResult Edit(int id, TownRecord tr, FormCollection collection)
{
tr.RecordTypeId = Int32.Parse(collection["RecordType"]);
_ctx.TownRecords.Attach(tr);
_ctx.Entry(tr).State = EntityState.Modified;
_ctx.SaveChanges();
return RedirectToAction("List");
}
Actually the question with the same problem was asked 2 hours before you asked the question. I also tried to provide solution which works with independent association but I don't like it. The problem is that for independent association you need to have attached TownRecord loaded its actual TownRecordType and replace it with new TownRecordType.