Problem with tables creation by code first model with npgsql
Database creation should work from version 2.2.0-beta1:
«David Karlaš added support for EFMigration and Database creation in EF6+
Now it is possible to start Code First projects without needing to create a database upfront. EntityFramework and Npgsql will take care of it.
Emil Lenngren added support for a lot of missing features of EntityFramework.»
https://www.nuget.org/packages/Npgsql/2.2.0-beta1
But, when I tried to do this I faced with problems.
Firstly, I did the simple project, that works with SqlServer:
I created Asp.Net MVC project (VS2013) and add some code:
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
}
public class MovieDBContext : DbContext
{
public DbSet<Movie> Movies { get; set; }
}
public class MovieDBInitializer : DropCreateDatabaseIfModelChanges<MovieDBContext>
{
}
public class HomeController : Controller
{
private MovieDBContext db = new MovieDBContext();
public ActionResult Index()
{
List<Movie> objs = db.Movies.ToList();
return View(objs);
}
WebConfig:
<connectionStrings>
<add name="MovieDBContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=MovieCreateDbInSql;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\MovieDB.mdf" providerName="System.Data.SqlClient" />
</connectionStrings>
<entityFramework>
<contexts>
<context type="MovieCreateDbInSql.Models.MovieDBContext, MovieCreateDbInSql">
<databaseInitializer type="MovieCreateDbInSql.Models.MovieDBInitializer, MovieCreateDbInSql" />
</context>
</contexts>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
When starting the project the database MovieDB is created.
All is works.
We can see this database in App_Data folder in the project.
Good.
Then I tried to do the same with npgsql.
Add libs:
EntityFramework6.Npgsql.dll (version 3.1.0.0)
Npgsql.dll (version 3.1.2.0)
2.
Than change WebConfig:
<connectionStrings><add name="MovieDBContext" connectionString="Server=127.0.0.1;Port=5432;Database=postgres;User Id=postgres;Password=postgres000;" providerName="Npgsql" /></connectionStrings>
<entityFramework>
<contexts>
<context type="MovieCreateDbInSql.Models.MovieDBContext, MovieCreateDbInSql">
<databaseInitializer type="MovieCreateDbInSql.Models.MovieDBInitializer, MovieCreateDbInSql" />
</context>
</contexts>
<defaultConnectionFactory type="Npgsql.NpgsqlFactory, Npgsql" />
<providers>
<provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, EntityFramework6.Npgsql"></provider>
</providers>
</entityFramework>
<system.data>
<DbProviderFactories>
<remove invariant="Npgsql" />
<add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql"
type="Npgsql.NpgsqlFactory, Npgsql" />
</DbProviderFactories>
</system.data>
Start. Error:
System.NotSupportedException was unhandled by user code
HResult=-2146233067
Message=Model compatibility cannot be checked because the database does not contain model metadata. Model compatibility can only be checked for databases created using Code First or Code First Migrations.
Source=EntityFramework
But this configuration was enough for SqlServer!
Ok. Try this:
EF5 Getting this error message: Model compatibility cannot be checked because the database does not contain model metadata
1.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
}
Don't really help. The error is the same
public class MovieDBInitializer : DropCreateDatabaseAlways
{
}
New error:
Npgsql.PostgresException was unhandled by user code
HResult=-2147467259
Message=55006: база данных "postgres" занята другими пользователями
Source=Npgsql
ErrorCode=-2147467259
BaseMessage=база данных "postgres" занята другими пользователями
Code=55006
Detail=Эта база данных используется ещё в 1 сеансе.
(error 55006 database is being accessed by other user)
This error is not good too.
As far as I understand this error is because we have serious database posgresql unlike primitive localdb.sql.
And dropping db operation in postgresql is not so easy as in localdb.sql.
I found several links on that error:
https://groups.google.com/forum/#!topic/npgsql-help/1f5niOiHpGg
Drop a database being accessed by another users?
npgsql and Entity Framework code first setup problems
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MovieDBContext>(null);
}
The same error again:
System.NotSupportedException was unhandled by user code
HResult=-2146233067
Message=Model compatibility cannot be checked because the database does not contain model metadata. Model compatibility can only be checked for databases created using Code First or Code First Migrations.
Source=EntityFramework
What should I do to have the opportunity to create tables by the code first model?
Of course I can generate the database in SqlServer and than convert scripts to postgresql, but I want do this with npgsql.
Libraries that I use:
EntityFramework.6.1.3
EntityFramework6.Npgsql.3.1.0
Npgsql.3.1.3
My Database context:
public class CustomContext : DbContext
{
public CustomContext()
: base("name=CustomContext")
{
Database.SetInitializer(
new MigrateDatabaseToLatestVersion<CustomContext, Configuration>());
}
public DbSet<Movie> Movies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Change schema name from "dbo" => "public" for all entities (except MigrationHistory).
// MigrationHistory schema name is modified in custom HistoryContext
modelBuilder.Types().Configure(c => c.ToTable(c.ClrType.Name, "public"));
}
}
Migration history table has a default schema set to "dbo". If you want to have it in a different schema (ex. public) you have to create a custom HistoryContext.Currently it's impossible to set a default schema with modelBuilder.HasDefaultSchema("public") while using automatic migrations (AFAIK).
public class CustomHistoryContext : HistoryContext
{
public CustomHistoryContext(DbConnection existingConnection, string defaultSchema)
: base(existingConnection, defaultSchema)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Change schema from "dbo" to "public"
modelBuilder.Entity<HistoryRow>()
.ToTable(tableName: "__MigrationHistory",
schemaName: "public");
}
}
Finally, my migration configuration class looks like this:
internal sealed class Configuration : DbMigrationsConfiguration<CustomContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
// Setting custom historyContext factory.
// Used for modify __MigrationHistory table schema name from "dbo" to "public"
this.SetHistoryContextFactory("Npgsql",
(connection, defaultSchema) =>
new CustomHistoryContext(connection, defaultSchema));
}
protected override void Seed(CustomContext context)
{
// This method will be called after migrating to the latest version.
}
}
After these steps automatic code first migrations work correctly.
However, here is a bit more details.
I have classes:
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime IssueDay { get; set; }
public decimal EarnedMoney { get; set; }
public virtual ICollection<Actor> Actors { get; set; }
}
public class Actor
{
public int ID { get; set; }
public int MovieID { get; set; }
public DateTime BirthDay { get; set; }
public string Name { get; set; }
}
And I got scripts:
CREATE TABLE "Movie"
(
"ID" serial NOT NULL,
"Title" text,
"IssueDay" timestamp without time zone NOT NULL DEFAULT '0001-01-01 00:00:00'::timestamp without time zone,
"EarnedMoney" numeric(18,2) NOT NULL DEFAULT 0,
CONSTRAINT "PK_public.Movie" PRIMARY KEY ("ID")
)
WITH (
OIDS=FALSE
);
ALTER TABLE "Movie"
OWNER TO postgres;
CREATE TABLE "Actor"
(
"ID" serial NOT NULL,
"MovieID" integer NOT NULL DEFAULT 0,
"Name" text,
"BirthDay" timestamp without time zone NOT NULL DEFAULT '0001-01-01 00:00:00'::timestamp without time zone,
CONSTRAINT "PK_public.Actor" PRIMARY KEY ("ID"),
CONSTRAINT "FK_public.Actor_public.Movie_MovieID" FOREIGN KEY ("MovieID")
REFERENCES "Movie" ("ID") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
)
WITH (
OIDS=FALSE
);
ALTER TABLE "Actor"
OWNER TO postgres;
CREATE INDEX "Actor_IX_MovieID"
ON "Actor"
USING btree
("MovieID");
And these scripts require additional processing of course.
That is why in general there is no difference generate scripts in postgres directly or generate in sqlServer and then convert to postgres by means of Navicat for example…
Related
I'm currently trying to use Npgsql (version 3.1.3) to insert a record into a table with a generated identity using the official documentation (Npgsql.org) . But I always get the error:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Npgsql.PostgresException (0x80004005): 428C9: cannot insert into column "mitteilung_id"
I have already found several questions about this topic, but they are either outdated (version 2 or lower) or do not work.
My project is structured as follows. The table definition looks like this:
CREATE TABLE mitteilung
(
mitteilung_id INTEGER GENERATED ALWAYS AS IDENTITY
CONSTRAINT mitteilung_pk
PRIMARY KEY,
betreff TEXT
CONSTRAINT mitteilung_nn_betreff
CHECK (betreff IS NOT NULL)
CONSTRAINT mitteilung_ck_length_betreff
CHECK (length(betreff) <= 100),
nachricht TEXT
CONSTRAINT mitteilung_ck_length_nachricht
CHECK (length(nachricht) <= 500)
CONSTRAINT mitteilung_nn_nachricht
CHECK (nachricht IS NOT NULL),
erfasst_am TIMESTAMP WITH TIME ZONE
CONSTRAINT mitteilung_nn_erfasst_am
CHECK (erfasst_am IS NOT NULL)
);
I have defined the entity as follows:
public class Mitteilung : ISlongooEntity
{
public int MitteilungId { get; set; }
...
I have also tried to add the following attributes to the ID property:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
In the DB context I have tested the following settings to solve the problem.
modelBuilder.Entity<Mitteilung>()
.Property(b => b.MitteilungId)
.UseIdentityAlwaysColumn();
modelBuilder.Entity<Mitteilung>()
.Property(b => b.MitteilungId)
.Metadata.SetValueGenerationStrategy(NpgsqlValueGenerationStrategy.IdentityAlwaysColumn);
modelBuilder.Entity<Mitteilung>()
.Property(b => b.MitteilungId)
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
But no matter in which combination I use the settings, I get the above mentioned error message when trying to save an entity. I also don't quite understand why an attempt is made to update the ID at all when an update is made. What am I doing wrong?
public Mitteilung Save(Mitteilung obj)
{
var addedObj = Context.Mitteilungen.Add(obj);
// Context.Entry(obj).Property(x => x.MitteilungId).IsModified = false;
Context.SaveChanges();
return addedObj.Entity;
}
The code below does work correctly.
Note that EF Core will automatically detect that MitteilungId is the primary key for Mitteilung, and since it's an int, will set it up as GENERATED BY DEFAULT AS IDENTITY. In other words, you don't need any of the fluent API calls - or the [Key] or [DatabaseGenerated] annotations - EF Core will set things up correctly by convention.
If, for some reason, you need to have GENERATED ALWAYS AS IDENTITY (instead of BY DEFAULT), then the fluent API call below can be used.
If you're still having an issue, can you please change the code sample below to produce the error?
class Program
{
static async Task Main(string[] args)
{
await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
ctx.Blogs.Add(new Mitteilung { Name = "foo" });
await ctx.SaveChangesAsync();
}
}
public class BlogContext : DbContext
{
public DbSet<Mitteilung> Blogs { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql("...")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Mitteilung>()
.Property(b => b.MitteilungId)
.UseIdentityAlwaysColumn();
}
}
public class Mitteilung
{
public int MitteilungId { get; set; }
public string Name { get; set; }
}
This will do for all entities
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.UseIdentityAlwaysColumns();
...
}
I am building a NET Core MVC app that consumes an existing MS SQL database. Primary keys and foreign keys are already established and working correctly at the database level.
I followed the example in this article and used package manager console to reverse engineer the models and database context from the database. This seemed to work well. It resulted in all models being added to my app's Models folder including a robust database context class. The problem I'm having is that relational information about these entities isn't being populated at runtime. I'm getting nulls for related entities for which foreign keys are established both in the database and in the fluent API code generated by the scaffolding process.
I have two tables, Mode and Submode, that are related via foreign key.
Scaffolding generated these two classes for the above two tables:
public partial class Submode
{
public Submode()
{
Contact = new HashSet<Contact>();
}
public int Id { get; set; }
public int ModeId { get; set; }
public string Code { get; set; }
public bool Visible { get; set; }
public bool IsDefault { get; set; }
public Mode Mode { get; set; }
public ICollection<Contact> Contact { get; set; }
}
public partial class Mode
{
public Mode()
{
Contact = new HashSet<Contact>();
Submode = new HashSet<Submode>();
}
public int Id { get; set; }
public string Code { get; set; }
public bool Visible { get; set; }
public bool IsDefault { get; set; }
public ICollection<Contact> Contact { get; set; }
public ICollection<Submode> Submode { get; set; }
}
Scaffolding also generated this fluent API snippet in the database context:
modelBuilder.Entity<Submode>(entity =>
{
entity.HasIndex(e => e.Code)
.HasName("UQ__Submode__A25C5AA75D2A9AE7")
.IsUnique();
entity.Property(e => e.Code)
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false);
entity.HasOne(d => d.Mode)
.WithMany(p => p.Submode)
.HasForeignKey(d => d.ModeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Submode_ModeId");
});
Every example I've read on setting foreign keys with fluent API show a similar pattern to the above snippets. But Mode comes back null for Submode.
Null at runtime
And I get a null reference exception in the returned view because I'm trying to display properties of the related Mode object. Am I missing some configuration or is there a problem with the scaffolded code?
UDPATE - as requested, here's the implementation that's fetching data from the database context.
public class SQLSubModeData : ISubModeData
{
private w4lkrContext _context;
public SQLSubModeData(w4lkrContext context)
{
_context = context;
}
public IQueryable<Submode> Get()
{
return _context.Submode.OrderBy(p => p.Id);
}
public Submode Get(int id)
{
return _context.Submode.FirstOrDefault(p => p.Id == id);
}
}
UPDATE (SOLVED) - Enabling lazy loading fixed the problem. Three steps got me there:
Installed Microsoft.EntityFrameworkCore.Proxies(2.1.2) via NuGet
Updated Startup.cs -> AddDbContext() method, as follows:
services.AddDbContext(options => options.UseLazyLoadingProxies().UseSqlServer(_configuration.GetConnectionString("W4LKR")));
Made all navigation properties virtual. This had to be done on every model in the app, not just the one being called in my example above. Errors are thrown if even one is left out.
But Mode comes back null for Submode.
Since your Navigation Properties aren't declared as virtual, you have disabled Lazy Loading, so EF will only populate your Navigation Properties if you do Eager Loading, or Explicit Loading.
See Loading Related Data
I'm currently working on new project with WebApi and SQL Server, but as every application we must to generate and save logs, the question is, how to create it and the proper way to storage, since I'm using Azure there's a Azure Blob Table, which sounds perfect to create logs, but I'm not a professional. So every user that has been logged, how to organize this, I need some help!
Azure Web App provide Application log feature, we can enable it in Azure portal.
After that, we can write log using following code and logs will be written in the blobs which I configured on Azure Portal.
Trace.TraceInformation("Hello Azure Log");
If you want to write your application log to Azure table storage, you could create a custom TraceListener. I just created a AzureTableStorageTraceListener which will write trace log to Azure Table, code below is for your reference.
public class AzureTableStorageTraceListener : System.Diagnostics.TraceListener
{
protected override string[] GetSupportedAttributes()
{
return new[] { "StorageConnectionString", "LogsTableName" };
}
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
{
Write(message, eventType.ToString());
}
public override void Write(string message, string category)
{
string stroageConnectionString = Attributes["StorageConnectionString"];
string tableName = Attributes["LogsTableName"];
// Retrieve the storage account from the connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(stroageConnectionString);
// Create the table client.
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
// Create the CloudTable object that represents the "people" table.
CloudTable table = tableClient.GetTableReference(tableName);
// Create a new log entity.
LogEntity log = new LogEntity(category, message);
// Create the TableOperation object that inserts the log entity.
TableOperation insertOperation = TableOperation.Insert(log);
// Execute the insert operation.
table.Execute(insertOperation);
}
public override void WriteLine(string message, string category)
{
Write(message + "\n", category);
}
public override void Write(string message)
{
Write(message, null);
}
public override void WriteLine(string message)
{
Write(message + "\n");
}
}
public class LogEntity : TableEntity
{
public LogEntity(string category, string message)
{
category = category == null ? "Default" : category;
this.PartitionKey = category;
this.RowKey = Guid.NewGuid().ToString();
this.Message = message;
this.CreatedDate = DateTime.Now;
}
public LogEntity() { }
public string Category { get; set; }
public string Message { get; set; }
public DateTime CreatedDate { get; set; }
}
To use this TraceListener, you need to add following configuration section to Web.config.
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add name="AzureTableStorageListener"
type="{your namespace name}.AzureTableStorageTraceListener,{your assembly name which contains the custom trace listener}"
StorageConnectionString="{your storage connection string}"
LogsTableName="{azure storage table name}"/>
</listeners>
</trace>
</system.diagnostics>
I'm having trouble updating the database, when I apply the migration I'm getting this error:
The object 'PK_dbo.CostCenter' is dependent on column 'Id'.
ALTER TABLE DROP COLUMN Id failed because one or more objects access this column.
I just tried to add a virtual property (CostCenter) to this table to be able to get that data
[Table("Department")]
public class Department
{
public int Id { get; set; }
public string Code { get; set; }
public virtual IList<Cost> Costs_Department { get; set; }
public virtual CostCenter CostCenter { get; set; }
}
This is the CostCenter table
[Table("CostCenter")]
public class CostCenter
{
public int Id { get; set; }
public string Code { get; set; }
[Required]
public virtual Department Department { get; set; }
}
And another table related to Department
[Table("Cost")]
public class Cost
{
public int Id { get; set; }
...
public virtual Department Departament { get; set; }
public virtual Material Material { get; set; }
}
The only change I added was the CostCenter property on the Department table, this is what VS created on the migrations file
public partial class CC : DbMigration
{
public override void Up()
{
DropForeignKey("dbo.CostCenter", "Department_CostCenter_Id", "dbo.Department");
DropIndex("dbo.CostCenter", new[] { "Department_CostCenter_Id" });
DropColumn("dbo.CostCenter", "Id");
RenameColumn(table: "dbo.CostCenter", name: "Department_CostCenter_Id", newName: "Id");
DropPrimaryKey("dbo.CostCenter");
AlterColumn("dbo.CostCenter", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.CostCenter", "Id");
CreateIndex("dbo.CostCenter", "Id");
AddForeignKey("dbo.CostCenter", "Id", "dbo.Department", "Id");
}
public override void Down()
{
DropForeignKey("dbo.CostCenter", "Id", "dbo.Department");
DropIndex("dbo.CostCenter", new[] { "Id" });
DropPrimaryKey("dbo.CostCenter");
AlterColumn("dbo.CostCenter", "Id", c => c.Int(nullable: false, identity: true));
AddPrimaryKey("dbo.CostCenter", "Id");
RenameColumn(table: "dbo.CostCenter", name: "Id", newName: "Department_CostCenter_Id");
AddColumn("dbo.CostCenter", "Id", c => c.Int(nullable: false, identity: true));
CreateIndex("dbo.CostCenter", "Department_CostCenter_Id");
AddForeignKey("dbo.CostCenter", "Department_CostCenter_Id", "dbo.Department", "Id", cascadeDelete: true);
}
}
I already have a relationship similar to this on other tables, so I don't know what's causing the problem this time
I faced similar issue and just remove all the previous migration files, create a new one and executed update-database command. If you can, also delete the database, Before it.
Per suggested solution above, once you are in the production, dropping db won't be the case.
In my case,
first I dropped the relation between two tables by editing my
entities
Then create a migration script which will have these changes
(dropping relation changes)
run update-database command.
Then edit your entity to drop the column.
Then create a migration script which will have most recent changes
(dropping column)
After that, edit your entities to have the relation again
Then you can add another migration which will have the changes to
re-create the relation between tables.
As final step, run update-database command to publish changes.
Is this the best solution?
I doubt about it.
Was it fixed the issue?
Yes.
I created a model class, ClassOne, for my database as follows
public class ClassOne
{
[Key]
public int primary { get; set; }
public string column1 { get; set; }
public string column2 { get; set; }
public string column3 { get; set; }
}
Then in a DAL folder I created, i added the following code
public class MyContext : DbContext
{
public DbSet<ClassOne> ClassOnes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
And I set up my connection string in my web.config
Instead of adding a controller for the model ClassOne, I want to access the data from my project's home controller file, in the index method. So I added the code
private ProjectContext db = new ProjectContext();
Hand my index method return statement is
return(db.ClassOnes.ToList());
However, when I run the code, I get the following error message:
There is already an object named 'ClassOne' in the database.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.SqlClient.SqlException: There is already an object named 'ClassOne' in the database.
ANd it points to my return statement in the homecontroller.cs file:
return(db.ClassOnes.ToList());
How can I fix this please?