Create Postman Delete request with composite key - c#

I'm working in Postman, I'm a complete beginner and am currently learning database work.
I've created a web API with a small local database and filled it with the data I want, using C# and Entity Framework. All the Postman requests work just as designed, except for the PUT and DELETE ones, where I get error messages (405 on put and 500 on delete). I suspect these are related to the same problem, namely that I'm working with composite keys.
The 500 one on the other hand says that I'm working with a composite key and I'm only entering one value. I have chosen to have composite keys because of a many-to-many relationship between two tables, but how do I enter this into a delete request? Is there a way to format a request URL for this (since I keep getting error 405 Method Not Allowed if I put anything in the body of a delete request)?
I'm sure I'm missing something very obvious here because this doesn't feel like it should be that complicated, but I couldn't find a similar question having been asked.
Edit:
The code is extremely basic, not sure it makes any difference to the problem at all, it looks like this;
{
public int StudentId { get; set; }
public int CourseId { get; set; }
public Student Student { get; set; }
public Course Course { get; set; }
}
StudentId and CourseId make a composite primary key, and I'm trying delete an object of the class StudentCourse. The suggested/needed request url for this is: /api/StudentCourses/{id} which I don't know how to enter since it's a composite key.
The HttpDelete action looks like this, mind you this is the autogenerated one and I can totally see that it doesn't work because StudentCourses doesn't have any single id to find, as such the FindAsync would never go through regardless because it wouldn't find anything.
That being said, I don't know how to get around that because the action itself only asks for one integer, meaning that Postman recognises that I need more parts of my primary key before it even gets to here.
public async Task<IActionResult> DeleteStudentCourse(int id)
{
var studentCourse = await _context.StudentCourses.FindAsync(id);
if (studentCourse == null)
{
return NotFound();
}
_context.StudentCourses.Remove(studentCourse);
await _context.SaveChangesAsync();
return NoContent();
}
private bool StudentCourseExists(int id)
{
return _context.StudentCourses.Any(e => e.StudentId == id);
}

