inner exception when using UserId as FK - c#

this may be a simple problem for some of you, but I am have a difficult time trying to resolve it.
I have a Message.cs class:
public class Message : Audit
{
public int MessageId { get; set; }
public string TextBody { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
}
I am using entity framework to have secure log ins and etc.
When I post a message, I have set break points to see that the message object that I am sending does contain the UserId, but when db.SaveChanges(); is called, there is an error:
"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_dbo.Messages_dbo.AspNetUsers_ApplicationUser_Id\". The conflict occurred in database \"aspnet-ChatApp-20140708035313\", table \"dbo.AspNetUsers\", column 'Id'.\r\nThe statement has been terminated."
Also, I am using data adapters and interfaces to work with my APIs.
my MessageDataAdapter for POST looks like this: (EDITTED LOOK BELOW)
public Models.Message PostMessage(Models.Message newMessage)
{
ApplicationUser user = new ApplicationUser();
Message message = new Message();
newMessage.TextBody = message.TextBody;
newMessage.DateSent = DateTime.Now;
newMessage.Hidden = message.Hidden;
newMessage.UserId = user.Id;
db.Messages.Add(newMessage);
db.SaveChanges();
return newMessage;
}
When I set a breakpoint, newMessage contains the necessary data plus the UserId when I am logged in, for I have it authroized so only "Users" can POST to the DATABASE. However, when it reaches db.SaveChanges that error occurs.
Any ideas on how to resolve this issue?
Thank you!
EDIT
this is what I have(I guess what I really want to be able to do is to be able to POST Message containing the UserId of whoever is logged in):
public Models.Message PostMessage(Models.Message newMessage)
{
ApplicationUser user = new ApplicationUser();
Message message = new Message();
message.TextBody = newMessage.TextBody;
message.DateSent = DateTime.Now;
message.Hidden = newMessage.Hidden;
message.UserId = newMessage.UserId;
db.Messages.Add(message);
db.SaveChanges();
return message;
}
My front end HTML has a input type hidden with the value being "User.Identity.GetUserId()" which I want to be POSTed with the Message.

Why are you creating new user when posting message?
Retrive the old-one from db.AspNetUsers(or db.Users ?). Or if creating new user is really needed, then you must also add user to db.AspNetUsers repository - id will be generated only when you add user to repository. If not, then user.Id property will be default - i.e. 0 for integer type. But there is no user with id=0 in AspNetUsers table. This error says about that.

Related

SqliteException: SQLite Error 1: 'foreign key mismatch - "Rental" referencing "Movie"'

I am getting SqliteException: SQLite Error 1: 'foreign key mismatch - "Rental" referencing "Movie"'.
CREATE TABLE Movie (
title VARCHAR(30) NOT NULL,
description VARCHAR(300) NOT NULL);
CREATE TABLE Rental (
user_id TEXT ,
movie_id INT ,
FOREIGN KEY (user_id) REFERENCES AspNetUsers(Email),
FOREIGN KEY (movie_id) REFERENCES Movie(rowid));
with following code.
public async Task OnGetAsync(int movie_id, string email)
{
Rental newRental = new Rental();
newRental.movie_id=movie_id;
newRental.user_id=email;
_context.Rental.Add(newRental);
_context.SaveChanges();
}
Movie table has implicit rowid automatically added by SQLlite
What am I doing wrong?
A couple things: Movie appears to be missing a PK. When using identity (auto-generated) keys EF needs to be told about them as well. It can deduce some by naming convention, but I recommend being explicit to avoid surprises. Your entities will need to be set up for the appropriate relationships:
I.e.
public class Movie
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int movie_id { get; set; }
// ...
}
public class Rental
{
[Key, Column(Order=0)]
public int movie_id { get; set; }
[Key, Column(Order=1)]
public int user_id { get; set; }
// ...
}
Unfortunately this doesn't really tell EF that there is a relationship between Movie and Rental. (Or User and Rental) From an application point of view, without that relationship,
what guarantee is there that the Movie_id your call receives from a client exists? It's also a bit strange that this method is listed as an "OnGet" type method which implies a GET action rather than a POST or PUT action.
Typically with EF you will want to leverage navigation properties for you domain rather than just exposing FKs. Also, you are defining this as an async method without awaiting any async operations.
I would recommend avoiding composite keys unless truly necessary, so give Rental a Rental ID and just rely on many to one relationships for the Movie and User references:
public class Rental
{
[Key, DatabaseGenerate(DatabaseGeneratedOption.Identity)]
public int rental_id { get; set; }
// ....
public int movie_id { get; set; }
[ForeignKey("movie_id")]
public virtual Movie Movie { get; set; }
public int user_id { get; set; }
[ForeignKey("user_id")]
public virtual User User { get; set; }
}
public void RentMovie(int movie_id, string email)
{
var movie = _context.Movies.Single(x => x.Movie_Id == movie_id);
// Better would be to go to the Session for the current logged in user rather than trusting what is coming from the client...
var user = _context.Users.Single(x => x.Email = email);
try
{
Rental newRental = new Rental
{
Movie = movie;
User = user;
};
_context.Rental.Add(newRental);
_context.SaveChanges();
}
catch
{ // TODO: Handle what to do if a movie could not be rented.
}
}
In the above example we attempt to load the requested movie and user. If these do not exist, this would fall through to the global exception handler which should be set up to end the current login session and log the exception. (I.e. any tampering or invalid state should be captured and log the user out.) Where when attempting to record the rental, the exception handling can be set up to display a message to the user etc. rather than a hard failure.
You can go a step further and remove the FK properties from the entities and use Shadow Properties (EF Core) or .Map(x => x.MapKey()) (EF6) to set up the relationship. This avoids having two sources of truth for viewing/updating relationships between entities.
Optionally the Movie object could have an ICollection<Rental> where Rentals could have a RentedDate and ReturnedDate for instance so that movies could be inspected to see if a copy was available to rent. I.e. searching by name then determining if one or more copies is currently in-stock. A Rental record could be added to movie.Rentals rather than treating Rentals as a top-level entity.
Using navigation properties is a powerful feature of EF and can accommodate some impressive querying and data retrieval options via Linq without reading a lot of records and piecing things together client side.

