EF Code First Cascade NULL - asp.net-mvc

We have ASP MVC Entity Framework 6 Code First project with 70+ tables in a MS SQL database.
For each record on each table we are storing the user that has modified it the last. The structure of almost every table is like this:
public class TableA
{
public int Id{ get; set; }
.....
public DateTime? DateModified { get; set; }
public int? UserModifiedId { get; set; }
public virtual ApplicationUser UserModified { get; set; }
}
Our problem is we are getting an error when deleting a user if the user Id is in any of the tables' property UserModifiedId.
We need EF to set to NULL the UserModifiedId of all the tables where UserModifiedId = UserId.
In the past we setup this adding to the tables ON DELETE SET NULL, but EF doesn't allow to setup the tables like that.
Any idea how can we achieve this?
UPDATED
We already know EF manages this is the children are loaded into the context, but we can't load more than 70+ tables every time we want to delete a user.

One approach could be to add a migration that modifies all your tables that should have CASCADE ON DELTE SET NULL like that way (only for your example code above):
public partial class AddOnDeleteSetNull : DbMigration
{
public override void Up()
{
Sql("ALTER TABLE dbo.TableA DROP CONSTRAINT [FK_dbo.TableA_dbo.Users_UserModifiedId]");
Sql("ALTER TABLE dbo.TableA ADD CONSTRAINT [FK_dbo.TableA_dbo.Users_UserModifiedId_SetNullOnDelete] FOREIGN KEY (UserModifiedId) REFERENCES dbo.Users(UserId) ON UPDATE NO ACTION ON DELETE SET NULL");
}
public override void Down()
{
// Here you have to undo the changes!
// ...
}
}

Related

When I register an user with CodeFirst EF6 MVC5 Asp.NET, results in duplicate tables

