How to generate and auto increment Id with Entity Framework - c#

Revised entire post.
I'm trying to post the following JSON POST request via Fiddler:
{Username:"Bob", FirstName:"Foo", LastName:"Bar", Password:"123", Headline:"Tuna"}
However I'm getting this error:
Message "Cannot insert the value NULL into column 'Id', table 'xxx_f8dc97e46f8b49c2b825439607e89b59.dbo.User'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated." string
Though if I manually send a random Id along with the request then all is good. Like so:
{Id:"1", Username:"Bob", FirstName:"Foo", LastName:"Bar", Password:"123", Headline:"Tuna"}
Why does Entity Framework not generate and auto increment the Id's? My POCO class is as follows:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string Headline { get; set; }
public virtual ICollection<Connection> Connections { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Phonenumber> Phonenumbers { get; set; }
public virtual ICollection<Email> Emails { get; set; }
public virtual ICollection<Position> Positions { get; set; }
}
public class Connection
{
public string ConnectionId { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class Phonenumber
{
public string Id { get; set; }
public string Number { get; set; }
public int Cycle { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
Here is the controller method. When in debug mode and I send the request via Fiddler it breaks at db.SaveChanges(); and gives the error seen a bit above.
// POST api/xxx/create
[ActionName("create")]
public HttpResponseMessage PostUser(User user)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, user);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = user.Id }));
return response;
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
What's wrong?
Solution
Change string Id to int instead and remove Data annotations. Renamed the Id to UserId, still following convention, and made changes where necessary in other POCO's to match up with the changes.

This is a guess :)
Is it because the ID is a string? What happens if you change it to int?
I mean:
public int Id { get; set; }

You have a bad table design. You can't autoincrement a string, that doesn't make any sense. You have basically two options:
1.) change type of ID to int instead of string
2.) not recommended!!! - handle autoincrement by yourself. You first need to get the latest value from the database, parse it to the integer, increment it and attach it to the entity as a string again. VERY BAD idea
First option requires to change every table that has a reference to this table, BUT it's worth it.

Related

Cannot insert value for identity in table when IDENTITY_INSERT is set to OFF

