C# Trying to add items into database, getting error - c#

I am trying to add this information into my database. My SQL Server will generate the Id for each row in the table. However, for some reason my code is adding a "0" for Id and I cannot figure out why or where it is coming from and I need to remove it so that the database can just generate it.
Here is my code:
public class Contact
{
public Contact()
{
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[StringLength(50)]
public string Email { get; set; }
[Column(TypeName = "ntext")]
[Required]
public string Message { get; set; }
[Column(TypeName = "date")]
[Required]
public DateTime Date { get; set; }
[Column(TypeName = "time")]
[Required]
public TimeSpan Time { get; set; }
}
public class Context : DbContext
{
public Context()
: base("ContactConnectionString")
{
}
public DbSet<Contact> ContactForm { get; set; }
}
public class ImportToDataBase
{
public static void SubmitToDatabase(string theMessage, string fullName, string emailAddress)
{
using (var db = new Context())
{
var contact = new Contact()
{
Email = emailAddress,
Message = theMessage,
Name = fullName,
Date = DateTime.Now.Date,
Time = DateTime.Now.TimeOfDay,
// ID is not set in here
};
db.ContactForm.Add(contact);
db.SaveChanges();
}
}
}
}

Try decorating your Id property with the following:
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
The Key attribute tells Entity Framework that the Id property is the primary key and should not be included in the insert statement.

