Update model in database using entity framework gives conflict with foreign key - sql-server

I know this has been asked millions of times and I've had it myself hundreds of times, but for some reason I can't fix this one.
I get the well known error:
The UPDATE statement conflicted with the FOREIGN KEY constraint ...
All my tables in my database are cascaded when an insert or delete is done.
Now on to the error:
I want to update an admins table (administrator accounts) that is linked to a cultures table (for languages).
Everything is filled in correctly. and thus we get to the following code:
[HttpPost]
public ActionResult Edit(Admins admins)
{
if (!ModelState.IsValid)
{
return View(admins);
}
admins.cultures_id = admins.Cultures.id;
_unitOfWork.AdminsRepository.Update(admins);
_unitOfWork.Save();
return RedirectToAction("Index", "Overview", new { area = "Admin" });
}
I first set the cultures id of my admin object/entity equal to that of the id in the cultures table that is linked:
admins.cultures_id = admins.Cultures.id;
I then fill update the table:
_unitOfWork.AdminsRepository.Update(admins);
The method update holds this code:
public virtual void Update(TEntity entityToUpdate)
{
DbSet.Attach(entityToUpdate);
ArtWebShopEntity.Entry(entityToUpdate).State = EntityState.Modified;
}
So far so good, but then, when I actually want to save the admin:
_unitOfWork.Save();
That save method holds this code:
public void Save() {
try
{
_artWebshopEntity.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:", validationErrors.Entry.Entity.GetType().Name, validationErrors.Entry.State);
foreach (var validationError in validationErrors.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"", validationError.PropertyName, validationError.ErrorMessage);
}
}
throw; // Will do something here later on...
}
}
And at the SaveCHanges method I get the error. I know what it means but I can't seem to fix it. I've tried all the things I know that could cause it.
Edit
I only want to update the admin values, so I don't want to update the culture values.
This is the query:
update [dbo].[Admins]
set [login] = 'Herve' /* #0 */,
[password] = null,
[salt] = null,
[email] = 'xxxxx.xxx#glevin.be' /* #1 */,
[permissions] = 'administrator' /* #2 */,
[attempts] = 4 /* #3 */,
[locked] = 0 /* #4 */,
[cultures_id] = 0 /* #5 */
where ([id] = 1 /* #6 */)
So, the cultures_id is the issue. I've now did the following:
var updateAdmin = new Admins
{
attempts = admins.attempts,
cultures_id = admins.cultures_id,
email = admins.email,
locked = admins.locked,
login = admins.login,
id = admins.id,
password = admins.password,
permissions = admins.permissions,
salt = admins.salt,
};
And that works, but the moment I add the Cultures object to the mix, it crashes and gives me the reference error. So it boils down to, how the frack do I update a table with a foreign key to another table to also needs to be updated?
Edit II
My admin and cultures entity (database first), also image of database in sql management studio:
Admin class:
public partial class Admins
{
public int id { get; set; }
public string login { get; set; }
public string password { get; set; }
public string salt { get; set; }
public string email { get; set; }
public string permissions { get; set; }
public Nullable<int> attempts { get; set; }
public Nullable<bool> locked { get; set; }
public Nullable<int> cultures_id { get; set; }
public virtual Cultures Cultures { get; set; }
}
Cultures class:
public partial class Cultures
{
public Cultures()
{
this.Categories_local = new HashSet<Categories_local>();
this.Menu_items_local = new HashSet<Menu_items_local>();
this.Products_local = new HashSet<Products_local>();
this.Subcategories_local = new HashSet<Subcategories_local>();
this.Webpages_local = new HashSet<Webpages_local>();
this.Admins = new HashSet<Admins>();
}
public int id { get; set; }
public string name { get; set; }
public string display_name { get; set; }
public virtual ICollection<Categories_local> Categories_local { get; set; }
public virtual ICollection<Menu_items_local> Menu_items_local { get; set; }
public virtual ICollection<Products_local> Products_local { get; set; }
public virtual ICollection<Subcategories_local> Subcategories_local { get; set; }
public virtual ICollection<Webpages_local> Webpages_local { get; set; }
public virtual ICollection<Admins> Admins { get; set; }
}

