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.
Related
I am using ASP.NET Core 6 to make a simple Blog website.
I have the 2 following classes:
AppUser.cs
public class AppUser : IdentityUser
{
public ICollection<Blog>? Blogs { get; set; }
public string? Name { get; set; }
}
Blog.cs
public class Blog
{
public int ID { get; set; }
public string Title { get; set; }
public string UserID { get; set; }
}
}
Below is suppose to get the current users info when creating a blog:
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
Blog.UserID = user.Id;
if (!ModelState.IsValid)
{
return Page();
}
_context.Blog.Add(Blog);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
For some reason, the ModelState is not valid when attempting to create the new Blog.
When I print to console the Blog.UserID and Blog.Title I get the correct data, but it still does not work.
Potentially unrelated, but the table Entity Framework made for the Blog is:
Which I also don't understand why there is a UserID and AppUserId column.
Any help would be greatly appreciated!
Update
I seem to have fixed it by making the UserID a nullable field.. I'm not sure if that is ideal...
First, for your ModelState problem. I recommend you to check this ModelState.IsValid.Question
And another problem is "Which I also don't understand why there is a UserID and AppUserId column."
I think you would like to use Navigation Property.
So you should change your entities like;
public class AppUser : IdentityUser
{
public string Name { get; set; }
public ICollection<Blog> Blogs { get; set; }
}
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string AppUserId { get; set; }
public AppUser AppUser {get;set;}
}
This is an example on dotnet. github.com/dotnet... I work it on version net-6.0
The result of the validation check is false because the navigational properties of the class participate in the validation.
I implemented a simple experiment on net-5.0 - navigational properties are not reflected in the result. But, maybe I'm wrong.
How to solve this problem correctly?
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
CoursesController.cs
// POST: Courses/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("CourseID,Credits,DepartmentID,Title")] Course course)
{
if (ModelState.IsValid)
{
_context.Add(course);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
Validation result
I think the problem is a new nullable feature net6. I highly recommend you to remove it or comment in project properties
<!--<Nullable>enable</Nullable>-->
It is a very stupid feature. You will have to mark all properties as nullable till the end of your life.
public ICollection<CourseAssignment>? CourseAssignments { get; set; }
and IMHO never use bind in the controller action parameters. You will always have problems with it. It is only usefull in razor pages, but in very very rare cases. And use Dto in a very rare casess. I usually use Dto only for select when I have to create the most properties from joins.
Not sure this answers your question or not. But would suggest you create a Data Transfer Object (DTO) class rather than directly use the (generated) Database object class.
The DTO class is designed based on what value (schema) API is expected to receive. And this DTO class would also be used for doing the first-level data validation such as Required, Range and etc. (without involving validation against database)
public class CreateCourseDto
{
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
}
Then only bind the value from DTO to the real DB object. Either manually bind/assign the value or apply tool/library such as AutoMapper.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("CourseID,Credits,DepartmentID,Title")] CreateCourseDto course)
{
if (ModelState.IsValid)
{
// Map received course value to DB object
Course _course = new Course
{
CourseID = course.CourseID,
Title = course.Title,
Credits = course.Credits,
DepartmentID = course.DepartmentID
};
_context.Add(_course);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
I have the Users Model follows:
Model Users.cs
[Key]
[Column("cod")]
public int Cod { get; set; }
[Column("nome")]
[StringLength(120)]
public string Nome { get; set; }
[Column("sobrenome")]
[StringLength(80)]
public string Sobrenome { get; set; }
[Column("email")]
[StringLength(60)]
public string Email { get; set; }
[Column("password")]
[StringLength(20)]
public string Password { get; set; }
And I also have the ViewModel only with the data shown in View
UsersViewModel.cs
public int Cod { get; set; }
public string Nome { get; set; }
public string Sobrenome { get; set; }
My generic repository looks like this:
public void Update(TEntity obj)
{
Db.Entry(obj).State = EntityState.Modified;
Db.SaveChanges();
}
In my controller it looks like this:
[HttpPost]
public IActionResult SalvaPerfil([FromBody] UsersViewModel usersViewModel)
{
if (ModelState.IsValid)
{
var user = Mapper.Map<UsersViewModel, Users>(usersViewModel);
_PetApp.Update(user);
return Content("fim...");
}
return Content("ERRO");
}
The problem is that the Email and Password fields are being written as NULL in the database, I do not want to change the Email and Password values as they are changed in another View.
I saw this other post, but it is not with Generic Repository, I could not implement it ..
How to update only one field using Entity Framework?
In this case do I need to create a custom method for this change? or is there any way to only change the fields that are in the ViewModel?
Thanks!
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
}
I am attempting to create a new database record in ASP.NET MVC using a code first database. I created the controller for the model with scaffolding and views and every time I attempt to do a POST on create the model is never valid, the two models that are members of the one I am attempting to create are always null.
Below is the code for the Create POST in my controller.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "DeviceNumber,ManufacturerNumber,CarrierNumber,Name")] Device device)
{
if (ModelState.IsValid)
{
db.Devices.Add(device);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CarrierNumber = new SelectList(db.Carriers, "CarrierNumber", "CarrierID", device.CarrierNumber);
ViewBag.ManufacturerNumber = new SelectList(db.Manufacturers, "ManufacturerNumber", "ManufacturerID", device.ManufacturerNumber);
return View(device);
}
The following is the model I am trying to create. The errors in the ModelState always tell me that Manufacturer and Carrier are null, but they are required.
public class Device
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DeviceNumber { get; set; }
[ForeignKey("ManufacturerNumber"), Required]
public virtual Manufacturer Manufacturer { get; set; }
public int ManufacturerNumber { get; set; }
[ForeignKey("CarrierNumber")]
[Required]
public virtual Carrier Carrier { get; set; }
public int CarrierNumber { get; set; }
[Required]
public string Name { get; set; }
}
I've tried assigning the Carrier and Manufacturer in the Create method but it still resulted in a failed validation.
You should not have the [Required] attributes on the navigation properties(Manufacturer and Carrier).
Instead keep it on the ManufacturerNumber and CarrierNumber properties because you are getting values for those columns from the UI.
public class Device
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DeviceNumber { get; set; }
[ForeignKey("ManufacturerNumber")]
public virtual Manufacturer Manufacturer { get; set; }
[Required]
public int ManufacturerNumber { get; set; }
[ForeignKey("CarrierNumber")]
public virtual Carrier Carrier { get; set; }
[Required]
public int CarrierNumber { get; set; }
[Required]
public string Name { get; set; }
}
Also there is no need to include DeviceNumber inside Bind as the value for this will be auto generated. This should be good enough.
[HttpPost]
public ActionResult Create([Bind(Include = "ManufacturerNumber,CarrierNumber,Name")]
Device device)
{
// your code
}
This should work fine assuming your view has a form which sends data for the required fields.
#model Device
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
#Html.LabelFor(f=>f.Name)
#Html.TextBoxFor(f=>f.Name)
#Html.LabelFor(f => f.CarrierNumber)
#Html.DropDownList("CarrierNumber")
#Html.LabelFor(f => f.ManufacturerNumber)
#Html.DropDownList("ManufacturerNumber")
<input type="submit"/>
}
Remember, the best way to prevent over posting is to use a view model. This also helps to keep your layers loosely coupled.