As a best practice, you need to mark your id with Key and Identity Option.
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
For fluent API you can use as:
HasKey(i => i.ID);
Property(i => i.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

Related

Cannot convert ViewModel to DataModel

I have a UserViewModel for my users. I want to use that for registration.
I do not want to use my datamodel for registration or login.
My UserViewModel is as below:
public class UserViewModel
{
public int user_id { get; set; } //Primary Key in user table
[Required]
[DisplayName("Email:")]
public string email { get; set; }
[Required]
[DisplayName("First Name:")]
public string f_name { get; set; }
[Required]
[DisplayName("Last Name:")]
public string l_name { get; set; }
[Required]
[DisplayName("Contact Number:")]
public string contact { get; set; }
[Required]
[DisplayName("Gender:")]
public string gender { get; set; }
[Required]
[DisplayName("Blood Type:")]
public string blood_type { get; set; }
[Required]
[DisplayName("Password:")]
[DataType(DataType.Password)]
public string password { get; set; }
[Required]
[DisplayName("Confirm Password:")]
[DataType(DataType.Password)]
[Compare("password")]
public string confirm_password { get; set; }
}
My Registration ActionMethod is as below:
[HttpPost]
public ActionResult Registration(UserViewModel uvmr)
{
db.users.Add(uvmr);
db.SaveChanges();
return View();
}
My dataModel for user(user.cs) is as below:
public partial class user
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public user()
{
this.appointments = new HashSet<appointment>();
}
public int user_id { get; set; }
public string f_name { get; set; }
public string l_name { get; set; }
public string email { get; set; }
public string contact { get; set; }
public string gender { get; set; }
public string blood_type { get; set; }
public string password { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<appointment> appointments { get; set; }
}
N.B: I have no Confirm Password column in my user table.
Now the error says cannot convert from das.Models.ViewModel.UserViewModel to das.Models.DataModel.user
What is work-around for this one?
You can't provide just any object instance to db.users.Add(uvmr); - the DbSet<T>.Add() method is typed to each of your data models.
Since your models are fairly similar, converting one to the other is relatively simple:
var newUser = new user
{
user_id = uvmr.user_id
f_name = uvmr.f_name
l_name = uvmr.l_name
email = uvmr.email
contact = uvmr.contact
gender = uvmr.gender
blood_type = uvmr.blood_type
password = uvmr.password
};
Then you can add the new user instance:
db.users.Add(newUser);
Assuming that you don't want to duplicate this code everytime you need to map one to the other, you can create a mapping utility - I tend to use a static class for that:
public static class Mapper
{
public static user MapUser(UserViewModel uvmr)
{
return uvmr == null ? null : new user
{
user_id = uvmr.user_id
f_name = uvmr.f_name
l_name = uvmr.l_name
email = uvmr.email
contact = uvmr.contact
gender = uvmr.gender
blood_type = uvmr.blood_type
password = uvmr.password
};
}
}
Then you could do something like:
var user = Mapper.MapUser(uvmr);
Of course, utilities like AutoMapper can do the same, and in your case might be simpler - AutoMapper, even without any setup code, will try to map properties with the same name.
As an aside, your class names and properties are currently violating Microsoft's naming conventions - I'd suggest reading the Naming Guidelines if this is code meant to shared outside of your organization.

EF core won't let me access the same property twice

I'm trying to store older versions of entities in my database. To do that I am copying the existing values before I update them. For some reason EF Core won't let me use the same batch.Values property twice.
public async Task<Batch> UpdateBatch(Batch batch, Batch updatedBatch)
{
foreach (var valueParameter in batch.Values)
{
batch.ValuesHistory.Add(new ParameterValueHistory
{
Parameter = valueParameter.Parameter,
ParameterBatchNumber = valueParameter.ParameterBatchNumber,
Value = valueParameter.Value
});
}
batch.Values = updatedBatch.Values;
batch.Version++;
await this.context.SaveChangesAsync();
return batch;
}
The foreach loop and batch.Values = updatedBatch.Values; work exactly like they should when only one of them exists. But whenever they're both active I get the following error:
The instance of entity type 'ParameterValue' 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.
These are the relevant models:
ParameterValue:
public class ParameterValue
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public virtual RecipeParameter Parameter { get; set; }
public string Value { get; set; }
public string? ParameterBatchNumber { get; set; }
}
ParameterValueHistory:
public class ParameterValueHistory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public virtual RecipeParameter Parameter { get; set; }
public string Value { get; set; }
public string? ParameterBatchNumber { get; set; }
}
RecipeParameter for context:
public class RecipeParameter
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Type { get; set; }
public string Unit { get; set; }
public string Value { get; set; }
public bool BatchRequired { get; set; }
}
Batch:
public class Batch
{
[Key]
[MaxLength(12)]
public string BatchNumber { get; set; }
public virtual List<ParameterValue> Values { get; set; }
public virtual List<ParameterValueHistory> ValuesHistory { get; set; }
public int Version { get; set; }
[Required]
public bool IsResearch { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
}
This is my DbContext class:
public class ApplicationDataContext : DbContext
{
public ApplicationDataContext(DbContextOptions<ApplicationDataContext> options)
: base(options)
{
}
public DbSet<Product> Product { get; set; }
public DbSet<Batch> Batch { get; set; }
public DbSet<ParameterValue> ParameterValue { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
base.OnConfiguring(optionsBuilder);
}
}
Why does this error keep showing up? Even when I am just accessing the propety as batch.Values more than once, it gives me this error.
UPDATE:
This is the controller method that calls the UpdateBatch method.
[HttpPut("{productId}/batches/{batchNumber}")]
public async Task<ActionResult<Batch>> PutBatch(string batchNumber, Batch updatedBatch)
{
Batch batch = await this.repository.GetBatchByBatchNumber(batchNumber);
if (batch == null)
{
return NotFound()
}
return await this.repository.UpdateBatch(batch, updatedBatch);
}
When you use batch.Values = updatedBatch.Values;, because batch.Values contains the foreign key of Batch, and if the value in updatedBatch.Values also contains the key value,if the equal operation is performed directly, due to the foreign key constraint, the foreign key cannot be modified directly, which will cause your error.
Therefore, you cannot include the key value in the Values in your updateBatch.
Regarding your question. I did a simple test. You can see the following code(updateBatch.Values have no Id).
var batch = _context.Batches.Include(c => c.Values)
.ThenInclude(c => c.Parameter)
.Include(b => b.ValuesHistory)
.ThenInclude(c => c.Parameter)
.Where(c => c.BatchNumber == "1")
.FirstOrDefault();
var updateBatch = new Batch
{
Version = 3,
CreatedOn = new DateTime(),
IsResearch = true,
Values = new List<ParameterValue>
{
new ParameterValue
{
Value = "hello",
Parameter = new RecipeParameter
{
BatchRequired = true,
Name = "h",
Type = "e",
Unit = "l",
Value = "o"
}
},
},
ValuesHistory = new List<ParameterValueHistory>()
};
foreach (var valueParameter in batch.Values)
{
batch.ValuesHistory.Add(new ParameterValueHistory
{
Parameter = valueParameter.Parameter,
ParameterBatchNumber = valueParameter.ParameterBatchNumber,
Value = valueParameter.Value
});
}
batch.Values = updateBatch.Values;
batch.Version++;
_context.SaveChanges();
Test result:
start by making these changes..
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
should not be on
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
instead model like
public class Batch
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
//you can add index on this
[MaxLength(12)]
public string BatchNumber { get; set; }
public int Version { get; set; }
[Required]
public bool IsResearch { get; set; }
[Required]
public DateTime CreatedOn { get; set; };// set this in the repo or create do another way
//you add this but don't see the linkage aka ParameterValue does not have a BatchId
public virtual List<ParameterValue> Values { get; set; }
public virtual List<ParameterValueHistory> ValuesHistory { get; set; }
}

