We recently created a web project in .NET Core and are using PostgreSQL via NPGSQL Entity Framework extension. At first, there were no problems at all. But when trying to add more schemas, the new schema was not created. According to other posts on the Internet, we should not be limited to any number of schemas. Does anybody know what might cause the problem?
P.S.: After dropping the original schema, the new schema was successfully created. However, trying to add next schema without dropping the previous one seems like a problem.
FooDbContext.cs
public class FooDbContext : DbContext
{
/// <summary>
/// For unit testing.
/// </summary>
public FooDbContext()
: base()
{
}
public FooDbContext(DbContextOptions<FooDbContext> options)
: base(options)
{
}
public virtual DbSet<Person> People { get; set; }
public virtual DbSet<Project> Projects { get; set; }
public virtual DbSet<PersonOnProject> PeopleOnProjects { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("TestingFoo");
base.OnModelCreating(modelBuilder);
}
}
Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddEntityFrameworkNpgsql().AddDbContext<Model.FooDbContext>((provider, builder) => {
builder.UseNpgsql(Configuration.GetConnectionString("LocalDb"), b => b.MigrationsAssembly("TestingFoo"));
});
services.AddMvc();
}
....
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.ApplicationServices.GetService<Model.FooDbContext>().Database.EnsureCreated();
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 404
&& !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/index.html";
await next();
}
});
app.UseStaticFiles();
app.UseMvc();
}
We found we had difficulty "seeing" other schemas besides "public" until we adjusted the connection string to connect to the cluster instead of the instance.
Related
I have a CatalogDbContext class.
I want to use Bogus library to seed fake data into the database that my unit tests will use.
The example provided in bogus's github repo makes use of the HasData method of the CatalogDbContext class to seed data into the tables.
However, I will not want this HasData method to be executed from the API - meaning, the HasData method should only be run if the DBContext is created from the Unit Tests.
Kindly advise how to achieve this?.
using Bogus;
using Catalog.Api.Database.Entities;
using Microsoft.EntityFrameworkCore;
namespace Catalog.Api.Database
{
public class CatalogDbContext : DbContext
{
public CatalogDbContext(DbContextOptions<CatalogDbContext> options) : base(options)
{
}
public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfiguration(new CatalogBrandEntityTypeConfiguration());
builder.ApplyConfiguration(new CatalogTypeEntityTypeConfiguration());
builder.ApplyConfiguration(new CatalogItemEntityTypeConfiguration());
FakeData.Init(10);
builder.Entity<CatalogItem>().HasData(FakeData.CatalogItems);
}
}
internal class FakeData
{
public static List<CatalogItem> CatalogItems = new List<CatalogItem>();
public static void Init(int count)
{
var id = 1;
var catalogItemFaker = new Faker<CatalogItem>()
.RuleFor(ci => ci.Id, _ => id++)
.RuleFor(ci => ci.Name, f => f.Commerce.ProductName());
}
}
}
I have a working web application (an end point) containing a few methods and connected to two tables in sql server. This application is fully implemented from scratch by myself in an ashx file and does not follow any new or old architecture, simply some methods in ashx file that are remotely called and handle requirements of client. There are shared DLLs among client and server for data handling.
For some reasons I want to upgrade client side to Dot Net core, consequently common DLL needs to be upgraded and finally the end point.
Now I'm facing the problem that EF Core only supports code first, but there are ways for scaffolding . I started with Microsoft tutorials. Then I see There are certain ways for migrating and scaffolding existing database, but I got stuck for hours in first step of using command "dotnet ef dbcontext scaffold "Data Source=..." . Then usually tutorial materials get combined with other technologies like asp.net core very fast, I need to read tons of technologies to do a simple task.
I'm worried I'm going the wrong way. there are only two tables and I can implement table structure by hand. Isn't there any sample code that I can modify it's table definitions and I can restart my project soon? If things are so hard, I will omit EF from my project and redefine the whole end point logic by text sql queries.
I can implement table structure by hand.
Great. Simply create a DbContext subtype that has a DbSet for each of your entities. The only thing scaffolding does is save you time.
Here's a complete example for SQL Server:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Order> Orders { get; } = new HashSet<Order>();
}
public class Order
{
public int CustomerId { get; set; }
public int Id { get; set; }
public Customer Customer { get; set; }
}
public class Db : DbContext
{
string connectionString = "Server=localhost; database=efcore5test; integrated security = true;TrustServerCertificate=true;";
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders{ get; set; }
public Db(string connectionString) : base()
{
this.connectionString = connectionString;
}
public Db() : base()
{
this.Database.SetCommandTimeout(180);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var constr = this.connectionString;
optionsBuilder.LogTo(Console.WriteLine);
optionsBuilder.UseSqlServer(constr, o => o.UseRelationalNulls().CommandTimeout(180).UseNetTopologySuite());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasKey(o => new { o.CustomerId, o.Id });
base.OnModelCreating(modelBuilder);
}
}
I want to create an in-memory SQLite database.
Here is startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<TestDBContext>().AddEntityFrameworkSqlite();
}
Here is the Model of database:
public class TestModel
{
public string UserName { get; set; }
[Key]
public string id { get; set; }
}
Here is the DBContext of database:
public class TestDBContext : DbContext
{
public virtual DbSet<TestModel> Test { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=:memory:");
}
}
And here is the controller:
private readonly TestDBContext TestDBContext;
public HomeController(ILogger<HomeController> logger,TestDBContext _TestDBContext)
{
_logger = logger;
this.TestDBContext = _TestDBContext;
}
public IActionResult Index()
{
TestDBContext.Database.EnsureCreated();
TestDBContext.SaveChanges();
TestDBContext.Test.Add(new TestModel() { User = DateTime.Now.ToString(),id=Guid.NewGuid().ToString() });
TestDBContext.SaveChanges();
return View(TestDBContext.Test.ToList());
}
Every time it runs, it will report an error:
Inner Exception 1:
SqliteException: SQLite Error 1: 'no such table: Test'.
I have used the EnsureCreated and the EnsureCreated runs without any error. Why it still be like this?
EF Core's DbContext always opens and closes connections to the database automatically, unless you pass an already open connection. And when the connection gets closed, the Sqlite In-memory database will be removed. So I modified your code a little bit like this.
public void ConfigureServices(IServiceCollection services)
{
var connection = new SqliteConnection("datasource=:memory:");
connection.Open();
services.AddControllersWithViews();
services.AddDbContext<TestDBContext>(options =>
{
options.UseSqlite(connection);
});
}
And the Database Context class - I added the constructors so that I can provide the parameters.
public class TestDBContext : DbContext
{
public TestDBContext(DbContextOptions options) : base(options)
{
}
protected TestDBContext()
{
}
public virtual DbSet<TestModel> Test { get; set; }
}
And instead of creating the database in the Index action method, create it in the startup.
Also, opt to use the DbContext.Database.Migrate() method instead of EnsureCreated else you won't be able to use migrations later down the line.
I am working on Multi Tenant application where each user has different database schema.
Based on user credentials we will get which schema he belongs.
Below is my Context Class which dynamically refreshes context every time based on schemaName
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public string SchemaName { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public ApplicationDbContext(string schemaname)
: base()
{
SchemaName = schemaname;
}
public DbSet<EmployeeDetail> EmployeeDetail { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var configuration = builder.Build();
var serviceProvider = new ServiceCollection().AddEntityFrameworkSqlServer()
.AddSingleton<IModelCustomizer, SchemaContextCustomize>()
.BuildServiceProvider();
optionsBuilder.UseSqlServer(configuration["ConnectionStrings:SchemaDBConnection"]).UseInternalServiceProvider(serviceProvider);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// modelBuilder.MapProduct(SchemaName);
modelBuilder.RemovePluralizingTableNameConvention();
if (!string.IsNullOrEmpty(SchemaName))
{
modelBuilder.HasDefaultSchema(SchemaName);
}
base.OnModelCreating(modelBuilder);
}
public string CacheKey
{
get { return SchemaName; }
}
public class SchemaContextCustomize : ModelCustomizer
{
public SchemaContextCustomize(ModelCustomizerDependencies dependencies)
: base(dependencies)
{
}
public override void Customize(ModelBuilder modelBuilder, DbContext dbContext)
{
base.Customize(modelBuilder, dbContext);
string schemaName = (dbContext as ApplicationDbContext).SchemaName;
(dbContext as ApplicationDbContext).SchemaName = schemaName;
}
}
Above code works perfectly without any issues. OnConfiguring method is responsible for providing correct schema everytime
Major concern is related to performance For every request below line is taking almost 40% of Time every time for initializing context
base.Customize(modelBuilder, dbContext);
Is there any way to increase performance, ideally this should be called once.
What is the proper way for changing schema at runtime based on user so that OnConfiguring method is called only once?
I am new to Asp.Net Core (Even to Asp.Net and web). I am using Asp.Net Core 2 with MySQL, using Pomelo.EntityFrameWorkCore.MySql (2.0.1) driver. I just created a custom dbcontext with Courses and Enrollments table, along with the default created ApplicationDbContext. The Primary Key for Enrollments is a composite key, comprising of UserId and CourseId. Below is the code :
public class CustomDbContext : DbContext
{
public virtual DbSet<Courses> Courses { get; set; }
public virtual DbSet<Enrollments> Enrollments { get; set; }
public CustomDbContext(DbContextOptions<CustomDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Courses>(entity =>
{
entity.ToTable("courses");
entity.HasIndex(e => e.Name)
.HasName("Coursescol_UNIQUE")
.IsUnique();
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.Duration).HasColumnType("time");
entity.Property(e => e.Name).HasMaxLength(45);
});
modelBuilder.Entity<Enrollments>(entity =>
{
entity.HasKey(e => new { e.UserId, e.CourseId });
entity.ToTable("enrollments");
entity.HasIndex(e => e.CourseId)
.HasName("fk_Courses_Enrollments_CourseId_idx");
entity.HasIndex(e => e.UserId)
.HasName("fk_Users_Enrollments_CourseId_idx");
entity.HasIndex(e => new { e.UserId, e.CourseId })
.HasName("UniqueEnrollment")
.IsUnique();
entity.Property(e => e.CourseId).HasColumnType("int(11)");
entity.HasOne(d => d.Course)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.CourseId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Courses_Enrollments_CourseId");
entity.HasOne(d => d.User)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Users_Enrollments_UserId");
});
}
}
The Program.cs goes like :
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<CustomDbContext>();
context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
The configure services method in Startup.cs goes like :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<CustomDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
The Courses Model goes like :
public partial class Courses
{
public Courses()
{
Enrollments = new HashSet<Enrollments>();
}
public int Id { get; set; }
public string Name { get; set; }
public TimeSpan? Duration { get; set; }
public ICollection<Enrollments> Enrollments { get; set; }
}
The Enrollments Model goes like :
public partial class Enrollments
{
public string UserId { get; set; }
public int CourseId { get; set; }
public Courses Course { get; set; }
public ApplicationUser User { get; set; }
}
The applicationUser model goes like :
public ApplicationUser()
{
Enrollments = new HashSet<Enrollments>();
}
public ICollection<Enrollments> Enrollments { get; set; }
Now, here's what I've tried so far :
If i add Course and Enrollment model to the ApplicationDBContext, then everything goes fine.
If in CustomDBContext i have a non-composite primary Key, even then it works fine. (I just tried another example)
Can somebody please throw some light on why is this error ? Is this the intended way to handle such a case ?
Thanks in advance.
It's because the Enrollments entity has been discovered by ApplicationDbContext through ApplicationUser.Enrollments navigation property. This is explained in the Including & Excluding Types - Conventions section of the EF Core documentation:
By convention, types that are exposed in DbSet properties on your context are included in your model. In addition, types that are mentioned in the OnModelCreating method are also included. Finally, any types that are found by recursively exploring the navigation properties of discovered types are also included in the model.
I guess now you see the problem. The Enrollments is discovered and included in the ApplicationDbContext, but there is no fluent configuration for that entity there, so EF uses only the default conventions and data annotations. And of course composite PK requires fluent configuration. And even there wasn't a composite PK, it's still incorrect to ignore the existing fluent configuration. Note that Courses is also included in the ApplicationDbContext by the aforementioned recursive process (through Enrollments.Courses navigation property). Etc. for other referenced classes.
Note that the same applies in the other direction. ApplicationUser and all referenced from it are discovered and included in the CustomDbContext w/o their fluent configuration.
The conclusion - don't use separate contexts containing interrelated entities. In your case, put all the entities in the ApplicationDBContext.