I am getting the error above when I call SaveChanges() using an instance of my Context. Now I understand what the error is pointing to, but I simply do not understand why it occurs for this particular situation.
The error occurs because I save an instance of the TestMember into the TestMember field in the Report class (which is the model for my table). Since TestMember is the foreign key this should not be a problem should it? For my own clarity I wrote raw SQL and explicitly put a valid int into the ForeignKey field and it worked fine. INSERT INTO [TestMemberReports].[dbo].[TestReports] (TestMemberId,Date,IsSuccess) values (3, '2019-05-09', 0).
However when done in code like shown below. It throws the SqlException: Cannot insert explicit value for identity column in table 'TestMembers' when IDENTITY_INSERT is set to OFF. error. For the purpose of the question please assume:
CreateReports() is called from main()
Code
public class TestReportService
{
private TestMemberReportContext Context { get; }
public void SaveChanges() => Context.SaveChanges();
public TestReportService(TestMemberReportContext context)
{
Context = context;
}
public void CreateReports()
{
var reports = AddReport();
Context.TestReports.Add(reports);
SaveChanges();
}
private TestReport AddReport()
{
return new TestReport { IsSuccess = 0, TestMember = GetTestMember("Member 1"), Date = DateTime.Now() });
}
public TestMember GetTestMember(string name)
{
return Context.TestMembers.Single(c => c.Name == name);
}
}
Models
public class TestReport
{
public int Id { get; set; }
public TestMember TestMember { get; set; }
public DateTime Date { get; set; }
public bool IsSuccess{ get; set; }
}
public class TestMember
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
public ICollection<TestRecipient> Recipients { get; set; }
}
For anyone who encounters this error in the future the fix is using a different kind of pattern to store foreign keys. Using SQL profiler I determined that when I stored a member object to be used as an FK in the Reports object, EF Core was infact running an INSERT query to my database (Hence the error message pointing to a table which is never called in the code).
Old Report Model
public class TestReport
{
public int Id { get; set; }
public TestMember TestMember { get; set; }
public DateTime Date { get; set; }
public bool IsSuccess{ get; set; }
}
New Report Model
public class TestReport
{
public int Id { get; set; }
public int? TestMemberId { get; set; }
[ForeignKey(nameof(TestMemberId))]
public virtual TestMember TestMember { get; set; }
public DateTime Date { get; set; }
public bool IsSuccess { get; set; }
}
This way whenever you want to store a TestMember object in a TestReport object you simply store the Id in the TestMemberId field.
And whenever you want to obtain a TestMember object from a TestReport object you simply use the Id as a predicate and use .Include(x => x.TestMember) in your LINQ (if you're using LINQ obviously)
I hope this helps you!

MVC 5 Create Validation error but valid ModelState

I am trying to create within MVC 5 and am getting a validation error even though the ModelState is coming back valid.
Error message
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
and when I look at the message, it shows....
The name 'e' does not exist in the current context
When I look at the POST data, the model that was created has all required fields filled in. I did notice that the model ID was assigned 0. I'm not sure if that is the error or if it is supposed to pass a zero for the ID.
What might the problem be?
WosController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "id,woNumber,woDescription,dueDate,qty,item_id,releaseDate,parentWO_id,wip_id")] Wo wo)
{
if (ModelState.IsValid)
{
db.Wos.Add(wo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(wo);
}
Wo.cs
public partial class Wo
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Wo()
{
this.WoParts = new HashSet<WoPart>();
this.WoStatuses = new HashSet<WoStatus>();
}
public int id { get; set; }
public string woNumber { get; set; }
public string woDescription { get; set; }
public Nullable<System.DateTime> dueDate { get; set; }
public string qty { get; set; }
public Nullable<int> item_id { get; set; }
public Nullable<System.DateTime> releaseDate { get; set; }
public string status { get; set; }
public Nullable<int> parentWO_id { get; set; }
public int wip_id { get; set; }
public Nullable<int> part_id { get; set; }
public virtual Item Item { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoPart> WoParts { get; set; }
public virtual Wo woParentWO { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoStatus> WoStatuses { get; set; }
public virtual Part Part { get; set; }
public virtual Wip Wip { get; set; }
}
Wrap your call to SaveChangesAsync in a try...catch like so:
try
{
await db.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
var errorMessages = e.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(e.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, e.EntityValidationErrors);
}
That will show you the actual properties causing the validation issues. Then, update your question with the results, if you still need assistance.
Likely, your database is out of sync with your entities. The status property is not required on your entity, and by default properties of type string are nullable. That would explain why you're passing validation on post, but failing on actually saving the entity.
Generally, it's best not to rely on the database setting a default value in the first place. Instead, have the property itself have a default value, and then it will always be fine, regardless of what's going on at the database level:
private string _status;
public string status
{
get { return _status ?? "Default Value"; }
set { _status = value;
}
Short of that, if status is truly not required, then you should ensure that the status column on your table is nullable.

ASP.Net MVC forms/model binding and delete records?

I have a model that I'm loading into a table within a form. The records are retrieved from an Oracle DB using EF6 and loaded into the model.
I also want the user to be able to select records to delete from the database via a checkbox in each row in the form.
The function to retrieve the Attendees:
public List<WebinarAttendeesList> getAttendees(string webinarKey)
{
string connectionString = "Password=password;User Id=user;Data Source=Oracle";
List<WebinarAttendeesList> r = null;
using (webinarAttendeesListDbContext context = new webinarAttendeesListDbContext(connectionString))
{
var result = from w in context.WebinarAttendeesList
where w.webinarKey == webinarKey
orderby w.FirstPollCount, w.SecondPollCount
select w;
r = result.ToList();
}
return r;
}
Here is the model:
[Table("WEBINARATTENDEESLIST")]
public class WebinarAttendeesList {
[Key, Column("WAL_ID")]
public int wa_id { get; set; }
[Column("WAL_CLI_RID")]
public int ParticipantID { get; set; }
[Column("WAL_FULLNAME")]
public string FullName { get; set; }
[Column("WAL_EMAIL")]
public string Email { get; set; }
[Column("WAL_JOINTIME")]
public string JoinTime { get; set; }
[Column("WAL_TIMEINSESSION")]
public string TimeInSession { get; set; }
[Column("WAL_LEAVETIME")]
public string LeaveTime { get; set; }
[Column("WAL_FIRSTPOLLCOUNT")]
public int FirstPollCount { get; set; }
[Column("WAL_SECONDPOLLCOUNT")]
public int SecondPollCount { get; set; }
[Column("WAL_ATTENDEDWEBINAR")]
public int AttendedWebinar { get; set; }
[Column("WAL_MAKEUP")]
public int Makeup { get; set; }
[Column("WAL_COMMENTS")]
public string Comments { get; set; }
[Column("WAL_REGISTRANTKEY")]
public string RegistrantKey { get; set; }
[Column("WAL_WEBINARKEY")]
public string webinarKey { get; set; }
}
When the form is submitted, I am passing the model to a function to store the records in EF6.
public ActionResult PostAttendees(ICollection<WebinarAttendeesList> attendees)
{
foreach (WebinarAttendeesList attendee in attendees)
{
UpdateAttendee(attendee);
}
}
How would I edit the model to allow this delete the records that are selected and update the ones that don't have the checkbox selected?
If I put an int delete property on the model that has no Column attribute I get this exception:
ORA-00904: "Extent1"."delete": invalid identifier
I found this tutorial but I'm NOT using any helpers in the creation of the form and do not have any ViewModels and it also doesn't explain how to handle doing different things to the different records based on the checkbox: http://johnatten.com/2014/01/05/asp-net-mvc-display-an-html-table-with-checkboxes-to-select-row-items/
Is there a better way to do this?
Yes. All models properties in EF are suppose to be column. You should use NotMapped attribute if you don't want property to be treated as a 'column' in database.

Under posted model state always valid

I have an interesting issue. I have following model with required data annotation for some of the properties.
public class Consumer : MongoEntity
{
public Consumer()
{
Products = new List<Product>();
}
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
[Required]
public Location CurrentLocation { get; set; }
public List<Product> Products { get; set; }
}
Now in my web api controller I am accepting this as a parameter as show below:
[Route("")]
[ValidateModel]
public IHttpActionResult Post([FromBody]Consumer consumer)
{
try
{
if (ModelState.IsValid)
{
_consumerService.Create(consumer);
return Ok("User created sucessfully.");
}
return BadRequest("User object is not complete. It's missing mandatory fields");
}
catch
{
return InternalServerError(new Exception("Something went wrong while saving the data back to database."));
}
}
I am under the impression that if any of the required field of consumer model is null model state should be false, but it always return true. Web api only sets model state null when i send an empty body to the controller. Is there any logical explanation to this? Why web api don't take account of required properties of complex argument type?

Creating new open property

I am trying to do a blog project and I am using ado.net and I have 3-tier architecture.
In one classlibrary I have classes such as User and Comments:
public class User
{
public int userID{ get; set; }
public string userName{ get; set; }
public string userPassword { get; set; }
public string userMail{ get; set; }
}
public class Comments
{
public int ID { get; set; }
public int userID{ get; set; }
public string commentHeader{ get; set; }
public string commentContent{ get; set; }
}
I want to have a userName property in the Comments class. And I decided to create an open property in the Comments class.
Because I will show in these in a UI and I want to see the UserName along with UserID; for a better understanding about whom send this comment.
How I can create the following?
public string userName
{
get
{
return //(what I have to write here)
}
}
Multiple ways to do that.
Assuming you have list of Users in your code, you can query against that list and retrieve the UserName in your property. Something like:
public string userName
{
get
{
return userList.Single(r=>r.UserID == this.UserID).UserName; // Use single
//if you are sure there's going to be a single record against a user ID
//Otherwise you may use First / FirstOrDefault
}
}
Or
you may use composition and place User object inside the Comments class.
public class Comments
{
public int ID { get; set; }
public User user { get; set; } // User object in Comments class
public string commentHeader{ get; set; }
public string commentContent{ get; set; }
}
and then in your property you can simply do:
public string userName
{
get
{
return user.UserName;
}
}
public string userName
{
get
{
return userList.FirstOrDefault(user => user.userID == userID).userName;
}
}
where userList is
List<User> userList;

Categories