Cannot insert explicit value for identity column in 'DentalProcedures' when IDENTITY_INSERT is set to OFF. EF code first

I will try to keep this one short. Any help is welcome and appreciated!
I have 2 classes that have a many-to-many relationship and their composite key class. When I'm creating a new "appointment" I want to pick "dental procedures" that are in the system.
All works fine until I reach the AppointmentRepository where I try to save the newly created appointment. The error is as stated.
I tried to add the
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
or
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.None)]
annotations above the DentalProcedureId property in the DentalProcedure class, but nothing works. Saved the changes and dropped tables, deleted all of the migrations etc.
DentalProcedure class:
public class DentalProcedure
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DentalProcedureId { get; set; }
[Required(ErrorMessage = "The name of the procedure must be specified")]
public string ProcedureName { get; set; }
[Required(ErrorMessage = "The price of the procedure must be specified")]
public decimal ProcedurePrice { get; set; }
public bool isEnabled { get; set; }
public List<CustomerProcedure> CustomerProcedures { get; set; }
public List<AppointmentProcedure> AppointmentProcedures { get; set; }
}
Appointment class:
public class Appointment
{
[Key]
public int AppointmentId { get; set; }
[Required]
public DateTime AppointmentStart { get; set; }
[Required]
public DateTime AppointmentEnd { get; set; }
[Required]
public string Title { get; set; }
public string ProcedureDescription { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public int WorkDaysId { get; set; }
public WorkDays WorkDays { get; set; }
public List<AppointmentProcedure> AppointmentProcedures { get; set; }
}
AppointmentProcedure class:
public class AppointmentProcedure
{
public int AppointmentId { get; set; }
public Appointment Appointment { get; set; }
public int DentalProcedureId { get; set; }
public DentalProcedure DentalProcedure { get; set; }
public bool ProcedureAppointmentCanceled { get; set; }
}
Home controller:
Appointment appointment = new Appointment
{
AppointmentStart = model.AppointmentStart,
AppointmentEnd = model.AppointmentEnd,
Title = model.Title,
ProcedureDescription = model.ProcedureDescription,
CustomerId = Id,
WorkDaysId = workkWeek.WorkDaysId,
};
foreach (var proc in model.DentalProcedures)
{
if (proc.isEnabled)
{
appointment.AppointmentProcedures = new List<AppointmentProcedure>
{
new AppointmentProcedure
{
Appointment = appointment,
DentalProcedure = proc,
ProcedureAppointmentCanceled = false
}
};
}
}
_appointment.CreateAppointment(appointment);
And the error :
Thanks once more in advance.
PS: I'm still learning so if I forgot to mention something, I apologize in advance!
Entity Framework needs to track the object in the database so you need to either attach or (what I usually prefer to do) load it from the database. Here's the changed code:
foreach (var proc in model.DentalProcedures)
{
if (proc.isEnabled)
{
//assuming the DBSet is called Procedures
var dbProc = await db.Procedures.FirstOrDefaultAsync(p => p.DentalProcedureId == id));
appointment.AppointmentProcedures = new List<AppointmentProcedure>
{
new AppointmentProcedure
{
Appointment = appointment,
DentalProcedure = dbProc, //now set the loaded entity
ProcedureAppointmentCanceled = false
}
};
}
}

