Can't add foreign key constraints on Laravel5.5 - eloquent

i created several tables :
KEYNEEDS TABLE :
public function up()
{
Schema::create('keyneeds', function(Blueprint $table)
{
$table->integer('id', true);
$table->integer('segment_id')->index('FK_KN_SEGMENT');
$table->integer('design_id')->index('FK_KEYNEEDS_DESIGN');
$table->integer('admin_id')->index('FK_KN_ADMIN');
$table->string('kntextcolor', 191)->nullable();
$table->string('kntextcolorhover', 191)->nullable();
$table->string('knbgcolor', 191)->nullable();
$table->string('knbgcolorhover', 191)->nullable();
$table->string('knhvize', 191)->nullable();
$table->timestamps();
$table->softDeletes();
$table->engine = 'InnoDB';
});
}
DESIGNS TABLE :
public function up()
{
Schema::create('designs', function(Blueprint $table)
{
$table->integer('id', true);
$table->string('design', 191)->nullable();
});
}
I also added foreign key constraints
public function up()
{
Schema::table('keyneeds', function(Blueprint $table)
{
$table->foreign('design_id', 'FK_KEYNEEDS_DESIGN')->references('id')->on('designs')->onUpdate('RESTRICT')->onDelete('RESTRICT');
$table->foreign('admin_id', 'FK_KN_ADMIN')->references('id')->on('admins')->onUpdate('RESTRICT')->onDelete('RESTRICT');
$table->foreign('segment_id', 'FK_KN_SEGMENT')->references('id')->on('segments')->onUpdate('RESTRICT')->onDelete('RESTRICT');
});
}
i have this SQL error when i try to migrate the database:
Can not add external index constraints (SQL: alter table `keyneeds` add constraint` FK_KEYNEEDS_DESIGN` foreign key (`design_id`) references`
igns(id`) on delete RESTRICT on update RESTRICT)

The answer was given by Jonas Staudenmeir :
public function up()
{
Schema::create('designs', function(Blueprint $table)
{
$table->integer('id', true);
$table->string('design', 191)->nullable();
$table->engine = 'InnoDB';
});
}

Related

EF Core - How to add a record INSTANTLY when DbContext instance is created only once

I am trying to add a default record(administrator identities) instantly when DbContext instance is craeted. However, I tried to add corresponding logic on OnModelCreating and OnConfiguring methods but no success. both of the methods are complaining:
A DbContext instance cannot be used inside 'OnConfiguring' since it is
still being configured at this point
A DbContext instance cannot be used inside 'OnModelCreating' in any
way that makes use of the model that is being created
More importantly, I don't want that the corresponding logic(adding default record) to be called & checked more than once (unnecessary overhead). the logic shown below:
if (UserList.Find("admin") == null){
UserList.Add(new UserItem() { co_id = "1", username = "admin", name = "admin", password = "123", admin = true });
SaveChanges();
}
where do I put this piece of code and call it only once when Model is created?
An alternative way to do what you're trying to do is use migrations. Create an empty migration or use an existing migration. You can either use OnModelCreating() or Sql() for this. But make sure to hash your passwords before storing them in the database.
1. Using Sql()
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Username = table.Column<string>(type: "nvarchar(max)", nullable: true),
Password = table.Column<string>(type: "nvarchar(max)", nullable: true),
Admin = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
#region CustomInsertUsing_Sql()
migrationBuilder.Sql(#"
SET IDENTITY_INSERT [dbo].[Users] ON
INSERT INTO [dbo].[Users] ([Id], [Name], [Username], [Password], [Admin]) VALUES (1, N'Admin', N'Admin', N'123', 1)
SET IDENTITY_INSERT [dbo].[Users] OFF
");
#endregion
}
2. Using OnModelCreating()
In your database context class, add OnModelCreating method and use HasData to configure seed record.
using DataSeedingSample.Models;
using Microsoft.EntityFrameworkCore;
namespace DataSeedingSample.Data
{
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) {}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasData(
new User {
Id = 1,
Name = "Admin",
Username = "Admin",
Password = "123",
Admin = true
});
}
}
}
Now add a migration, which will create the following migration.
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "Users",
columns: new[] { "Id", "Admin", "Name", "Password", "Username" },
values: new object[] { 1, true, "Admin", "123", "Admin" });
}
Whatever method from above (Sql() or OnModelCreating()) you used, now you have a migration with the record you want to seed.
Next create a new class MigrationHelper in the project. It is going to be a static class because we need to create an extension method to start all the migrations at the application’s startup, which is what you want to do right?.
using System;
using DataSeedingSample.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace DataSeedingSample.Helpers
{
public static class MigrationHelper
{
public static IHost MigrateDatabase(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
using (var appContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>())
{
try
{
appContext.Database.Migrate();
}
catch (Exception ex)
{
Console.WriteLine($"Error >>>> {ex}");
throw;
}
}
}
return host;
}
}
}
Finally call this in the Program class' Main method like this.
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.MigrateDatabase()
.Run();
}
UPDATE .NET 6:
In Program.cs file, after line var app = builder.Build(); add app.MigrateDatabase();
var app = builder.Build();
app.MigrateDatabase(); //<-- Add this here
This will seed your database every time you start the application by running all the migrations, if they are not executed already. Take a look at the doc also.