I've gotten it to work!
The problem was that in the edit page the final field was the field that showed the name of the culture that corresponded with the id of the admin.
In other words I did the following:
#Html.EditorFor(model => model.Cultures.name)
But this wasn't the correct way.
In order to show the name of the culture but in the code pass the culture id, I used a #Html.DropDownListFor()-element.
The problem with this however was that my original model, Admins, didn't have a IEnumerable object that I could pass to the dropdownlist element in my view. I had to create a new model which I named CreateAdminModel, The new model looks like this:
public class CreateAdminModel
{
public CreateAdminModel() { }
public CreateAdminModel(IEnumerable<SelectListItem> cultures) { Cultures = cultures; }
public CreateAdminModel(Admins admin) { Admin = admin; }
public CreateAdminModel(IEnumerable<SelectListItem> cultures, Admins admin)
{
Cultures = cultures;
Admin = admin;
}
public IEnumerable<SelectListItem> Cultures { get; set; }
public Admins Admin { get; internal set; }
}
It has an Admin object created by the entity framework (database first).
With that new model I created the following method:
private CreateAdminModel CreateAdminWithcultureDetails(Admins admin = null)
{
var cultureItems = (_unitOfWork.CulturesRepository.Get()).ToArray();
var cultureList = new List<SelectListItem>();
for (int i = 0; i < cultureItems.Count(); i++) cultureList.Add(new SelectListItem { Text = cultureItems[i].name, Value = cultureItems[i].id.ToString() });
return admin != null ? new CreateAdminModel(cultureList, admin) : new CreateAdminModel(cultureList);
}
This fills the dropdown list with the cultures and depending on whether or not an admin object was passed also adds an admin object.
Now I can use this model in the view and correctly fill both the dropdown list and the admin if necessary.
I'm going to do the same for the other things that have to use CRUD.

Related

Adding a Role to an User inserts NULL into foreign key column ApplicationUserId in table AspNetUserRoles

