Updating database keys, clustered index error - asp.net

I'm working with VS2013 on an asp.net web api project an Entity Framework deployed on Azure
I'm trying to alter the keys on one of my tables to instead of being composed of 2 foreign keys its composed of 3 keys. Using the Add-Migration command it generates the following migration
public partial class ChangeKeys_v4 : DbMigration
{
public override void Up()
{
DropPrimaryKey("dbo.MyTable");
AddPrimaryKey("dbo.MyTable", new[] { "ClientId", "Order", "ZoneId" });
}
public override void Down()
{
DropPrimaryKey("dbo.MyTable");
AddPrimaryKey("dbo.MyTable", new[] { "ClientId", "ZoneId" });
}
}
This is the entity class that was changed to include order as a key:
public class MyTable
{
[Key, Column(Order = 1), ForeignKey("Client")]
public int ClientId { get; set; }
[Key, Column(Order = 2)]
public int Order { get; set; }
[Key, Column(Order = 3), ForeignKey("Zone")]
public string ZoneId { get; set; }
public virtual Client Client { get; set; }
public virtual Zone Zone { get; set; }
public MyTable() { }
public MyTable(Client c, int o, Zone z)
{
Client = c;
Order = o;
Zone = z;
}
}
I've successfully did Update-Database on my development environment, but when doing so to the text environment I get the following error
Tables without a clustered index are not supported in this version of SQL Server. Please create a clustered index and try again.
Could not drop constraint. See previous errors.
The statement has been terminated.
What changes can I do to my migation class so it works.
I've seen some solution and most say to drop table, and that's not really an option for when I have to push to production environment.

You can't do the EF code->DB upgrade for this change with Azure hosting the db.
First, you have to have downtime. Then you run a script in SSMS connecti ng to the Azure DB. The script will:
Renames MyTable to e.g. TableOld
Creates MyTable with the new PK, FK's etc
insert into MyTable(col names) select (col names) from MyTableOld;
Run some checks: row counts in both tables, other queries which you know the results to. Basically check everything works.
Drop table MyTableOld
Maybe delay step 5 until you're sure it's all OK after the users have given it a test drive.

Related

SqlException: On Delete Cascade not working ASP.NET