Assuming that Id is a student Id and you are trying to delete all StudentCourse records for this student
[Route[("~/api/StudentCourses/DeleteStudentCourse/{id}")]
public async Task<IActionResult> DeleteStudentCourse(int id)
{
var studentCourses = await _context.StudentCourses.Where(e => e.StudentId == id).ToListAsync();
if (studentCourses == null || studentCourses.Count==0)
{
return NotFound();
}
_context.StudentCourses.RemoveRange(studentCourses);
var result= await _context.SaveChangesAsync();
if (result> 0) return Ok();
return BadRequest();
}
suggested url
.../api/StudentCourses/DeleteStudentCourse/{id}

Related

Can't configure lazy loading in EF Core 2.2 to cut off unloaded parts

I'm getting the following content when I invoke my API. It kind of breaks up in the middle when the tenant entity that member is linked to, will start listing its member entities.
{
"id":"00000000-7357-000b-0001-000000000000",
"tenantId":"00000000-7357-000a-0001-000000000000",
"userName":"user1",
"tenant":{
"id":"00000000-7357-000a-0001-000000000000",
"name":"First Fake Org",
"members":[
I configured the lazy loading like this.
services.AddDbContext<Context>(config => config
.UseLazyLoadingProxies()
.UseSqlServer(Configuration.GetConnectionString("Register")));
How should I change the code so that the lazily loaded entities don't get served? I was hoping that it would simply return an empty list to the client. Should I use a DTO for that purpose and not return from the DB like this? There's talk about not using lazy loading for APIs at all here.
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
Member output;
output = Context.Members
.Include(e => e.Tenant)
.Single(e => e.UserName == userName);
return Ok(output);
}
I'm not sure what to google for and all the hits I got were pointing to the UseLazyLoadingProxies() invokation.
This will probably be somewhat long winded: But here goes.
It sounds like you have Entities which look something like:
public partial class Member
{
public virtual long Id { get; set; }
public virtual List<Tenant> Tenants { get; set; } //tables have fk relationship
}
public partial class Tenant
{
public virtual long Id { get; set; }
public virtual List<Member> Members{ get; set; } //tables have another fk relationship?
}
And then for this method:
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
Member output;
output = Context.Members
.Include(e => e.Tenant)
.Single(e => e.UserName == userName);
return Ok(output);
}
I see a few issues, but I'll try to keep it short:
I wouldn't have the controller do this directly. But it should work.
What I think you over looked is exactly what the .Include statement does. When the object is instantiated, it will get all of those related entities. Includes essentially converts your where statement to a left join, where the foreign keys match (EF calls these navigation properties).
If you don't want the Tenant property, then you can omit the .Include statement. Unless this is meant to be more generic (In which case, an even stronger reason to use a different pattern and auto mapper).
Hopefully your database doesn't truly have a FK relationship both ways, if it does, fix that ASAP.
The next issue is that you might not want a list of child properties, but it is in the model so they will be "there". Although your List Tenants might be null. And while this might be fine to you, right now. As a general rule when I see an API returning a property, I expect something to be either not there (This Member doesn't have tenants) or something is wrong, like perhaps there is a second parameter I missed. This probably isn't a problem 93.284% of the time, but it is something to be mindful of.
This starts to get into why an AutoMapper is great. Your Database Models, Business Models and views are likely different. And as much as you shouldn't return the database models directly. Taking control of how the data is represented for each part of the application is a great idea.
You could reduce the code easily, and remove the navitation properties:
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
return Ok(Context.Members
.Include(e => e.Tenant)
.Single(e => e.UserName == userName));
}
But again, a business layer would be better:
[HttpGet("test1/{username}"), AllowAnonymous]
public IActionResult GetStuff(string userName)
{
return Ok(MemberRepository.GetMember(userName));
}
The main point I'd stress though, is creating a view model.
For example, Let's say a user Detail:
public class MemberDetail
{
public string UserName {get; set;}
public long UserId { get; set; }
public string FullName { get; set; }
}
This way the view always receives exactly what you want to see, and not the extra data. Add this with the fact that you can know exactly how every use of Member to MemberDetail will map.

EF Core One to Many adding new object

I have tried many ways but I am still hitting different kinds of errors for each solution. I have tried to stop tracking, tried adding challenge itself, updating competition but all of them doesn't seem to work.
I basically have 1 competition to many challenges, and in this scenario, 1 competition and 1 challenge is already present, and I am adding another challenge which has a Foreign Key linking with the competition. I understand that I have asked a similar question before, but that was for bulk creation of 1 competition to many categories. I am thinking this is more like an update operation, which doesn't seem to be working. Appreciate your help a lot! :)
InvalidOperationException: The instance of entity type 'Challenge'
cannot be tracked because another instance with the same key value for
{'ID'} is already being tracked. When attaching existing entities,
ensure that only one entity instance with a given key value is
attached. Consider using
'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the
conflicting key values.
Competition Model Class:
public class Competition
{
[Key]
public int ID { get; set; }
public ICollection<CompetitionCategory> CompetitionCategories { get; set; }
public ICollection<Challenge> Challenges { get; set; }
}
Challenge Model Class:
public class Challenge
{
[Key]
public int ID { get; set; }
[ForeignKey("CompetitionID")]
public int CompetitionID { get; set; }
[Display(Name = "Competition Category")]
[ForeignKey("CompetitionCategoryID")]
public int CompetitionCategoryID { get; set; }
}
Controller:
public async Task<IActionResult> Create([Bind("ID,XXX,CompetitionID,CompetitionCategoryID")] Challenge challenge)
{
var competition = await _context.Competitions
.Include(c => c.CompetitionCategories)
.Include(c1 => c1.Challenges)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == challenge.CompetitionID);
if (ModelState.IsValid)
{
//_context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
competition.Challenges.Add(challenge);
_context.Update(competition);
_context.Entry(competition).State = EntityState.Detached;
_context.Entry(competition.Challenges).State = EntityState.Detached;
await _context.SaveChangesAsync();
//_context.Add(challenge);
//await _context.SaveChangesAsync();
//return RedirectToAction(nameof(Index));
return RedirectToAction("Index", "Challenges", new { id = challenge.CompetitionID });
}
return View();
}
Update: I have actually tried to just add challenge itself but it also throws up another error. Really quite at a lost of what to do.
SqlException: Cannot insert explicit value for identity column in
table 'Challenges' when IDENTITY_INSERT is set to OFF.
System.Data.SqlClient.SqlCommand+<>c.b__122_0(Task
result)
DbUpdateException: An error occurred while updating the entries. See
the inner exception for details.
Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection
connection, CancellationToken cancellationToken)
Update 2: Removing the ID from the binding works as there was some unknown ID value being passed in and being tracked. Ivan's answer on adding a new object with Foreign Key is correct.
public async Task<IActionResult> Create([Bind("XXX,CompetitionID,CompetitionCategoryID")] Challenge challenge)
{
//Codes here
_context.Add(challenge);
await _context.SaveChangesAsync();
}
Working with disconnected entities is not easy and requires different techniques depending of the presense/absense of navigation / innverse navigation and FK properties in the entity model.
Your Challenge class has explicit FK properties and no navigation properties. Adding new object like this is the simplest operation - just call DbContext.Add or DbSet.Add:
_context.Add(challenge);
await _context.SaveChangesAsync();
However, the exception you are getting makes me think that the Challenge object received by the Create method has the PK property Id populated with a value of an existing Challenge. If you really want to add new Challenge and Id is auto-generated, exclude Id from the binding or make sure it is set to 0 (zero) before calling Add.
For more info, see Disconnected Entities and related links in the EF Core documentation.