I have a custom derived class ApplicationUser from IdentityUser. The mapping table AspNetUserRoles consists of the following columns:
The problem is, when I add a role to an user, .AddToRoleAsync() method works fine, EF inserts a new row into the table AspNetUserRoles, UserId and RoleId get correct values, but ApplicationUserId gets NULL value.
What am I missing here?
Here is my code:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
this.Roles = new HashSet<IdentityUserRole<string>>();
this.Claims = new HashSet<IdentityUserClaim<string>>();
this.Logins = new HashSet<IdentityUserLogin<string>>();
}
public int IdUser { get; set; }
public virtual ICollection<IdentityUserRole<string>> Roles { get; set; }
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
}
public class ApplicationRole : IdentityRole, IModifiable
{
public ApplicationRole(): this(null)
{
}
public ApplicationRole(string name) : base(name)
{
this.Id = Guid.NewGuid().ToString();
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
There are a few questions already asked, but nothing seems to solve my issue.

Cannot Insert new Data in the Database in .NET Core API error Database operation

I am working on an API and when I started adding new data. I received this error. It was working when I manually add the ID every input but now I got this error and after adding some solutions from here its still not working.
Error:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
Code for insert:
public bool Insert(string UserName, SendInventoryModel sendInventoryModel)
{
using (DatabaseContext context = new DatabaseContext())
{
bool flag = false;
// Create new
InventoryEntity inventoryEntity = new InventoryEntity
{
UserName = sendInventoryModel.UserName,
Item = sendInventoryModel.Item ,
};
context.Table.Add(inventoryEntity);
context.SaveChanges();
// Check
var model = CheckUserNameID(UserName, sendInventoryModel.Item);
var data = context.Table.Find(model.Id);
if (null != data)
{
flag = true;
}
return flag;
}
}
SendInventoryModel:
public class SendSiteMailModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Item{ get; set; }
}
InventoryController:
[HttpPost("{username}")]
[Authorize]
public JObject Post([Required] string UserName, [FromBody] SendInventoryModel sendInventoryModel)
{
ResponseModel x = new ResponseModel();
try
{
InventoryRepository InventoryRepository = new InventoryRepository();
bool isSuccess = InventoryRepository.Insert(UserName, sendInventoryModel);
}
catch (Exception error)
{
// if not successful
}
return Json(x);
}
I already added [DatabaseGenerated(DatabaseGeneratedOption.Identity)] in my InventoryEntity and InventoryModel.
InventoryEntity:
[Key]
DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
InventoryModel:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
I also added the below code in my DBContext.cs:
public virtual DbSet<OtherTableEntity> Table{ get; set; }
public virtual DbSet<InventoryEntity> Table{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OtherTableEntity>();
modelBuilder.Entity<InventoryEntity>().Property(x => x.Id).ValueGeneratedOnAdd();
base.OnModelCreating(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
Add finally my table design: Inventory ID:
(Is Identity) = Yes
Identity Increment = 1
Identity Seed = 1
Note that there is no Primary Key in the Inventory table. And its an old table with existing data. The current database was migrated from membership to identity.
After all the things that I have added the context.SaveChanges(); in the insert method still does not work. Any ideas or suggestion on how to fix this problem?
Note: I've changed the table entity names and models per Asherguru suggestion since its kinda confusing and generic.
Are your TableEntity and Table in database same table names?
Why different names - TableEntity and Table?
Try to add [Table("YourTableNameInDatabase")] in TableEntity class. Then EF can find actual table in database and insert into this table.
[Table("YourTableNameInDatabase")]
public partial class TableEntity
{
public int Id { get; set; }
}
It would be less confusing if you show actual table names with some necessary screenshots.

Null data returned using a 1:1 model

I have two SQL tables, User and UserType joined with UserType as a foreign key, with their respective models in ASP. To my understanding, this should be a 1:1 relationship (correct me if I'm wrong). One unique user, set as a type of user (being admin, super admin, user etc).
When I try and retrieve a list of users, it returns a null on the property UserType.
I used Google to get this far, but I'm struggling to get this particular issue fixed.
At one point I got an error stating: "Unable to determine the principal end of an association". To get around that, I included a Required annotation (didn't work) and a ForeignKey annotation (didn't work either) in both models, both simultaneously and separately.
This is what I have so far.
[Table("Users", Schema = "dbo")]
public class Users
{
[Key, ForeignKey("UserType")]
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ContactNumber { get; set; }
public UserType UserType { get; set; }
public string IsActive { get; set; }
}
[Table("UserType", Schema = "dbo")]
public class UserType
{
[Key]
public Guid Id { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public string IsActive { get; set; }
public Users Users { get; set; }
}
I'm using the below LINQ method to retrieve the data:
public PagedTables<Users> GetAllUsers(Pagination pagination)
{
using (var db = new DbContext())
{
var user = new PagedTables<Users>()
{
Data = db.Users.OrderBy(U => U.Id).Skip(pagination.Page).Take(pagination.Limit).ToList(),
Count = db.Users.Count()
};
return user;
}
}
A break point on the users var shows that the property UserType returns null. I would expect the assigned user type to be joined onto the user.
Any help would be appreciated. Thanks
My EF background is database-first but if you are eager loading (i.e. not lazy loading) then are you missing an Include to tell LINQ to go and get the UserType? Something like;
Data = db.Users.OrderBy(U => U.Id).Skip(pagination.Page).Take(pagination.Limit).Include(U => U.UserType).ToList(),

Comparing the Values of Two Table Columns to Display a List MVC4

I have two tables within a database, one table is called Booking. This table will record data that a user can input via a form on the webstie. The other table is called Account, this table will record information regarding the login information of a user to the website.
When a user successfully logs into the website they are "Redirected" to a new View called Manage. Within the manage view I want to be able to display a table of just the information from the Booking table that relates to that particular account.
I think that in my Controller, within the Manage ActionResult I need to compare the values within the BookingEmail column of my Booking Table with the values within the AccountEmail column of my Account Table. If there is a match then a list of the information which relates to that email is shown in the manage view.
I have an idea of how to go about it but actually making the code for it too work I don't know. I am new to MVC4.
Please Help.
Booking Model:
public partial class Booking
{
public int BookingId { get; set; }
public string BookingFirstname { get; set; }
public string BookingSurname { get; set; }
public string BookingEmail { get; set; }
public string BookingMobileTel { get; set; }
public string BookingHomeTel { get; set; }
public int BookingAge { get; set; }
public int BookingPassengers { get; set; }
public int DepartureId { get; set; }
public int ArrivalId { get; set; }
}
Account Model:
public partial class Account
{
public System.Guid AccountId { get; set; }
public string AccountEmail { get; set; } // Primary Key
public string AccountPassword { get; set; }
public string AccountPasswordSalt { get; set; }
}
Removed validation for a clearer picture.
In my Account Controller, in a [HttpPost] method for LogIn:
[HttpPost]
public ActionResult LogIn(Account user)
{
if (ModelState.IsValid)
{
if (isValid(user.AccountEmail, user.AccountPassword))
{
FormsAuthentication.SetAuthCookie(user.AccountEmail, false);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Login Data does not exist, Please Register");
}
}
return View(user);
}
UPDATE:
In order to go to the Manage View, which is in HomeController I have used this:
[HttpPost]
public ActionResult LogIn(Account user)
{
if (ModelState.IsValid)
{
if (isValid(user.AccountEmail, user.AccountPassword))
{
FormsAuthentication.SetAuthCookie(user.AccountEmail, false);
bool hasBooking = db.Bookings.Where(b => b.BookingEmail == user.AccountEmail).Any();
if (hasBooking == true)
return RedirectToAction("Manage", "Home");
else
return RedirectToAction("Book", "Home");
}
else
{
ModelState.AddModelError("", "Login Data does not exist, Please Register");
}
}
return View(user);
}
UPDATE:
So I have passed the AccountEmail from the AccountController to the HomeController using TempData:
Account Controller:
TempData["user"] = user.AccountEmail;
Home Controller:
public ActionResult Manage()
{
var user = TempData["user"] as string;
IEnumerable<Booking> bookings = db.Bookings.Where(b => b.BookingEmail == user);
return View(bookings);
}

Showing specific data based on the currently logged in user

I have a question that deals with the logistics of returning rows of data in a SQL database (Entity Framework) based on the user that is logged in; I have mainly focused on desktop C# applications and while making the switch to ASP.NET MVC 4 I'm having a bit of difficulty when it comes to figuring this out (I've searched around and none of the answers seem to provide exactly what I'm looking for):
I would like to use the authorization built in to ASP.NET (MVC4), and allow users to post data about their websites (site category, url, age, etc.) with a form, and have the form store the data (using Entity Framework) to a database (called PrimaryDomainsDb) that is tied to their Id in the UserProfile table.
When the user clicks a button to show their list of domains, how can I make the application pull their list of domains (relevant rows of data) while ignoring other users rows?
Again, I'm mainly looking for the logistics and concepts (using foreign keys, for example) and psuedocode rather than actually spoonfeeding me a bunch of code.
If anyone has any best practice ideas (i.e. link the UserProfile to the PrimaryDomainDb this way, and use EF to call the rows matching their Id this way to return the rows to the View), it would be much appreciated.
Some sample code:
I currently have my PrimaryDomain code first set up like this (this doesn't have the decorators that specify min/max length, etc.):
public class PrimaryDomain
{
public virtual int Id { get; set; }
public virtual string SiteName { get; set; }
public virtual string SiteURL { get; set; }
public virtual SitePlatforms SitePlatform { get; set; }
public virtual decimal? SiteDA { get; set; }
public virtual decimal? SitePA { get; set; }
public virtual string SiteAge { get; set; }
public virtual DateTime? LastStatusUpdate { get; set; }
public virtual string SiteIP { get; set; }
}
And I have a User class that is different than the one provided by ASP.NET WebSecurity, that looks like this: (also, I know that "password" should not be in string formatting, this is just for initial set-up purposes - and password should probably be removed altogether and handled by WebSecurity, I think).
public class User
{
public virtual int Id { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual string Email { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string MozAccessID { get; set; }
public virtual string MozKey { get; set; }
public virtual decimal AccuountBalance { get; set; }
public virtual PrivateProxy PrivateProxies { get; set; }
public virtual PrimaryDomain PrimaryDomains { get; set; }
}
When pulling the data for Views I run everything through a repository using direct injection:
public interface IUserDataSource
{
IQueryable<User> Users { get; }
IQueryable<PrimaryDomain> PrimaryDomains { get; }
void Save();
}
This is my UserDb class, which is fed in whenever the code calls for an IUserDataSource (via direct injection):
public class UserDb : DbContext, IUserDataSource
{
public UserDb()
: base("DefaultConnection")
{
}
public DbSet<User> Users { get; set; }
public DbSet<PrimaryDomain> PrimaryDomains { get; set; }
IQueryable<User> IUserDataSource.Users
{
get { return Users; }
}
IQueryable<PrimaryDomain> IUserDataSource.PrimaryDomains
{
get { return PrimaryDomains; }
}
void IUserDataSource.Save()
{
SaveChanges();
}
}
And this is, for example, how I would pass the PrimaryDomains model to the View:
public class NetworkController : Controller
{
//
// GET: /Network/
private IUserDataSource _db;
public NetworkController(IUserDataSource db)
{
_db = db;
}
public ActionResult ListDomains()
{
var allDomains = _db.PrimaryDomains;
return View(allDomains);
}
}
But instead of pulling the entire PrimaryDomains list from the data source, I would like to add a way to reference the currently logged in user id to make the application only show the domains for that specific user, not all domains, and when adding a new domain via the form to reference the User Id and add it into the table as well.
My original question may have caused some confusion as to what I'm trying to achieve; It's my fault for posing the wrong way of going about what I'm trying to do. After much research and learning, I've found that exactly what I'm looking for is a multi-tenant data architecture approach.
This is probably what you are looking for. If I understood you correctly you want to use WebSecurity to login or register users but you want to use entity framework to store some user-specific data. Code below connects WebSecurity tables with your database CodeFirst created using EntityFramework.
You create class below (from tutorial).
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
try
{
if(!WebSecurity.Initialized)
WebSecurity.InitializeDatabaseConnection("ConnectionString", "DbUsers", "UserId", "Email", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
It creates necessary tables for registering and logging your users. The magic is in second, third and fourth parameter. It is respectively table, userId column and userName column from YOUR database that you can create by EntityFramework. WebSecurity uses that table along with other self-generated tables to manage your users and let them register, login and so on.
Then in your code first you simply create table
public class DbUser
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[MaxLength(40)]
public string Email { get; set; }
[MinLength(3)]
[MaxLength(30)]
[Required]
public string FirstName { get; set; }
[MinLength(3)]
[MaxLength(50)]
[Required]
public string LastName { get; set; }
}
Then you can simply query data from controller. In example below I use UserId stored by WebSecurity membership to retrieve account info from database.
public ActionResult AccountInfo()
{
if (FormsAuthentication.CookiesSupported == true && Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
var userId = WebSecurity.CurrentUserId;
var userInfo = context.Users.FirstOrDefault(x => x.UserId == userId);
userInfo.Password = "";
return View(userInfo);
}
else
{
ModelState.AddModelError("", "Wystąpił bląd autoryzacji, zaloguj się jeszcze raz.");
return RedirectToAction("Login", "Account");
}
}
EDIT:
Regarding your edited question as I understand besides the fact that you need to integrate WebSecurity with EF as above (I also forgot to mention that after creating InitializeSimpleMmebershipAttribute class as above you need to decorate your controller with that attribute) you also have problems with implementing generic repository. If that line is a problem:
var allDomains = _db.PrimaryDomains;
Then i suggest to read this article about implementing generic repository:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
If you want thing realy simple all you need is just add to your interface method
GetDomainByUserId(int userId)
and just implement that interface like that:
public class UserDb : DbContext, IUserDataSource
{
public UserDb()
: base("DefaultConnection")
{
}
public DbSet<User> Users { get; set; }
public DbSet<PrimaryDomain> PrimaryDomains { get; set; }
IQueryable<User> IUserDataSource.Users
{
get { return Users; }
}
IQueryable<PrimaryDomain> IUserDataSource.PrimaryDomains
{
get { return PrimaryDomains; }
}
IQueryable<PrimaryDomain> GetDomainByUserId(int userId)
{
return PrimaryDomains.Where(x => x.Id == userId).ToQueryable();
}
void IUserDataSource.Save()
{
SaveChanges();
}
}
But this is very bad approach and I strongly recommend reading that article.

Resources