Web API2 PUT not updating many-to-many relationship - asp.net-web-api

I got some troubles with my web api when using the PUT method. It updates the FooBar object but it doesn't update the changes in the Bars collection. I'm mainly trying to adding and/or removing Bar objects from the collection.
These are my two classes
public class FooBar
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public virtual ICollection<FooBar> FooBars { get; set; }
}
I've got the [JsonIgnore] to avoid JSON circular references.
This is the PUTmethod:
public IHttpActionResult PutBundle(FooBar fooBar)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
foreach (Bar b in fooBar.Bars)
{
db.Entry(b).State = EntityState.Modified;
}
db.Entry(fooBar).State = EntityState.Modified;
db.SaveChanges();
}
Looking at some other questions it seems like adding EntityState.Modified to the Bar objects would solve the problem but it hasn't changed anything.

Related

Creating an object with other object properties from DbContext in MVC EF Core

I am sorry if the answer to this question is so obvious, but all (and I really mean all) of my Google search results are purple, regardless of the search phrase I try to wrap around my question.
I am trying to build an MVC .NET Core 3.0 application with Code-First.
I am able to create my models, have these setup correct (I think) in my Database (Azure SQL), and using Visual Studio's standard templates to create controllers and views. I am therefore able to create each one of my model individually.
What I am trying to do is
Create a view from where I can create a Rental-object, with link to RentalOwner and ParkingSpot.
(Create a view from where I can see all rentals created. This is not in scope for this question)
My models
public class ParkingSpot
{
public int ParkingSpotId { get; set; }
public int ParkingSpotNumber { get; set; }
}
public class RentalOwner
{
public int RentalOwnerId { get; set; }
public int TenantId { get; set; }
public Tenant Tenant { get; set; }
}
public class Tenant
{
public int TenantId { get; set; }
[Required]
[Display(Name="Navn")]
public string Name { get; set; }
[EmailAddress]
[Required]
public string Email { get; set; }
}
public class Rental
{
public int RentalId { get; set; }
public int ParkingSpotId { get; set; }
public ParkingSpot ParkingSpot { get; set; }
public int RentalOwnerId { get; set; }
public RentalOwner RentalOwner { get; set; }
}
I have tried creating a ViewModel, to use for creation of the Rental-model and the binding to other models.
public class RentalCreationView
{
public int Id { get; set; }
public Rental Rental { get; set; }
public int ParkingSpotId { get; set; }
public int TenantId { get; set; }
}
I've tried with this GET and HttpPost POST Actions in my Controller.
// GET: Rentals/Create
public async Task<IActionResult> CreateAsync()
{
var parkingSpots = await _context.ParkingSpots.ToListAsync();
ViewData["ParkingSpots"] = new SelectList(parkingSpots, "ParkingSpotId", "ParkingSpotNumber");
var tenants = await _context.Tenants.ToListAsync();
ViewData["Tenants"] = new SelectList(tenants, "TenantId", "Name");
return View();
}
public async Task<IActionResult> Create(RentalCreationView rcw)
{
if (ModelState.IsValid)
{
_context.RentalOwners.Add(rcw.Rental.RentalOwner);
_context.Rentals.Add(rcw.Rental);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(rcw);
}
I don't think my issue is with my View, and I guess I am missing something in either my models or in my controller.
Right now, when I try to create a Rental, the view is just loading for a couple of minutes after submit.
I have seen numerous of tutorials, read hundreds of questions and articles, and I know that I am missing some basic steps, but the code above is what I have right now (I have about 500 lines of commented out code that doesn't work).
I've spent 2 weeks trying to learn how to do this but I need some help from someone who knows what I'm trying to do.
Any relevant links, videos or documentation would be awesome.. I am really stuck deep.
Thank you
EDIT: I have found a solution, and to all future readers who got this page from their 1000th Google search, what I did was change a bit in the models and add this HttpPost Create in my Controller.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(RentalCreationView rcw)
{
List<ParkingSpot> parkingSpots = _context.ParkingSpots.ToList();
List<Tenant> tenants = _context.Tenants.ToList();
RentalOwner ro = new RentalOwner
{
Tenant = tenants.Find(t => t.TenantId == rcw.TenantId),
OwnerSince = rcw.StartDate
};
Rental ren = new Rental
{
StartDate = rcw.StartDate,
ParkingSpot = parkingSpots.Find(ps => ps.ParkingSpotId == rcw.ParkingSpotId),
RentalOwner = ro
};
if (ModelState.IsValid)
{
_context.RentalOwners.Add(ro);
_context.Rentals.Add(ren);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
else
{
var errors = ModelState.Select(x => x.Value.Errors)
.Where(y => y.Count > 0)
.ToList();
}
return View();
}

EF Core: loading related entities - circular dependency

I have 2 related entities in EF Core (database first design from an existing database) and having trouble loading one-many relationship - it's a webapi ASP.NET core 1.0 application
Brand Entity
[Table("tblBranding")]
public class Brand {
[Key]
[Column("brandingId")]
public int BrandId { get; set; }
[Column("BrandingActive")]
public bool Active { get; set; }
[JsonIgnore]
[Column("DeadBrand")]
public bool DeadBrand { get; set; }
[Column("BrandingSiteTitle")]
public string Name { get; set; }
//navigation properties
public virtual ICollection<Event> Events { get; set; }
}
Event entity:
[Table("tblEvents")]
public class Event
{
public int EventId { get; set; }
[Column("eventActive")]
public bool Active { get; set; }
[Column("eventName")]
public string Name { get; set; }
public DateTime EventCloseDate {get;set;}
public int PaxAllocationLimit { get; set; }
//navigation properties
[Column("BrandingId")]
public int BrandId { get; set; }
public virtual Brand Brand { get; set; }
public virtual ICollection<Session> Sessions { get; set; }
}
code from for FLUID API in OnModelCreating in DbContext:
modelBuilder.Entity<Event>()
.HasOne(e => e.Brand)
.WithMany(b => b.Events).HasForeignKey(e=>e.BrandId);
public virtual DbSet<Brand> Brands { get; set; }
public virtual DbSet<Event> Events { get; set; }
code from BrandsController:
[HttpGet]
public IActionResult Get() {
//var brands = from b in _context.Brands
// where b.Active == true
// orderby b.BrandName
// select b;
var brands = _context.Brands.Include(e => e.Events).Where(b => b.Active == true).OrderBy(b => b.Name);
return new ObjectResult(brands);
}
code from EventsController
// GET: api/values
[HttpGet("{id:int?}")]
public IActionResult Get(int? id) {
var events = from e in _context.Events
where e.Active == true
orderby e.Name
select e;
if (!events.Any()) {
return HttpNotFound();
}
if (id != null) {
events = events.Where(e => e.EventId == id).OrderBy(e => 0);
if (events.Count() == 0) { return HttpNotFound(); }
return new ObjectResult(events);
}
else {
return new ObjectResult(events);
}
}
When I try to load brands through the API, I get an exception:
Microsoft.Data.Entity.Storage.Internal.RelationalCommandBuilderFactory: Information: Executed DbCommand (80ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [t].[EventId], [t].[EventCloseDate], [t].[eventActive], [t].[BrandingId], [t].[EventId1], [t].[eventName], [t].[PaxAllocationLimit]
FROM [tblEvents] AS [t]
INNER JOIN (
SELECT DISTINCT [e].[BrandingSiteTitle], [e].[brandingId]
FROM [tblBranding] AS [e]
WHERE [e].[BrandingActive] = 1
) AS [e] ON [t].[BrandingId] = [e].[brandingId]
ORDER BY [e].[BrandingSiteTitle], [e].[brandingId]
Microsoft.Data.Entity.Query.Internal.SqlServerQueryCompilationContextFactory: Error: An exception occurred in the database while iterating the results of a query.
System.Data.SqlClient.SqlException (0x80131904): Invalid column name 'EventId1'.
Apart from this, is there a way to load related entities without using the "Includes" ? If I do not use include and use the standard LINQ query, the related entities are loaded as NULL
UPDATE
I'm now getting an invalid column error - i noticed in my previous code I hadn't used virtual on the ICollection in brand
now i can't figure out why is it generating EventId1 column in the SQL
EF 7 version is 1.0.0-rc1-final
UPDATE-2
After playing around with the code the Exception changed to circular dependency exception in code given the exact same code as above - I don't know why it was generating the invalid column name earlier (EventId1)
Answering my own question here - figured it out-
the 2 entities used here, I've used fully defined relationships in the EF 7
- however the JSON serializer doesn not like that,as this createsa circular dependancy - Brand Contains Events List, and each event also contains the parent brand property -
so the solution here was to add [JsonIgnore] attribute to relationship properties on the child
updated Events class:
[Table("tblEvents")]
public class Event
{
public int EventId { get; set; }
[Column("eventActive")]
public bool Active { get; set; }
[Column("eventName")]
public string Name { get; set; }
public DateTime EventCloseDate {get;set;}
public int PaxAllocationLimit { get; set; }
//navigation properties
[JsonIgnore]
[Column("brandingId")]
public virtual int BrandId { get; set; }
[JsonIgnore]
public virtual Brand Brand { get; set; }
//public virtual ICollection<Session> Sessions { get; set; }
}

How do I write the Linq query to get complex model items?

I'm using EF Code First. Now, I'm having a hard time figuring out how to write the LINQ to retrieve the data into my models in my Controller, to display them in a view. Basically, I am receiving a feed of HolterTest data, and I am trying to create a worklist for the people who do a bunch of specific tasks to process the HolterTest, allowing them to flag the tasks as they are completed, and provide status of where the individual tests are in the process The basic Task class is so they can add or alter steps in the process, with the displayOrder being the order in which tasks are done. A WorkTask is a specific instance of a task, allowing us to mark who completed it, and when. A WorkItem is the complex type that includes the HolterTest, the list of WorkTasks, and status information, including when the WorkTasks were all completed.
Model Classes:
public class HolterTest
{
public Int32 HolterTestID { get; set; }
public string PatientNumber { get; set; }
public string LastName { get; set; }
public DateTime RecordingStartDateTime { get; set; }
public System.Nullable<DateTime> AppointmentDateTime { get; set; }
}
public class Task
{
public Int32 TaskID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Int32 DisplayOrder { get; set; }
public bool IsActive { get; set; }
}
public class WorkTask
{
public Int32 WorkTaskID { get; set; }
public Task Task { get; set; }
public bool IsCompleted { get; set; }
public System.Nullable<DateTime> CompletedDateTime { get; set; }
public string CompletedBy { get; set; }
}
public class WorkItem
{
public Int32 WorkItemID { get; set; }
public HolterTest HolterTest { get; set; }
public string Status { get; set; }
public List<WorkTask> WorkTasks { get; set; }
public bool IsStarted { get; set; }
public bool IsCompleted { get; set; }
public System.Nullable<DateTime> CompletedDateTime { get; set; }
}
Currently I have a business logic function that takes the list of HolterTests, finds the ones that don't have a WorkItem, and creates the WorkItems, associates the HolterTests including the WorkTasks, based on the current active list of Tasks.
My problem is how to write the LINQ to get all of the WorkItems (with their child items) for my WorkItemController so I can display the work to do in a View (WorkItems where IsCompleted = false) by PatientNumber, and make it possible to update WorkTasks for a particular WorkItem.
You want to access the related using it's navigation properties. Note that in your example, you haven't setup the navigation properties to be virtual. You should should update your model like this:
public class WorkItem
{
public Int32 WorkItemID { get; set; }
public virtual HolterTest HolterTest { get; set; }
public string Status { get; set; }
public virtual List<WorkTask> WorkTasks { get; set; }
public bool IsStarted { get; set; }
public bool IsCompleted { get; set; }
public System.Nullable<DateTime> CompletedDateTime { get; set; }
}
A simple function for accessing the work items by patient number is this:
IEnumerable<WorkItem> GetWorkIncompleteWorkItemsByPatient(string patientNumber)
{
var db = new YourContext();
return db.WorkItems.Where(wi => wi.IsCompleted == false && wi.HolterTest.PatientNumber == patientNumber);
}
Then to work on the related tasks, you would access it through the task, in this example if you knew the task ID:
var workTask = YourWorkItem.WorkTasks.FirstOrDefault(wt => wt.WorkTaskID == worktaskId);
You could look through all the tasks in the work item like this:
foreach (var workTask in YourWorkItem.WorkTasks)
{
//your logic here...
}
Linq to entities explained
http://msdn.microsoft.com/en-us/library/bb399367.aspx
But starting here may suit better: The EF Main Site http://msdn.microsoft.com/en-us/data/ee712907
if you really want to dive straight intry this video and sample code. http://msdn.microsoft.com/en-us/data/jj193542
then see this http://msdn.microsoft.com/en-us/data/jj573936
Essentially based on the POCO you have you could read per POCO and get the data that way.
However EF does a lot of heavy lifting if the POCOS have navigational properties and foreign keys defined. Worth revisiting the POCO definitions and Code-First patterns.

How can I edit an entity in MVC4 with EF5 which has a unique constraint?

[HttpPost]
public ActionResult Edit(Car car)
{
if (ModelState.IsValid)
{
db.Entry(car).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(car);
}
This is a controller method scaffolded by MCV 4
My "car" entity has a unique field: LicensePlate.
I have custom validation on my Entity:
Validation:
public partial class Car
{
partial void ValidateObject(ref List<ValidationResult> validationResults)
{
using (var db = new GarageIncEntities())
{
if (db.Cars.Any(c => c.LicensePlate.Equals(this.LicensePlate)))
{
validationResults.Add(
new ValidationResult("This licenseplate already exists.", new string[]{"LicensePlate"}));
}
}
}
}
should it be usefull, my car entity:
public partial class Car:IValidatableObject
{
public int Id { get; set; }
public string Color { get; set; }
public int Weight { get; set; }
public decimal Price { get; set; }
public string LicensePlate { get; set; }
public System.DateTime DateOfSale { get; set; }
public int Type_Id { get; set; }
public int Fuel_Id { get; set; }
public virtual CarType Type { get; set; }
public virtual Fuel Fuel { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
ValidateObject(ref result);
return result;
}
partial void ValidateObject(ref List<ValidationResult> validationResults);
}
QUESTION:
Everytime I edit a car, it raises an error:
Validation failed for one or more entities. See
'EntityValidationErrors' property for more details.
The error is the one raised by my validation, saying it can't edit because there is already a car with that license plate.
If anyone could point me in the right direction to fix this, that would be great!
I searched but couldn't find anything, so even related posts are welcome!
note: this field has a unique constraint, so it is imperative that this validation is still triggered for a create-action
Allright I found a fix but I'm not sure it's the best ever.
I modified the validation so that it only triggers when the Id is non existend (so.. 0).
This way, I can make a distiction between new entities and updated ones.
if (db.Cars.Any(c => c.LicensePlate.Equals(this.LicensePlate) && c.Id != this.Id))
This does fix my problem, but somehow I think there should be a cleaner fix.

Reference built-in Account UserId Assign to Model as Foreign Key

after looking for an answer in the already existing questions, I am still a little confused as how I should proceed. I am new to the MVC 3 framework, so if I come off sounding like a dope, I appologize!!
Ok, so I created a MVC 3 internet application, created 3 new Users administrator, user1, and user2. I have created a new model, and controller for my "Posts" I am able to add, edit and delete the items. I currently have a column called UserID in my posts table. I would like this to be automagically populated with the current UsersID. I think I would define this in the controller like this:
[HttpPost]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
public int User = System.Web.Security.Membership.GetUser(id);
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
Inside the model, this is what I currently have:
public class Post
{
public int PostID { get; set; }
public int UserID { get; set; }
public string PostTitle { get; set; }
public int PostType { get; set; }
public string PostBody { get; set; }
public string PostBlogTitle { get; set; }
public string PostBlogURL { get; set; }
public string PostCategory { get; set; }
public string PostSEO { get; set; }
public int PostStatus { get; set; }
}
public class PostDBContext : DbContext
{
public DbSet<Post> Posts { get; set; }
}
I would like to replace public int UserID { get; set; } with my newly defined variable in the controller, but not sure where/how to add it.
Not sure I'm clear on what you're trying to achieve. Are you just trying to assign the current user to the model, save it and then pass it back to the view?
If so you can just do this:
[HttpPost]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
// Note: I'm assuming this actually works/returns an int
public int User = System.Web.Security.Membership.GetUser(id);
post.UserID = User;
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}

Resources