Entity Framework: Help Creating Database using Code first Approach

OK 2 questions:
1. I am new to Databases and I want to learn the code first approach to creating a Database. I have used the Model-first approach a few times. Can anyone help me with coding this DB using Code-first approach (I also would like to add an Employee table if possible)?
Here is the link to the DB Diagram:
http://databaseanswers.org/data_models/customers_and_orders/images/customers_and_orders_ref_data_model.gif
Also, how would I insert into lets say the customer table / customer address address all in one go, using entity framework of course?
Thank you in advance for anyone willing to help.
You can do it like follows:
Please note the this solution has done as a console application.
Please add the following class to do this as code first:
public class Customer
{
[Key]
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string CustomerOtherDetails { get; set; }
}
public class CustomerAddress
{
[ForeignKey("Customer")]
public int CustomerId { get; set; }
[ForeignKey("AddressType")]
public int AddressTypeId { get; set; }
[ForeignKey("Address")]
public int AddressId { get; set; }
[Key]
public DateTime DateFrom { get; set; }
public DateTime DateTo { get; set; }
public virtual Customer Customer { get; set; }
public virtual AddressType AddressType { get; set; }
public virtual Address Address { get; set; }
}
public class AddressType
{
[Key]
public int AddressTypeId { get; set; }
public string AddressTypeDescriptiom { get; set; }
}
public class Address
{
[Key]
public int AddressId { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string City { get; set; }
}
When you do it Code First approach you need to create the model out of the class you created and it can be done as follows:
Context class should be like follows:
public class CustomerContext : DbContext
{
public CustomerContext()
: base("DBConnectionString")
{
//If model change, It will re-create new database.
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<CustomerContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Set primary key to Customer table
modelBuilder.Entity<Customer>().HasKey(m => m.CustomerId);
//set primary key to Address table
modelBuilder.Entity<CustomerAddress>().HasKey(m => m.DateFrom);
modelBuilder.Entity<AddressType>().HasKey(m => m.AddressTypeId);
modelBuilder.Entity<Address>().HasKey(m => m.AddressId);
//Set foreign key property
modelBuilder.Entity<CustomerAddress>().HasRequired(t => t.Customer)
.WithMany().HasForeignKey(t => t.CustomerId);
modelBuilder.Entity<CustomerAddress>().HasRequired(t => t.AddressType)
.WithMany().HasForeignKey(t => t.AddressTypeId);
modelBuilder.Entity<CustomerAddress>()
.HasRequired(t => t.Address)
.WithMany()
.HasForeignKey(t => t.AddressId);
}
Database creating and the inserting address with a customer should be like below:
static void Main(string[] args)
{
using (var ctx = new CustomerContext())
{
//ctx.Database.Create(); // This command can be used to create the database using the code first class
ctx.CustomerAddresses.Add(new CustomerAddress
{
AddressType = new AddressType
{
AddressTypeId = 1,
AddressTypeDescriptiom = "Test"
},
Customer = new Customer
{
CustomerId = 1,
FirstName = "Customer 1"
},
Address = new Address
{
Line1 = "Line 1",
City = "USA"
},
DateFrom = DateTime.Now,
DateTo = DateTime.Now
});
ctx.SaveChanges();
}
}
Connection string should like below:
<connectionStrings>
<add name="DBConnectionString"
connectionString="Data Source=(local);Initial Catalog=CustomerDB;Integrated Security=true"
providerName="System.Data.SqlClient"/>
</connectionStrings>
Please note following note the above code need following references.
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
I tried this and it works as you expected. Please let me know whether you need a sample project on this.
Hope this helps you.
EDITED:
To configure self reference in code first please do as below:
public class Product
{
[Key]
public int ProductId { get; set; }
public int? ParentProductId { get; set; }
public virtual Product ParentProduct { get; set; }
}
Add the following code lines in OnModelCreating method:
modelBuilder.Entity<Product>().HasKey(m => m.ProductId);
modelBuilder.Entity<Product>().
HasOptional(e => e.ParentProduct).
WithMany().
HasForeignKey(m => m.ParentProductId);
For the Employee Table, You Can create a class (Employee.cs):
public class Employees
{
public string FName {get;set;}
public string LName {get;set;}
public string Position {get;set;}
public string Email {get;set;}
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LName + ", " + FName;
}
}
}
For the insert, You can do :
var users = new List<User>
{
new User{FName ="Chris", LName ="Fajardo",Position=#"Dev",Email="test.test#test.ae"}
};
users.ForEach(s => context.User.Add(s));
context.SaveChanges();

Automapper Missing Map Configuration

I have an Attendee Class and An AttendeeViewModel
The datetime field on the Attendee Model gets set to the default .NET Datetime when i map it from AttendeeViewModel instead of the value that is already existing in the Attendee Model
Here's my AttendeeViewModel
public class AttendeeViewModel
{
public int Id { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
public int FEventId { get; set; }
public string DisplayName
{
get { return string.Format("{0}, {1}", FirstName, LastName); }
}
}
Here's my Base AttendeeModel
public class Attendee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public DateTime CreatedAt { get; set; }
public bool IsActive { get; set; }
public int FEventId { get; set; }
public virtual ApplicationUser CreatedBy { get; set; }
public virtual FEvent FEvent { get; set; }
public ICollection<ProjectPledge> ProjectPledges { get; set; }
}
Here's My mapping configuration
public static void Configure()
{
Mapper.CreateMap<AttendeeViewModel, Attendee>().ForMember(dest=>dest.CreatedAt , opt=>opt.Ignore());
}
And heres's the Controller Action
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult Edit(AttendeeViewModel attendee)
{
if (!_attendeeService.CanAddAttendee(attendee.Email, attendee.FilanthropyEventId))
{
AddEmailModelError();
}
if (ModelState.IsValid)
{
var mappedAttendee = _attendeeService.GetById(attendee.Id);
mappedAttendee = Mapper.Map<AttendeeViewModel, Attendee>(attendee);
_attendeeService.AddOrUpdate(mappedAttendee);
return RedirectToAction(MVC.Attendee.Index(mappedAttendee.FilanthropyEventId));
}
return View(attendee);
}
if I set the configuration to be this insetad of opt.Ignore()
Mapper.CreateMap<AttendeeViewModel, Attendee>().ForMember(dest=>dest.CreatedAt , opt=>opt.UseDestinationValue());
The Mapping fails giving this exception
Missing type map configuration or unsupported mapping.
Mapping types:
AttendeeViewModel -> DateTime
MyProject.Web.ViewModels.AttendeeViewModel -> System.DateTime
Destination path:
Attendee.CreatedAt.CreatedAt
Source value:
MyProject.Web.ViewModels.AttendeeViewModel
Any ideas on how i can resolve this?
If you want to map onto an existing object you need to use the overload that takes the existing destination:
Mapper.Map<Source, Destination>(source, destination);
that should do the trick.
Have you tried removing the ".ForMember" section and just let AutoMapper ignore it? In order to help you any more it would be helpful to see the two models for comparison.
Update: after lookind at your models I would suggest the following should solve the issue you are having...
Mapper.CreateMap <attendeeviewmodel, attendee>.ForMember (x => x.CreatedAt, opt => opt.MapFrom (src => datetime.utcnow));

Categories

Resources