ASP.NET Multiple DTOs to one Model and validation inside DTO

In my web app I have class with many properties. User is able to modify these properties with bunch of selectboxes, each responsible for one property. Everytime when given property is changed, the change event is triggered and ajax sends the new value of this property to Update method in the Controller.
So far I have one UpdateDto which consists of nullable fields. In my Update controller I check each DTO's field if it is null or not. If it's not null it means that user want to change this property and it is updated and saved in database.
Unfortunately the Update method's code looks a little bit ugly for me. It checks each property and it's quite long. Have a look:
Update Method in the controller:
public IHttpActionResult UpdateScrumTask(int id, ScrumTaskDetailsDto scrumTaskDto)
{
var scrumTaskFromDb = _context.ScrumTasks
.SingleOrDefault(s => s.Id == id);
if (scrumTaskFromDb == null)
return NotFound();
if (!ModelState.IsValid)
return BadRequest();
if (!string.IsNullOrWhiteSpace(scrumTaskDto.UserId))
{
var user = _context.Users
.SingleOrDefault(u => u.Id.Equals(scrumTaskDto.UserId));
scrumTaskFromDb.UserId = user?.Id;
}
else if (scrumTaskDto.EstimationId != null)
{
var estimation = _context.Estimations
.SingleOrDefault(u => u.Id == scrumTaskDto.EstimationId.Value);
scrumTaskFromDb.EstimationId = estimation?.Id;
}
else if (scrumTaskDto.Priority != null)
{
if (scrumTaskDto.Priority.Value == 0)
scrumTaskDto.Priority = null;
scrumTaskFromDb.Priority = scrumTaskDto.Priority;
}
else if (scrumTaskDto.TaskType != null)
{
scrumTaskFromDb.TaskType = scrumTaskDto.TaskType.Value;
}
_context.SaveChanges();
return Ok();
}
UpdateDTO:
public class ScrumTaskDetailsDto
{
public int? EstimationId { get; set; }
[Range(0, 5)]
public byte? Priority { get; set; }
[Range(0, 2)]
public TaskType? TaskType { get; set; }
public string UserId { get; set; }
}
Note that these properties are also nullable in the database. That's why if for example UserId is not found the property in database is set to null.
I wonder how it should look like. What is better solution?
Keep one Update method and use one UpdateDto with many nullable fields OR
Devide Update method into many methods, each responsible for one property and create separate DTOs. It brings that there will be a lot of DTOs connected with one model with single property (is it good?)
Another question is:
Should I use validtion (DataAnnotation) inside DTO? If not, what is alternative solution?
I'd suggest couple of improvements:
Separate your query logic from your core business logic, using repository pattern or query and command object using a library like MediatR, also controller only should call other methods to do something for it and return a response, it shouldn't have any query or any other logic
The DTO looks ok to me, as long as it's centered around one specific task
I would definitely separate the update from controller, as for it being one method or more, it's 35... lines, which is not ideal, maybe you could separate it into two methods, one responsible for validation and one responsible for the actual update, also you can use dependency injection to decouple the update and validation method from your controller

Inserting Entities in OData with required Foreign Keys