When I try to delete a user from the ASP.NETUsers table I get SqlException:
SqlException: The DELETE statement conflicted with the REFERENCE
constraint "FK_Applications_AspNetUsers_UserID". The conflict occurred
in database "JobGuide", table "dbo.Applications", column 'UserID'.
This problem is occurring because the User's Id is the Foreign key in another table, but "On delete cascade" is not working. For more details this is my model:
My extended Identity User:
public class AppUser : IdentityUser
{
public string Name { get; set; }
public string City { get; set; }
public string RoleName { get; set; }
public virtual ICollection<Application> Applications { get; set; }
}
Application model (i.e. when a user applies for a job):
public class Application
{
public int ApplicationID { get; set; }
public int JobID { get; set; }
public virtual Job Job { get; set; }
public string UserID { get; set; }
public virtual AppUser User { get; set; }
}
Job model:
public class Job
{
public int JobID { get; set; }
public string Title { get; set; }
public virtual ICollection<Application> Applications { get; set; }
}
So up to here I created two One to Many relationships, AspNetUser one to many with Application and Job one to many with Application.
And this is my Fluent API mapping configuration:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Application>()
.HasKey(i => i.ApplicationID);
builder.Entity<Application>()
.HasOne<AppUser>(sc => sc.User)
.WithMany(s => s.Applications)
.HasForeignKey(sc => sc.UserID)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Application>()
.HasOne<Job>(sc => sc.Job)
.WithMany(s => s.Applications)
.HasForeignKey(sc => sc.JobID)
.OnDelete(DeleteBehavior.Cascade);
}
Delete method from controller:
var userInfo = await userManager.FindByIdAsync(user.Id);
if (userInfo == null)
{
return NotFound();
}
_ = await userManager.RemoveFromRoleAsync(userInfo, userInfo.RoleName);
_ = await userManager.DeleteAsync(userInfo);
int rowsAffected = await db.SaveChangesAsync();
Any idea why this error is not disappearing, is Fluent API good? or i need to type raw Sql to delete the Application with that User once and then the User? I have looked at almost all similar questions but none of them are working for me.
It seems that the cascade delete is not configured in the application table, try to use SSMS to check it:
Open the SQL Server Object Explorer (or using Server Explorer), find the SQL Server Database, then right click the Applications table -> Script As -> CREATE To -> New Query Window, then check whether the table is configured Cascade delete, check this screenshot:
To solve this issue, after configuration Cascade Delete using Fluent API mapping, please remember to enable migration and update the database:
Add-Migration AddCascadeDelete
Update-Database
Besides, you could also configure the Cascade Delete by executing the following SQL command (via SSMS):
ALTER TABLE [dbo].[Applications]
ADD CONSTRAINT [FK_Applications_AspNetUsers_UserID] FOREIGN KEY ([UserID]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE;
Can you try setting it the other way around;
builder.Entity<User>()
.HasMany<Application>(u => u.Applications)
.WillCascadeOnDelete();
or use .WillCascadeOnDelete() on your code.

ef core migration insert data

I'd like to insert data into table in migration. Is it possible? Migration needs parameterless constructor available and I'd like to use db context defined in Startup.cs file (best I'd like to get it throught dependency injection). How do that?
In the EF Core 2.1, migrations can automatically compute what insert, update or delete
operations must be applied when upgrading the database to a new version of the model.
As an example, we have a User and UserComment entities like these:
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public List<UserComment> UserComments { get; set; }
}
public class UserComment
{
[Key]
public int CommentId { get; set; }
public string CommentTitle { get; set; }
[ForeignKey("User")]
public int FKUserId { get; set; }
public User User { get; set; }
}
In the DBContext, override the OnModelCreating function and seed data to each entity:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasData(new User[] {
new User{UserId=1,Name="iman"},
new User{UserId=2,Name="Alex"},
});
}
To seed datas that have a relationship, the foreign key value must be specified:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserComment>().HasData(new UserComment[] {
new UserComment{FKUserId=1,CommentId=1,CommentTitle="Comment1"},
});
}
Be careful: you must use migrations to apply changes
Migration is a process of "upgrading" your DB to a new "version". During this, your existing DB tables ("old version") does not required to match your classes (entities) ("new version"), so you can't safely use them.
During migration you should operate only with tables and records using raw SQL commands. You may use migrationBuilder.Sql("UPDATE ..."); for such updates, put them manually into migration Up() code.
If you need perform data modifications using entity classes - you should use "Seed Data" solution (from #itikhomi comment), but remember that it will be run every time your app starts, so you should do some version-check inside it.

EF Core custom results from stored procedure

I'm using EF Core which I believe is also known as EF 7? Anyways, I have a stored procedure that returns custom results that will not identify with any specific table. How am I supposed to access those results and how should I call the sql command?
Normally we have to use .FromSql but that is only available on entities, eg. _context.User.FromSql(). I don't have an entity for it.
So I tried building a dbset/entity for the results, but again, there is no associated table, and there is also no "Key". How am I supposed to parse the data then of the custom results?
You can create a fake entity for the result of your stored procedure. You can set any property as the Key, even if in the results of the stored procedure the key values are not unique.
For example if you have a table like the following :
CREATE TABLE [dbo].[banana_hoard]
(
[id] INT NOT NULL PRIMARY KEY IDENTITY (1,1),
[owner] NVARCHAR(64) NOT NULL,
[bananas] BIGINT NOT NULL
)
You can have a query that does not return the row id like this :
public class Program
{
public static void Main(string[] args)
{
using (var db = new MonkeyDbContext())
{
var sp_results = db.search.FromSql(#"execute <YOUR_STORED_PROC>");
str_result = String.Join("\n", sp_results.Select(a => JsonConvert.SerializeObject(a) ));
Console.WriteLine("stored proc result :\n" + str_result);
}
}
}
public class MonkeyDbContext : DbContext
{
public DbSet<StoredProcRow> search { get; set; }
protected override void OnConfiguring (DbContextOptionsBuilder builder)
{
builder.UseSqlServer(#"Server=(localdb)\monkey_db;Database=monkey_db;Trusted_Connection=True;");
}
}
public class StoredProcRow
{
[Key]
public string Owner { get; set; }
public long Bananas { get; set; }
}

EF Migrations: Move Table from 2 Column PK to Single Column causes ALTER before DROP and fails

I'm using EF 4.3.1 Code First Migrations. I have a table like:
public class Product
{
[Key]
[Column(Order=0)]
[MaxLength(100)]
public string Store { get; set; }
[Key]
[Column(Order=1)]
[MaxLength(100)]
public string Sku { get; set; }
}​
I have an existing table created with the above code. I then moved it to a single-column Primary Key:
public class Product
{
[MaxLength(100)]
public string Store { get; set; }
[Key]
[MaxLength(100)]
public string Sku { get; set; }
}​
This causes EF to fail in the next automatic migration, complaining:
ALTER TABLE [Product] ALTER COLUMN [Store] nvarchar
The object 'PK_Product' is dependent on column 'Store'. ALTER
TABLE ALTER COLUMN Store failed because one or more objects access this
column.
Clearly the PK_Product needs to be dropped before attempting to fire this ALTER statement (why is it altering the column at all?), but instead the migration fails.
Am I doing something wrong or is this a bug? Workarounds?
You won't be able to do this with an automatic migration. You'll have to create a migration using Add-Migration and then change it so it only modifies the PK.
The migration can be as simple as:
public partial class TheMigration : DbMigration
{
public override void Up()
{
DropPrimaryKey("Products", new[] { "Store", "Sku" });
AddPrimaryKey("Products", "Sku");
}
public override void Down()
{
DropPrimaryKey("Products", new[] { "Sku" });
AddPrimaryKey("Products", new[] { "Store", "Sku" });
}
}
EF is altering the column because, when it's part of a Key, it's implicitly NOT NULL.
You can leave it as-is, add a [Required] attribute, or allow EF to alter the column after dropping the PK.

The member with identity does not exist in the metadata collection. Parameter name: identity

We are using EF Code First 4.3.1.
We are developing an ASP.NET Web Role referring to multiple class libraries.
There are two class libraries each containing classes and an individual DBcontext.
Lets say the Library1 has classes A and B.
DBcon1: DbSet and DbSet
Lets say the Library2 has classes C and D.
Class C{
[Key]
public int CId{ get; set;}
[Required]
public virtual A referencedA {get; set;}
}
DBcon2: DbSet<C> and DbSet<D>
When I try to use the DBcon2 as such:
using (var con = new DBcon2())
{
C vr = new C();
vr.CId= 1;
vr.referencedA = DBCon1.As.First();
con.Cs.Add(vr);
con.SaveChanges();
}
I get an exception as:
"The member with identity does not exist in the metadata collection.
Parameter name: identity"
Both DBCon1 and DBcon2 are using the sane SQL Server Database "SampleDB".
Please point me in the right direction.
I got this error and fixed it by not trying to set the navigation property in the related table, just set the foreign key id instead
eg
public class Student()
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public int CourseId { get; set; }
public virtual Course Course { get; set; }
}
public class Course()
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Main code:
var myCourse = new Course();
var myCourseId = 1;
var student = new Student() {
CourseId = myCourseId
// Course = myCourse <-- this would cause the error
}
Might not be your issue but maybe it will point you in the right direction and hopefully will help someone else.
The exception is a bit cryptic, but pretty clear if you realise that a context needs information about entities (metadata) to be able to write sql statements. Thus, your DBcon2 context has no clue where to find the primary key of an A, because it has no metadata about A.
You could however set an integer property A_Id (or the like), but then you'll have to write custom code to resolve it to an A.
Another option is to merge (parts of) the contexts, if possible.

Resources