I have the exact problem described here:
Register custom UserProfile in ASP.NET MVC4 results in duplicate tables
But the solution doesn't work for me, because it's for MVC4 and I use the MVC5.
The problem is when I try to register a new user, duplicates almost all tables in database for a plural name, eg: Picture (duplicate to) Pictures
Edit:
I have an extended class from IdentityUser, named ApplicationUser, to put in this model additional attributes, like the id of postal code, photo and name:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public string Name { get; set; }
public string Photo { get; set; }
public int PostalCodeID { get; set; }
[ForeignKey("PostalCodeID")]
public virtual PostalCode PostalCode { get; set; }
public virtual ICollection<Auction> Auctions { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
public virtual ICollection<BlockHistory> Blocks { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {
public DbSet<PostalCode> PostalCode { get; set; }
public ApplicationDbContext()
// ConnectionString
: base("AuctionsContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
}
The PostalCodeID is a foreign key for the table/model "PostalCode". One user can have one postalcode, but one postalcode could have zero or several users.
Here is the model "PostalCode":
public class PostalCode {
public int ID { get; set; }
[RegularExpression(#"\d{4}-\d{3}", ErrorMessageResourceType=typeof(Resources), ErrorMessageResourceName="PostalCodeFormat")]
[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "Required")]
[StringLength(8, ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "Length")]
[Display(Name = "Name_PostalCode", ResourceType = typeof(Resources))]
public string ZipCode { get; set; }
public int LocalityID { get; set; }
public virtual Locality Locality { get; set; }
}
I have the migrations enabled, but not in automatic mode,I think this is happening because in the configuration.cs, i have this code
public Configuration() {
AutomaticMigrationsEnabled = false;
}
When I create the first migration, and updated the database, all tables were correctly created (with their names in singular), except the AspNetxxxx tables, which are created automatically when the first user registration occurs, and that is when most tables are duplicated, and the weblogger gives this error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.AspNetUsers_dbo.PostalCodes_PostalCodeID". The conflict occurred in database "PSIProject", table "dbo.PostalCodes", column 'ID'.
The statement has been terminated.
System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.AspNetUsers_dbo.PostalCodes_PostalCodeID". The conflict occurred in database "PSIProject", table "dbo.PostalCodes", column 'ID'.
The statement has been terminated.
Linha 164: PostalCodeID = model.PostalCodeID
Linha 165: };
Linha 166: var result = await UserManager.CreateAsync(user, model.Password);
Linha 167: if (result.Succeeded)
Linha 168: {
I understand what is happening which leads to this error, in some way, the IdentityUser can't find the PostalCode table, so, it creates the PostalCodes table,
and it's on this table where it's created the relationship between the PostalCodes and AspNetUsers (I've checked, and there is no relation between tables AspNetUsers and PostalCode)
PS:
I've tried to put [Table("PostalCode")] before public class PostalCode, but when registering a user I got an error saying dbo.AspNetUsers is an invalid object so I verified the database and noticed that it wasn't creating those automatic AspNetxxxx tables.
Your question is quite difficult to understand, but I'm going to take a wild stab in the dark. If I'm completely off-based, feel free to let me know and provide additional information.
The default Entity Framework convention is to create table names as the pluralized versions of the entities they represent, so for a class like Picture, you would get a table in your database named Pictures, with an s. If you're getting "duplicates", I can only surmised that you're using an existing database or otherwise created the schema for the database manually instead of letting Entity Framework do it. In this manually created schema, you apparently named your tables in a singular fashion. I'm also assuming that you've got automatic migrations running or else you'd just get an error that the database is out of sync instead of duplicate anything. With automatic migrations, if Entity Framework determines that the database doesn't contain the schema it needs, it will update it accordingly automatically, which in your scenario would result in "duplicate" tables named in the convention that Entity Framework looks for.
You have two choices here. The best is to simply let the conventions be the conventions. Don't create your own schema or name your tables in your schema according to Entity Framework conventions (pluralized). With EF6 you can actually alter the conventions if you really care that much.
Option two is to prefix every entity class with the Table attribute to explicitly tell Entity Framework what table name it should look for, e.g.:
[Table("Picture")]
public class Picture
{
...
}
However, that's quite cumbersome and prone to error over the long haul of your application.

Adding a view to database created by Code First

I have created my first ASP.NET MVC 4 application.
I have created my model which creates the Database (Database A). I now need to gather data from another database (Database B) which sits on the same server. I have created a view (2 columns - ID and Name called People) in Database A that shows me the data I require from Database B.
I'd like to add the view to my model and have typed the following
public class People
{
public int ID { get; set; }
public String Name { get; set; }
}
And added the following line to my dbContext
public class opsDBContext : DbContext
{
public DbSet<tbl_Operators> Operator { get; set; } // Existing
public DbSet<tbl_OpsWeekInfo> OperatorWeekInfo { get; set; } // Existing
public DbSet<tbl_OpsDayInfo> OperatorDayInfo { get; set; } // Existing
public DbSet<People> People{ get; set; } // New Line
}
But when i run project i get the following error
The model backing the 'opsDBContext' context has changed since the database was created. Consider using Code First Migrations to update the database
I think i understand why i get the message, i just want to me able to use the SQL View in my project, how can this be done?
Please let me know if you require more info
When a change occurs in classes of context or in your database, when you are using EF code first, you need to run migrations commands.
Take a look in this link :http://msdn.microsoft.com/en-us/data/jj591621.aspx

ASP.net MVC - Master Detail - foreign key is always zero

I'm new to MVC and trying to add/ edit records in master detail form. Both masterid and detailid are generated by oracle on insert of record. Thus when I try to call DBContext.SaveChanges() I get error that foreign key is violated and no primary row with id '0' can be found.
Below is the class description.
public class Master
{
public int MasterID { get; set; }
public string MasterTitle { get; set; }
public virtual IList<Detail> Details { get; set; }
}
public class Detail
{
public int DetailID { get; set; }
public int MasterID { get; set; }
public string DetailName { get; set; }
public virtual Master Master { get; set; }
}
Controller code
[HttpPost]
public ActionResult Create(MASTER masterrecord)
{
if (ModelState.IsValid)
{
db.MASTER.Add(masterrecord);
db.SaveChanges();
}
...
}
The primary key (masterid) will get meaningful values only after record is inserted to database. context.SaveChanges() at this point tries to save Client records too with '0' masterid. I searched every where couldn't find anything which could be of useful.
Though of saving only Master table first so that I can retrieve the masterid and us it with DETAIL model. however couldnt find anywhere how to do it using EF5 MVC ASP.NET
Can any one point me to right direction of provide with some working sample?
thanks
Siddhant
You might want to consider using GUIDs instead of ints for your PK. Then in your constructor for Master you can say MasterID = Guid.NewGuid();. This way you don't have to hit the database to find out what the next ID will be.
There is a pro and con list here http://www.codinghorror.com/blog/2007/03/primary-keys-ids-versus-guids.html
If you have set a breakpoint on the Create method and are getting a proper list in your Master object, it may be an issue with the Oracle provider.
As a workaround, you could try to change the method signature to accept your data like the following:
public ActionResult Create(MASTER masterrecord, List<Detail> details)
Then you could first save the masterrecord and subsequently add your details and save again. It's not optimal, but it may work.
Side note: change your IList to an ICollection.

willcascadeondelete in .netMVC4 is not working

I am creating .net MVC4 application. i have two entitites named group and groupmembers. group has a one to many relations with groupmembers.
Here are my classes
public class Group
{
public int GroupId{get;set;}
public virtual ICollection<GroupMember> Members { get; set; }
}
public class GroupMember
{
public int GroupMemberId { get; set; }
public int GroupId { get; set; }
public virtual Group Group { get; set; }
}
on the model side i am using the code
modelBuilder.Entity<GroupMember>()
.HasRequired(e => e.Group)
.WithMany()
.HasForeignKey(e => e.GroupId)
.WillCascadeOnDelete();
My problem is while deleting the group the members of the group is not getting deleted.Can any one please give me a solution
The WillCascadeOnDelete() tells EF that the database will perform the delete. EF does not perform the cascade, so you need to change your tables to add the delete.
You can do this in SSMS designer by opening the relationship selecting Delete for Cascade Action, or you can do it in T-SQL:
ALTER TABLE dbo.GroupMember DROP CONSTRAINT fkName
GO
ALTER TABLE dbo.GroupMember ADD CONSTRAINT
fkName FOREIGN KEY (GroupId)
REFERENCES dbo.Group (Id) ON DELETE CASCADE

Establish Foreign Key Connection Using Entity Framework With SQL Queries

I have a couple of classes (for this example anyway) that use code first with the entity framework to connect to the database.
public class Customer
{
[Key]
public long CustomerId { get; set; }
public string CompanyName { get; set; }
...
public virtual List<Contact> Contacts { get; set; }
}
public class Contact
{
[Key]
public long ContactId { get; set; }
public string Forename { get; set; }
...
public long CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
When I hook these up in my context class directly to the db the foreign key relationships hook up fine and I can access the collection of contacts from within the customer class.
class RemoteServerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
...
}
My problem is that these database tables are used by various different systems and are massive. In order to increase efficiency I have overridden the default behaviour to point at a view (and also a stored proc elsewhere) rather than directly at the table.
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
public IEnumerable<Contact> Contacts()
{
return Database.SqlQuery<Contact>("SELECT * FROM vw_ContactsList");
}
I have made sure that in each of the views I have included the foreign key fields: CustomerId and ContactId.
When I do this however the class joins appear to be lost - there's always a null when I drill into either of the objects where it should be pointing to the other one. I have tried to set up what the foreign key field should point to but this doesn't seem to help either.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(p => p.Customer)
.WithMany()
.HasForeignKey(k => k.CustomerId);
}
Is there a way to establish the connection when overriding the default behaviour?
There is no overriding in this case. If you removed
public DbSet<Customer> Customers { get; set; }
and replaced it with
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
you have completely changed the behavior. The first uses entities and full power of EF. The second is only helper to execute custom SQL. Second without first or without defining entity in OnModelCreating doesn't use Customer as mapped entity at all - it uses it as any normal class (only mapped entities can use features like lazy loading).
Because your Customer is now mapped to view you cannot use your former Customer class used with table. You must define mapping of Customer to a view by cheating EF:
modelBuilder.Entity<Customer>().ToTable("vw_ContactsList"); // EF code fist has no view mapping
Once you have this you can try again using:
public DbSet<Customer> Customers { get; set; }
Unless your view is updatable you will get exception each time you try to add, update or delete any customer in this set. After mapping relation between Customer and Contact mapped to views your navigation properties should hopefully work.
The problem with SqlQuery is the way how it works. It returns detached entities. Detached entities are not connected to the context and they will not lazy load its navigation properties. You must manually attach each Customer instance back to context and to do that you again need DbSet.

Resources