How to restart postgres sequence with Entity framework seeding migration?

I have the following database seeder:
public partial class Seed_Languages : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "Languages",
columns: new[] { "LanguageId", "LangCode", "LangName", "Sort" },
values: new object[,]
{
{ 1, "AU", "Австралия", 0 },
{ 159, "CX", "Остров Рождества", 0 },
{ 160, "PN", "Острова Питкэрн", 0 },
{ 161, "SH", "Острова Святой Елены, Вознесения и Тристан-да-Кунья", 0 },
{ 162, "PK", "Пакистан", 0 },
{ 163, "PW", "Палау", 0 },
.... and so on ...
As you can see I'm going to populate some table, that contains language's names (on Russian, for showing on UI), language's codes, some additional field - Sort (not important here) and primary key. Simple, right?
Here is the table:
Then I create it inside my OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// many fluent api calls
LanguagesSeeder.SeedLanguages(modelBuilder);
}
Then I run $ dotnet ef database update and seeding works fine! But problems soon began.
When I try to insert new one language, .NET gives me:
Exception data:
Severity: ERROR
SqlState: 23505
MessageText: duplicate key value violates unique constraint "PK_Languages"
Detail: Key ("LanguageId")=(1) already exists.
SchemaName: public
TableName: Languages
ConstraintName: PK_Languages
File: nbtinsert.c
Line: 434
"Hmmmm lets try again" - I thought. And:
Exception data:
Severity: ERROR
SqlState: 23505
MessageText: duplicate key value violates unique constraint "PK_Languages"
Detail: Key ("LanguageId")=(2) already exists.
SchemaName: public
TableName: Languages
ConstraintName: PK_Languages
File: nbtinsert.c
Line: 434
Routine: _bt_check_unique
You see that? The same error but with another Primary key complaint! The first was: Key ("LanguageId")=(1) already exists. and the second Key ("LanguageId")=(2) already exists.
!
So, what to do? I know this way:
ALTER SEQUENCE <name of sequence> RESTART WITH <your number is here>;
But it's pretty uncomfortable to run this SQL in a console after seeding. Am I miss something? Maybe, there is a standard way for this, I mean using some EF API?
Update
I will show you my Language model:
namespace Domains
{
public class Language
{
public int LanguageId { get; set; }
public int Sort { get; set; }
public List<Customer> Customers { get; set; }
public List<PushMessageLang> PushMessageLangs { get; set; }
[NotMapped]
public IEnumerable<PushMessage> PushMessages
{
get => PushMessageLangs?.Select(r => r.PushMessage);
set => PushMessageLangs = value.Select(v => new PushMessageLang()
{
PushMessageId = v.PushMessageId
}).ToList();
}
public string LangName { get; set; }
public string LangCode { get; set; }
}
}
I make insert via my repository abstraction:
Base repository:
public class BaseRepository<T, C> : IRepository<T>
where T : class
where C : DbContext
{
protected C DataContext;
private readonly DbSet<T> _dbset;
public BaseRepository(C context)
{
DataContext = context;
_dbset = context.Set<T>();
}
public virtual IQueryable<T> All => _dbset;
public virtual async Task SaveAsync(T entity)
{
await _dbset.AddAsync(entity);
await DataContext.SaveChangesAsync();
}
public async Task SaveAsync(List<T> entity)
{
await _dbset.AddRangeAsync(entity);
await DataContext.SaveChangesAsync();
}
public virtual async Task UpdateAsync(T entity)
{
_dbset.Attach(entity).State = EntityState.Modified;
_dbset.Update(entity);
await DataContext.SaveChangesAsync();
}
public virtual async Task DeleteAsync(int id)
{
var dbEntity = await _dbset.FindAsync(id);
if (dbEntity != null)
{
_dbset.Remove(dbEntity);
await DataContext.SaveChangesAsync();
}
}
}
And in the controller:
public async Task<IActionResult> Create([FromForm] LanguageViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View(viewModel);
}
var newLanguage = new Language()
{
Sort = viewModel.Sort,
LangCode = viewModel.Code,
LangName = viewModel.Name
};
await _languageRepository.SaveAsync(newLanguage);
return RedirectToAction("Index");
}
Update 2
As asked in the comments I'll pin here all fluent api for Language model:
// many to many with `Message` entity
modelBuilder.Entity<PushMessageLang>()
.HasKey(bc => new { bc.PushLangId, bc.PushMessageId });
modelBuilder.Entity<PushMessageLang>()
.HasOne(bc => bc.Language)
.WithMany(b => b.PushMessageLangs)
.HasForeignKey(bc => bc.PushLangId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<PushMessageLang>()
.HasOne(bc => bc.PushMessage)
.WithMany(c => c.PushMessageLangs)
.HasForeignKey(bc => bc.PushMessageId)
.OnDelete(DeleteBehavior.Cascade);
// has unique language code
modelBuilder.Entity<Language>()
.HasIndex(x => x.LangCode).IsUnique();
Update 3
As asked #Roman Marusyk, I pine here SQL script for creating Languages table.
-- auto-generated definition
create table "Languages"
(
"LanguageId" integer generated by default as identity
constraint "PK_Languages"
primary key,
"LangName" text,
"LangCode" text,
"Sort" integer default 0 not null
);
alter table "Languages"
owner to makeapp_pushes;
create unique index "IX_Languages_LangCode"
on "Languages" ("LangCode");
Hmm, now I see that don't have anything about auto increment.
But my SQL client shows my:
Add HasKey to model configuration
modelBuilder.Entity<Language>()
.HasKey(x => x.LanguageId)
.HasIndex(x => x.LangCode).IsUnique();
as #IvanStoev mentioned, by convention, the property LanguageId is already the primary key
Try to specify
modelBuilder.Entity<Language>()
.Property(p => p.LanguageId)
.ValueGeneratedOnAdd();
In the migration I have added manually this string:
migrationBuilder.RestartSequence("Languages_LanguageId_seq", 251, "public");
where Languages_LanguageId_seq - name of sequence,
251 - number of starting of the sequence (PK value),
public - scheme name.
Here is documentation. Now I can insert without any errors.

Referencing two models at the same time

I have a booking table which points to bookings on either room_bookings or restaurant_bookings table a room or restaurant table the migration are
Booking:
Schema::create('bookings', function (Blueprint $table) {
$table->increments('id');
$table->morphs('booking');
$table->morphs('business');
$table->boolean('status');
}
room_bookings:
Schema::create('room_bookings', function (Blueprint $table) {
$table->increments('id');
$table->string('from');
$table->string('to');
}
restaurant_bookings:
Schema::create('restaurant_bookings', function (Blueprint $table) {
$table->increments('id');
$table->string('table_no');
}
Restaurant:
Schema::create('restaurants', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
}
Room:
Schema::create('rooms', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->integer('number_of_rooms');
}
In previous projects I used to define single relationship to the model. I want to save the booking id ,class and room/restaurants id and class and class name on the table.
Before I defined realtionship on single class as
$boking = Booking::findOrFail($id);
//Defined relationsip on the model as booking()
$room->booking()->create($data)
The Models are as follows
Business.php
class Booking extends Model
{
public function bookings()
{
return $this->morphTo();
}
public function business()
{
return $this->morphTo();
}
}
on Room.php and Restaurant.php
public function booking()
{
return $this->morphMany(Booking::class, 'booking');
}
public function business()
{
return $this->morphMany(Booking::class, 'business');
}

EF different mapping for an entity based on inheritance

I have two entities (tables) Action and ActionLog. The ActionLog is derived from Action. I need to map entity Action to table Action when it is used alone and map it to table ActionLog when it is used inside an inheritance relationship.
Entities:
Action entity properties:
Action_Property1
Action_Property2
Action_Property3
ActionLog entity properties:
All the inherited properties from Action entity
ActionLog_Property1
Tables:
Action table columns:
Action_Property1
Action_Property2
Action_Property3
ActionLog table columns:
Action_Property1
Action_Property2
Action_Property3
ActionLog_Property1
Is this possible using EF6 Code First mapping in a single context?
Edit 1:
I try to be more explicit. I need something like this:
using(var ctx = new DbContext())
{
var action = new Action
{
Action_Property1 = 1,
Action_Property2 = 2,
Action_Property3 = 3
};
ctx.Actions.Add(action);
ctx.SaveChanges();
}
The lines above should write the Action entity to Action table.
using(var ctx = new DbContext())
{
var actionLog = new ActionLog
{
Action_Property1 = 1,
Action_Property2 = 2,
Action_Property3 = 3,
ActionLog_Property1 = 1
};
ctx.ActionLogs.Add(actionLog);
ctx.SaveChanges();
}
The lines above should write the ActionLog entity to ActionLog table.
Yes, it's possible. Using Mapping the Table-Per-Concrete Class (TPC) Inheritance
it can do so
public class InheritanceMappingContext : DbContext
{
public DbSet<Action> Action { get; set; }
public DbSet<ActionLog> ActionLog { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ActionLog>().Map(m =>
{
m.MapInheritedProperties(); // add inherited property to actionLog
m.ToTable("ActionLog");
});
modelBuilder.Entity<Action>().Map(m =>
{
m.ToTable("Action");
});
}
}
Sure, map your base class:
public class YourBaseClassMapping<T> : EntityTypeConfiguration<T> where T : YourBaseClassEntity
{
protected YourBaseClassMapping()
{
HasKey(x => x.Id);
...
}
}
and then map inherited class:
public class InheritedClassMapping<T> : EntityTypeConfiguration<T> where T : InheritedClassMapping
{
public InheritedClassMapping()
{
// new mapping for inherited class
}
}
and add both mappings to DbModelBuilder as other mappings:
public class YourDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new YourBaseClassMapping());
modelBuilder.Configurations.Add(new InheritedClassMapping());
}
}

Database gets updated before SaveChanges is called in Entity Framework

I am trying to use EF with an insert stored procedure as I have no direct access to the table. My understanding is the database should not get updated until SaveChanges() is called in the code but database is updated everything an insert happens. In this case, 4 database calls were made.
How do I make this to have just one database call and update multiple records?
This may be classed as database-first EF? The stored procedure is imported as a function to edmx in a normal way.
Code sample:
public ActionResult Index()
{
List<Product> products = new List<Product> {
new Product() { Title = "coca cola", Description = "good"},
new Product() { Title = "apple", Description = "fruit"},
new Product() { Title = "orange", Description = "fruit"},
new Product() { Title = "banana", Description = "my favourite"}
};
EFwithSPtest context = new EFwithSPtest();
foreach(var p in products)
{
context.Insert(p.Title, p.Description);
}
context.SaveChanges();
return View();
}
Stored procedure:
ALTER PROCEDURE [dbo].[Insert]
#Title nvarchar (50),
#Description nvarchar (max)
AS
INSERT INTO [Product] (Title, Description) VALUES (#Title, #Description)
RETURN 0
Auto generated DbContext class:
public partial class EFwithSPtest : DbContext
{
public EFwithSPtest()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual int Insert(string title, string description)
{
var titleParameter = title != null ?
new ObjectParameter("Title", title) :
new ObjectParameter("Title", typeof(string));
var descriptionParameter = description != null ?
new ObjectParameter("Description", description) :
new ObjectParameter("Description", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("Insert", titleParameter, descriptionParameter);
}
}