EDIT-2: After hours of research and almost every odata related link on google turning purple, I found out that the concept of 'deep-inserts' (link) exists in the OData specification. So after all, what I'm doing should work, even without the links. Does anyone know how to enable this on the Microsoft OData client? Are there any other OData clients out there that support that concept?
EDIT: Maybe this is the wrong approach, so please tell me if I'm doing it totally wrong. Not being able to save is really blocking our progress!
I have an issue with OData v3. I have a class Associate that has a required Address. When I try to POST a new Associate, it fails due to the Address property being null (EF6 throws DbUpdateException with foreign key violation). My Associate class looks like this:
public class Associate
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(50)]
public string Role { get; set; }
public bool IsMailReceiver { get; set; }
public bool IsLegalRepresentative { get; set; }
[ForeignKey("AddressId")]
public virtual Address Address { get; set; }
public int AddressId { get; set; }
}
I use the Microsoft OData client, and try to add the associate in the following way:
var associate = new Associate { /* ... */ };
context.AddObject("Associates", associate);
context.AddObject("Addresses", associate.Address);
/* UI fills associate data */
context.SetLink(associate, "Address", associate.Address);
context.UpdateObject(associate);
context.UpdateObject(associate.Address);
/* at this point the associate has the address set! */
context.SaveChanges(); // << Exception
On the server, in the controller, the Associate arrives without the foreign key, however. When I inspect the POST request with Fiddler, I see why:
{
"odata.type" : "xxx.Data.Entities.Associate",
"AddressId" : 0,
"Id" : 0,
"IsLegalRepresentative" : false,
"IsMailReceiver" : false,
"Name" : "John Doe",
"Role" : "Father"
}
The address is not transmitted, even though the generated class on the client has an Address property.
How can i solve this problem?
I too could not find any information about this - it really feels like an issue in OData. Here is how I managed to get it to work.
Define the foreign key explicitly
class Student {
public int TeacherId { get; set; }
[Required, ForeignKey("TeacherId")]
public virtual Teacher Teacher { get; set; }
}
When performing the insert, fetch the related record and fix the model state:
public IHttpActionResult Post(Student student)
{
student.Teacher = this.db.Teacher.FirstOrDefault(i => i.TeacherId == student.TeacherId);
if (student.Teacher != null)
{
this.ModelState.Remove("student.Teacher");
}
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
}
So from then on to post a Student, you ignore the Teacher field and just post with TeacherId.
I haven't tested this with the OData client, but I can't think of why this wouldn't work. You will just have to use the Id field rather than the object.
Basically when you create the object
var associate = new Associate { /* ... */ };
It is not inserted into the database. It is created in the memory. When you call
context.SaveChanges();
It will be saved in the database. At this point database validation happens and key's are generated. Assuming your Id is unique identifier, that is generated in the datebase, note that in order for it to get updated value back from the database you need to have StoreGeneratedPattern set to Identity from Entity model view.
If this is not done your local context and database context no longer match. If you where to use that object with reference to something else it would fail.
I assume something like this would work:
Address address = new Address{ City = "Tallinn" /*etc*/};
context.SaveChanges();
//At this point Address will be in database context and has Id
associate = new Associate {
name = "Margus",
role = "Admin",
receiver = true,
representative = true,
AddressId = address.id
};
context.SaveChanges();
There is no solution to this. I will roll my own context with a notion of change sets that works with web-api. I will put it on github, when I'm done.
The only way addlink and setlink work is if the foreign key is nullable and you ahvbe to create a postput function call create link see here
I came across this and I can confirm that it is indeed a problem with the OData client (proxy), although I haven't found any references about it.
I managed to fix it using a workaround, which is not 100% perfect but works for me. Here is the code and I will explain about its shortcomings.
public static class DataServiceContextExtensions
{
public static int PostChanges(this DataServiceContext context)
{
using (var client = new WebClient())
{
client.Credentials = context.Credentials;
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var entities = context.Entities.Where(x => x.State == EntityStates.Added);
foreach (var descriptor in entities)
{
var url = $"{context.BaseUri}{descriptor.Entity.GetType().Name}";
var data = JsonSerializer.Serialize(descriptor.Entity);
var response = client.UploadString(url, data);
context.ChangeState(descriptor.Entity, EntityStates.Detached);
}
return entities.Count();
}
}
}
As you can see, I am using an extension method over DataServiceContext where I iterate through all the entities stored in the change tracker which are marked as added, and then I use a WebClient to POST a JSON serialized version of them to the OData endpoint, using any credentials that the proxy might have.
Problems:
First, it only deals with added entities, I'm OK with that, but others may need a more comprehensive solution. The only way I see is to replace WebClient with some other client that can do arbitrary HTTP verbs.
Second, it doesn't hydrate the entities with the generated primary key. Again, I don't need it in my case, but this one will be difficult to solve, as OData does not seem to return it on the result of UploadString.
Third, all URLs are always, by convention, assumed to be composed as BaseUri + entity type (e.g., https://foo.bar/MyEntity). This may or may not always be the case, not 100% sure, again, works in my case.

Updating related data using MVC 4 and Entity Framework?

So, I have a problem in save data which contains related entities, when I save it a new relation blank is created.
Exemple:
Entities:
public class Project
{
public int Id { get; set; }
public int Code{ get; set; }
public string Description{ get; set; }
public virtual Client Client { get; set; }
}
public class Client
{
public int Id { get; set; }
public int Code { get; set; }
public string Name { get; set; }
}
The Controller GET:
public ActionResult Create()
{
PopulateDropDownClienteList(String.Empty); //Returns to ViewBag to create a combobox .in view
return View();
}
The View:
#Html.DropDownListFor(m => m.Client.Id, new SelectList(ViewBag.Client_Id, "Id", "Name"), new { Name = "Client.Id" });
The Controller POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(string command, Project project)
{
try
{
if (ModelState.IsValid)
{
projectRepository = new ProjeRepository();
Project pro = projectRepository.ReturnByCode(project.Code);
if (pro == null)
projectRepository.Save(project);
else
projectRepository.Update(project);
PopulateDropDownClienteList(String.Empty);
Return View();
}
else
{
return View(project);
}
}
catch (Exception ex)
{
return View();
}
}
So when I save the data, the client is not associated with the project. just creating a new blank Client.
You Project Save code is not updating the entity, it is ADDING a new one all the time.
You should have update logic similar to following grounds -
To Add new FK Entry and associate it with parent record -
var entity = entities.Students.Where(p => p.Id == "2").First();
entity.StudentContact = new StudentContact() { Contact = "xyz", Id = "2" };
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
// other changed properties
entities.SaveChanges();
To update a FK record with new details -
var entity = entities.Students.FirstOrDefault();
entity.StudentContact.Contact = "ABC";
entities.Students.Attach(entity);
var entry = entities.Entry(entity);
entry.Property(e => e.StudentContact.Contact).IsModified = true;
// other changed properties
entities.SaveChanges();
The above code, I have a Student records which has FK relationship with StudentContacts. I updated Contact information of a student and then updated it to database using ATTACH.
You've got a number of issues here, so let me break them down.
First and foremost, do not ever catch Exception (at least without throwing it again). There's two very important things about using try...catch blocks: you should only wrap the code where you're expecting an exception (not nearly your entire method as you've done here), and you should only catch the specific exception you're expecting (not the base type Exception). When you catch Exception, any and every exception that could possibly be generated from your code will be caught, and in this case, simply discarded, which means you really will never know if this code works at all.
Second, you have a fine method that generates a dropdown list of choices, but never store the user's selection anywhere meaningful. To understand why, you need to stop and think about what's happening here. An HTML select element has a string value and a string text or label component. It does not support passing full objects back and forth. I can't see what your PopulateDropDownClienteList method does, but what it should be doing is creating an IEnumerable<SelectListItem>, where each item gets its Text property set to whatever you want displayed and its Value property to the PK of the Client. However, once you have that, you need some property on Project to post back to. Your virtual Client won't work as that needs a full Client instance, which your form will never have. So, you have two choices:
Implement a view model to feed to the view (and accept in the post). In that view model, in addition to all other editable fields, you'll include something like ClientId which will be an int type, and you'll bind this to your drop down list. Once you're in your post method, you map all the posted values to your project instance, and then use the ClientId to look up a client from the database. You then set the resulting client as the value for your Client property and save as usual.
You alter your database a bit. When you just specify a virtual, Entity Framework smartly creates a foreign key and a column to hold that relationship for you behind the scenes. That's great, but in situations like this, where you actually need to access that foreign key column, you're screwed. That way around that is to explicitly define a property to hold that relationship on your model and tell Entity Framework to use that instead of creating its own.
[ForeignKey("Client")]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
With that, you can now directly use ClientId without worrying about filling in Client. You again bind your drop down list to ClientId, but now, you do not need to look up the client explicitly from the database. Entity Framework will just save the ClientId as it should to the database, and then restore the Client based on that when you look up the project again in the future.

Categories