Adding int value to database giving error, yet string value works

I am currently developing in an MVC project, using Entity framework, and SQL Express server.
I am trying to add a user to a Users table. The user is currently compromised of the following:
CREATE TABLE [dbo].[Users] (
[UserID] INT NOT NULL PRIMARY KEY,
[Admin] BIT NOT NULL,
);
When I go through the function below to add into the above table:
[HttpPost]
public String AddUser(List<String> values)
{
using (DataBASE db = new DataBASE())
{
Users newUser = new Users();
newUser.UserID = Int32.Parse(values[0]); //There is correct data stored here as an int value
newUser.Admin = Boolean.Parse(values[1]); //There is correct data stored here as an bool value
db.Users.Add(newUser); //The newUser object is correct
db.SaveChanges(); //The error occurs here
}
return "success";
}
EDIT, this is the Users class requested (which is changed to a string whenever stored into the database):
[Table("Users")]
public class Users
{
[Key]
public int UserID { get; set;}
public bool Admin { get; set; }
}
I receive the following error:
"Cannot insert the value NULL into column UserID, table ‘…\\DATABASE.MDF.dbo.Users';
column does not allow nulls. INSERT fails.\r\nThe statement has been terminated."}
The value is not NULL and the database is connecting correctly (as the delete function works properly and I when I break into the function the database connection is correct and the function is giving the correct data before updating by adding a new user.
When I change the database to the following:
CREATE TABLE [dbo].[Users] (
[UserID] NVARCHAR(50) NOT NULL PRIMARY KEY,
[Admin] BIT NOT NULL,
);
And I add a user using the following function (changed the function above to where it doesn't pass an int but rather keeps the string value):
[HttpPost]
public String AddUser(List<String> values)
{
using (DataBASE db = new DataBASE())
{
Users newUser = new Users();
newUser.UserID = values[0]; //There is correct data stored here as an string value
newUser.Admin = Boolean.Parse(values[1]); //There is correct data stored here as an bool value
db.Users.Add(newUser); //The newUser object is correct
db.SaveChanges(); //NO error occurs here
}
return "success";
}
This is the Users class changed to a string to work for the string solution:
[Table("Users")]
public class Users
{
[Key]
public string UserID { get; set;}
public bool Admin { get; set; }
}
Is there something against using an INT value in the database that I do not know of?
You should add attribute
[DatabaseGenerated(DatabaseGeneratedOption.None)]
to your UserId property. It says that EF should get value from property instead of defaulting on database level.
Users newUser = new Users();
newUser.UserID = Int32.Parse(values[0]); //There is correct data stored here as an int value
newUser.Admin = Boolean.Parse(values[1]); //There is correct data stored here as an bool value
db.Users.Attach(newUser);
db.Users.Add(newUser); //The newUser object is correct
db.SaveChanges();
Final try, I am not sure , Just came to mind
if same id already in database, may have trouble
To be honest this isn't much of answer. But the best thing that I was able to do to solve my issue, which is writing a raw query and executing it below works with int values in the database and throughout the application. I still do not know, and probably will never know, why the above function does not work for int values.
using (DataBASE db = new DataBASE())
{
try
{
db.Database.ExecuteSqlCommand("INSERT INTO Users VALUES({0},{1})", Int32.Parse(values[0]), Boolean.Parse(values[1]));
db.SaveChanges();
}
catch (Exception e)
{
var exception = e;
}
}
You could try rename the UserID column to Id or UsersId, removing the [Key] notation, following the EF convention, but just for test.

How do I seed IdentityUser references in an Entity Framework application?

I have an Entity Framework application using ASP.NET Identity 2.2 (i.e., my context inherits from IdentityDbContext<T> and I have a User class that inherits from IdentityUser). I am successfully seeding the AspNetUsers table using the following calls in my seed method:
var testUser = new User() {
UserName = "TestUser",
Email = "TestUser#Domain.tld"
};
manager.Create(testUser, "TestPassword");
I have extended the model to include a Post class which includes a reference to my User class:
public class Post {
public Post() {}
[Required]
public int Id { get; set; }
[Required]
public User User { get; set; }
}
And, of course, this corresponds to the following in my User class:
public class User : IdentityUser {
public User() : base() {
this.Posts = new HashSet<Post>();
}
public ICollection<Post> Posts { get; set; }
//Additional members...
}
I am then seeding the Posts collection with the following:
var testPost = new Post() { Id = 1, User = testUser };
context.Posts.AddOrUpdate(
post => post.Id,
testPost
);
Technically, this works fine; the Post instance is created, and the automatically generated User_Id field is correctly populated with the Id of the newly created User instance.
So what's the problem? Every time it runs, I get the following in my EntityValidationErrors: "The User field is required". It doesn't prevent my application from working, but it makes it difficult to detect legitimate errors.
Obviously, I could add custom code to my DbContext.SaveChanges() method in order to ignore this error, but I'd rather understand why it's happening in the first place, particularly if there's a problem with how I'm seeding my data.
It seems that when the UserManager.Create() method is called, it doesn't update the User instance with the information needed to create a reference. My assumption is that it's not populating the Id field, but I haven't confirmed.
Regardless, the solution is to reload the user after the call to UserManager.Create(). My final code looks something like:
var manager = new UserManager<User>(new UserStore<User>(context));
var testUser = new User() {
UserName = "TestUser",
Email = "TestUser#Domain.tld"
};
if (manager.Users.Count<User>() == 0) {
manager.Create(testUser, "TestPassword");
}
testUser = (User)manager
.Users
.Where<User>(u => u.UserName == "TestUser")
.FirstOrDefault<User>();
I was then able to seed the Post record the same way as before:
var testPost = new Post() { Id = 1, User = testUser };
context.Posts.AddOrUpdate(
post => post.Id,
testPost
);
Note: If using a [ForeignKey] attribute, it is apparently necessary to assign the User by Id (e.g., UserId = testUser.Id) instead of by object reference (e.g., User = testUser).
In my actual application this is all shuffled off to a CreateUser() helper method so it's easy to create multiple test users, but this covers the basics. This also addresses a flaw in my original code in that I wasn't previously checking to determine if the user had already been created or not.

Entity Framework 6 Email Checking causes 'Attaching an Entity failed' exception

I have an issue that has been plaguing me for a few hours, I've been able to narrow it down to the exact code block, but I can't seem to make any further progress, I'm still fairly new to EF6 so I might not be 100% current on the best practices.
I have a User model;
public class User
{
public Guid ID { get; set; }
[DisplayName("Display Name")]
[Required]
public string DisplayName { get; set; }
[DisplayName("Email Address")]
[DataType(DataType.EmailAddress, ErrorMessage = "Please enter a valid email address")]
[Required]
public string EmailAddress { get; set; }
public string Company { get; set; }
[DisplayName("Internal ID")]
public string InternalId { get; set; }
[DisplayName("User Status")]
public UserStatus Status { get; set; }
[DisplayName("Access Request Notifications")]
public bool SendNotifications { get; set; }
[DisplayName("Admin")]
public bool IsAdmin { get; set; }
public virtual ICollection<Transaction> Submissions { get; set; }
}
Within my Views the users within the database will be enumerated and displayed to the user, this is all working wonderfully.
On my Create action on the User controller ([HTTP Post]), I am running the check below to see if the email address that is being passed already exists within the database and if it does, it returns a message back to the user informing them and prevents the user from being created.
public ActionResult Create([Bind(Include = "DisplayName,EmailAddress,Company,InternalId,Status,SendNotifications,IsAdmin")] User user)
{
try
{
user.ID = Guid.NewGuid();
if (ModelState.IsValid)
{
var existingUser = db.Users.FirstOrDefault(x => x.EmailAddress.Equals(user.EmailAddress, StringComparison.InvariantCultureIgnoreCase));
if(existingUser == null)
{
db.Users.Add(user);
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
StaticConfig.Trace.Trace(SFTraceEvents.DbFailedAddingUser1, string.Format("User with email address '{0}' already exists in database", user.EmailAddress));
ViewData.Add("DbError", string.Format("Creation failed. User with email address '{0}' already exists", user.EmailAddress));
}
}
}
catch (Exception ex)
{
StaticConfig.Trace.Trace(SFTraceEvents.DbFailedAddingUser1, ex);
ViewData.Add("DbError", "Unable to create user, an internal error has occured. Please try again, if the problem persists, please contact your system administrator.");
}
return View(user);
}
This process works fine, I'm not using the built in 'Find()' method since this only seems to search on the Primary key on an entity and I want to find on something other than the PK.
When I try and implement the same logic on my Edit method, I'm encountering the error below.
Exception: [InvalidOperationException : System.InvalidOperationException: Attaching an entity of type Models.User failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
My Edit method code is currently the following:
public ActionResult Edit([Bind(Include = "ID,DisplayName,EmailAddress,Company,InternalId,Status,SendNotifications,IsAdmin")] User user)
{
try
{
if (ModelState.IsValid)
{
var existingUser = db.Users.FirstOrDefault(x => x.EmailAddress.Equals(user.EmailAddress, StringComparison.InvariantCultureIgnoreCase));
if(existingUser == null || existingUser.ID.Equals(user.ID))
{
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
StaticConfig.Trace.Trace(SFTraceEvents.DbFailedUpdatingUser2, user.DisplayName, string.Format("Email address '{0}' already exists in database", user.EmailAddress));
ViewData.Add("DbError", string.Format("Unable to save changes, email address '{0}' already exists", user.EmailAddress));
}
}
}
catch(Exception ex)
{
StaticConfig.Trace.Trace(SFTraceEvents.DbFailedUpdatingUser2, user.DisplayName, ex);
ViewData.Add("DbError", "Unable to save changes, an internal error has occured. Please try again, if the problem persists, please contact your system administrator.");
}
return View(user);
}
So I am checking to see if there is an existing user with the email address that has been entered on the edit page, I am then checking to see if the ID of the user being edited, matches that of the user found within the database, if so, then of course the same email address should be allowed since we are editing the user that this address belongs to. If however the IDs are different, then there is another user in the database using the email address that has been entered on the edit page.
If I remove the 'var existingUser' and the subsequent if() statement that carries out the ID checks then the edit goes through fine, but then I run the risk of having several users with the same email address on the system. When I put the check back in, then I get the exception above.
Anyone got any suggestions on what I might be doing wrong....is there a more efficient way I can check for entities that might already contain certain data?
Edit
I have found that in EF6.1, it supports an 'Index' data annotation that seems to allow a unique property to be set within it as well. I need to have a look properly, but this might offer what I'm looking for.
EF6.1 Index Attribute
I also know I could do some raw SQL queries to resolve this, but if possible, I'd like to avoid this.
If you want to make sure that there are no duplicate email addresses in the database, including preventing a user from entering a new email address that is already in use, I would solve it this way:
try
{
// Make sure that there is an existing user, since this is an edit
var existingUser = db.Users.FirstOrDefault(x => x.ID.Equals(user.Id));
if (existingUser == null)
throw new ValidationException("User not found");
// Validate that the email address is unique, but only if it has changed
if (!existingUser.EmailAddress.Equals(user.EmailAddress) &&
db.Users.Any(x => x.EmailAddress.Equals(user.EmailAddress, StringComparison.InvariantCultureIgnoreCase)))
throw new Exception(string.Format("Email address '{0}' is already in use", user.EmailAddress));
// Move data to existing entity
db.Entry(existingUser).CurrentValues.SetValues(user);
db.SaveChanges();
}
catch (Exception ex)
{
StaticConfig.Trace.Trace(SFTraceEvents.DbFailedUpdatingUser2, user.DisplayName,
string.Format(ex.Message));
ViewData.Add("DbError", ex.Message);
}
This way of pulling the existing data and mapping properties over is the best way to solve updates, in my experience. You'll be able to do extensive validation involving both the new and the old data etc.
There are several different ways to handle errors. I prefer to throw exceptions and then act on them in the catch clause, but there are other ways too, of course.
The problem you have here is the expectation you have set. The ONLY time this function would run without error is if you are changing the e-mail address. If you are editing any other field, you have a consistency issue.
When you pass the user in to this function, Entity Framework isn't currently tracking that instance. You then go and do a search in the database, and find an existingUser from the database, which Entity Framework is now tracking. You then try to "attach" the first user instance to Entity Framework (via the .State change), but Entity Framework is already tracking the original, not modified version.
What you should do instead is merge the objects if there is an existing tracked item in the database, like so:
if(existingUser == null || existingUser.ID.Equals(user.ID))
{
attachedUser = db.Entry(existingUser);
attachedUser.CurrentValues.SetValues(user);
db.SaveChanges();
return RedirectToAction("Index");
}

LINQ to SQL exception when attempting to insert a new entry in a database table

I'm trying to insert a user in my database via LINK to SQL and I'm getting an exception:
Cannot insert the value NULL into
column 'UserID', table
'mydatabase.developer.Users'; column
does not allow nulls. INSERT fails.
The statement has been terminated.
I've marked the UserID as the primary key in my Users table and I was expecting that the SQL Server will automatically generate a PK when I try to insert a new user in the table.
I pretty much copied and pasted the example from Pro ASP.NET MVC Framework Chapter 4 Section on "Setting Up LINQ to SQL." Everything is in order... my database has a Users table with a UserID (PK) column and a Name column (both non-nullable), below is the class corresponding to the database table:
public class User
{
[DisplayName("User ID")]
[Column(IsPrimaryKey=true, IsDbGenerated=true, AutoSync=AutoSync.OnInsert)]
internal int UserID { get; set; }
[DisplayName("Name")]
[Column]
public string Name{ get; set; }
}
I also have a repository class which allows me to modify the database:
public class UsersRepository : IUsersRepository
{
private DataContext database;
private Table<User> usersTable;
public UsersRepository(string connectionString)
{
database = new DataContext(connectionString);
usersTable = database.GetTable<User>();
}
public void AddUser(User user)
{
usersTable.InsertOnSubmit(user);
try
{
database.SubmitChanges();
}
catch (Exception e)
{
var msg = e.Message;
}
}
//...
}
IUsersRepository ur = new UsersRepository(connectionString);
ur.AddUser(new User{Name = "Joe"});
I've also tried setting the UserID to -1, 0 and 1, but I get the same exception!
ur.AddUser(new User{UserID = -1, Name = "Joe"});
ur.AddUser(new User{UserID = 0, Name = "Joe"});
ur.AddUser(new User{UserID = 1, Name = "Joe"});
Does anybody know what may be happening here? I'm really baffled by this: what could cause the SQL server not generating the primary key? Is there any way I can inspect the insert statement and verify what is actually being sent to the server?
Is your primary key an integer? You need to set the identity specification:
If you are using something like a uniqueidentifier, you need to generate on the client with
User user = new User();
user.Id = Guid.NewGuid();
Check the properties of the table and make sure UserId allow nulls is set to false; make sure auto-increment is set to yes

Categories