Override DbContext EF6 - c#

I'm trying to understand how to dynamically create the connection string for my DbContext, but my application says it has no connection string in the app.config (and that's correct because I don't want to use it in the app.config or web.config). This is what I have:
In my solution I have a project called InterfaceApp. It is a ASP.NET MVC 5 application. When I put my connection string in the web.config all seems to be working fine.
In my solution I have an other project called InterfaceApp.Connector.Erp1. Here I want to connect to an ERP application and fetch some items. So in my repository I have:
namespace InterfaceApp.Connector.Erp1.Repository
{
internal class ItemRepository : IItemRepository
{
public IEnumerable<Item> Items
{
get
{
List<Item> items = new List<Item>();
using (Models.Entities context = new Models.Entities())
{
var itemList = context.Items.ToList();
foreach(var item in itemList)
{
items.Add(new Item() { Id = item.ID, Description = item.Description, ItemCode = item.ItemCode });
}
}
return items.ToList();
}
}
}
}
I've created a partial class to connect to the database:
namespace InterfaceApp.Connector.Erp1.Models
{
public partial class Entities
{
public Entities(string connectionString)
: base(ConnectionString())
{
}
private static string ConnectionString()
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder
{
DataSource = "MyServer", //When this works it will be dynamic
InitialCatalog = "XXX", //When this works it will be dynamic
PersistSecurityInfo = true,
IntegratedSecurity = true,
MultipleActiveResultSets = true,
};
var entityConnectionStringBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/Models.Erp1Model.csdl|res://*/Models.Erp1Model.ssdl|res://*/Erp1Model.msl",
ProviderConnectionString = sqlBuilder.ConnectionString
};
return entityConnectionStringBuilder.ConnectionString;
}
}
}
The Context class that is auto-generated by EF6 (Db First) looks like this:
namespace InterfaceApp.Connector.Erp1.Models
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Items> Items { get; set; }
}
}
When I run my application, the debugger stops at the auto-generated class, but not at my partial class. Because it cannot find the connection string Entities in my app.config and web.config it generates an error saying that the connection string is not found in the application config file. What am I doing wrong?

When you are calling the DbContext, you're calling the empty constructor (new Models.Entities()). Thus, it will call the auto-generated DbContext. If you want to call your partial class, you need to call it explicitly with the parameter.
Remember when you create a partial class, the compiler merges them, so you have this when compiled :
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
public Entities(string connectionString)
: base(ConnectionString())
{
}
private static string ConnectionString()
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder
{
DataSource = "MyServer", //When this works it will be dynamic
InitialCatalog = "XXX", //When this works it will be dynamic
PersistSecurityInfo = true,
IntegratedSecurity = true,
MultipleActiveResultSets = true,
};
var entityConnectionStringBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/Models.Erp1Model.csdl|res://*/Models.Erp1Model.ssdl|res://*/Erp1Model.msl",
ProviderConnectionString = sqlBuilder.ConnectionString
};
return entityConnectionStringBuilder.ConnectionString;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Items> Items { get; set; }
}
What you probably need a a method to create your DbContext and call it instead of calling a new DbContext.
public static Entities Create()
{
return new Entities(ConnectionString());
}
Then you can use it this way :
using (var context = Entities.Create())
{
//...
}

Related

Entity Framework Core - database is not filled

I'm trying to fill the database with my StoreInitializer, but tables are empty, I'm using Entity Framework Core and ASP.NET Core, I want to configure my Initializer for using Update-Database in Package Manager Console. I followed this tutorial https://learn.microsoft.com/pl-pl/aspnet/core/data/ef-mvc/intro but on start or on updating, database is empty anyway. Visual studio shows me no errors.
StoreInitializer.cs :
namespace PC_SHOP.DAL
{
public static class StoreInitializer
{
public static void Initialize(StoreContext context)
{
context.Database.EnsureCreated();
if (context.Electronics.Any())
{
return;
}
// Działy główne
var electronics = new Electronic[]
{
new Electronic { ID=1, Name = "Laptopy"},
new Electronic { ID=2, Name = "Komputery"},
new Electronic { ID=3, Name = "Części PC"},
new Electronic { ID=4, Name = "Dla graczy"},
new Electronic { ID=5, Name = "Peryferia PC"},
new Electronic { ID=6, Name = "Sieci i komunikacja"},
new Electronic { ID=7, Name = "Oprogramowanie"},
};
foreach (Electronic e in electronics)
{
context.Electronics.Add(e);
}
context.SaveChanges();
// Rodzaje laptopów
var laptops = new List<Laptop>
{
new Laptop { ID=1, Name = "Laptopy", ElectronicID = 1, IconFileName = "1_laptop.jpg"},
new Laptop { ID=2, Name = "Laptopy Apple", ElectronicID = 1, IconFileName = "2_apple.jpg"},
};
foreach (Laptop l in laptops)
{
context.Laptops.Add(l);
}
context.SaveChanges();
}
}
}
StoreContext.cs :
namespace PC_SHOP.DAL
{
public class StoreContext : DbContext
{
public StoreContext(DbContextOptions<StoreContext> options) : base(options)
{
}
public DbSet<Electronic> Electronics { get; set; }
public DbSet<Laptop> Laptops { get; set; }
public DbSet<LaptopProduct> LaptopProducts { get; set; }
public DbSet<Employee> Employee { get; set; }
public DbSet<Project> Project { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Electronic>().ToTable("Electronic");
modelBuilder.Entity<Laptop>().ToTable("Laptop");
modelBuilder.Entity<LaptopProduct>().ToTable("LaptopProduct");
modelBuilder.Entity<Employee>().ToTable("Employee");
modelBuilder.Entity<Project>().ToTable("Project");
}
}
}
In ConfigurationServies in Startup.cs i added:
services.AddDbContext<StoreContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
and in appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "Data Source=DESKTOP-J0OBBIO\\SQLEXPRESS;Initial Catalog=PC_SHOP;Integrated Security=SSPI;Database=Employee;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Ok i fixed that,
I added context parameter to Configure method in Startup.cs file. Everything else have not been changed.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, StoreContext context )
{
...
//After app.UseMvc()
StoreInitializer.Initialize(context);
}
And it's filling my database correctly
Add this line to Program.cs Main() method :
StoreInitializer.Initialize(context);
public static void Main(string[] args)
{
//CreateWebHostBuilder(args).Build().Run();
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<AppDbContext>();
//context.Database.EnsureCreated();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
host.Run();
}
EnsureCreated can be used to create the database and tables in prototyping and testing scenarios. It should be used instead of, not together with Migrations. It will only create the database if it does not exist or contains no tables. In other cases it will do nothing.
Note that if the database exists it will not do anything, so you need to use a database name that does not exist.
Really you should be generating migrations from the comand line then running the migrations from your initializer instead of using .EnsureCreated. EnsureCreated is for in memory database that is created each time. ie prototyping.

Entity Framework Code First custom migrations Transaction Exception

I have defined a custom migration class, like so:
public class MyMigrate<TMigrationsConfiguration> : IDatabaseInitializer<MyContext>
where TMigrationsConfiguration : DbMigrationsConfiguration<MyContext>, new()
{
private readonly TMigrationsConfiguration _config;
public MyMigrate(string connectionString, string invariantProvider)
{
var typeInitialCreate = typeof(MyConfiguration);
var migrationsAssembly = Assembly.GetAssembly(typeInitialCreate);
var migrationsNamespace = typeInitialCreate.Namespace;
_config = new TMigrationsConfiguration
{
TargetDatabase = new DbConnectionInfo(connectionString, invariantProvider),
MigrationsAssembly = migrationsAssembly,
MigrationsNamespace = migrationsNamespace,
};
}
public void InitializeDatabase(MyContext context)
{
// Update the migrator with the config containing the right connection string
var dbMigrator = new DbMigrator(_config);
var dbm = dbMigrator.GetDatabaseMigrations();
var local = dbMigrator.GetLocalMigrations();
var pending = dbMigrator.GetPendingMigrations();
dbMigrator.Update();
}
}
This is the actual configuration class, that does some additional seeding stuff. This class is located in another assembly.
public sealed class MyMigrationConfiguration : DbMigrationsConfiguration<MyContext>
{
public MigrationConfiguration()
{
AutomaticMigrationsEnabled = false;
ContextKey = "MyContext";
}
}
This is the generated Configuration file you get after you enable migrations. It is used, to identify and load the assembly with the generated migration files.
internal sealed class MyConfiguration : DbMigrationsConfiguration<MyContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
ContextKey = "MyContext";
}
}
}
I execute it like so:
private static void MigrateToLatest()
{
var conString = System.Configuration.ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString;
Database.SetInitializer(new MyMigrate<MigrationConfiguration>(conString, Migrate.SqlInvariantProvider));
using (var fctx = new MyContext(conString, "SYSTEM"))
{
if (!fctx.Database.CompatibleWithModel(false))
{
fctx.Database.Initialize(false);
}
}
}
I get the following error message when i call Initialize(false):
`An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Additional information: The transaction passed in is not associated with the current connection. Only transactions associated with the current connection may be used.`
I have checked the connection strings, they seem fine. If i uncomment the Initializer line, or replace it with SetInitializer(null), i will get the "model has changed" error message, but transactions don't seem to be a problem. Somehow it must have to do with my custom migrator class. I used this code in EF 4.3 and it worked. In 6.1.3 it no longer executes.

Pass connection string to EF DbContext code first

I have a context that derives from DbContext like the one below:
public class StudentContext : DbContext
{
public StudentContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DBModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<StudentContext, StudentMigrations.Configuration>());
}
public DbSet<Students> Students {get; set;}
}
I'm trying to pass the connection string by:
studentContext = new StudentContext(settings.ConnectionString)
The settings are loaded at run-time by reading a configuration file.
I've tried this and I've also tried setting the connection string inside the StudentContext constructor by using this.Database.Connection.ConnectionString.In either case, I get an exception that asks me to provide a default constructor or provide an implementation of IDbContextFactory. The only thing that works is this:
public class StudentContext : DbContext
{
public static string ConnectionString;
public StudentContext(string connectionString) : base(ConnectionString = connectionString)
{
}
//And also provide a default implementation of the DbContext constructor:
public StudentContext() : base(ConnectionString)
{
}
protected override void OnModelCreating(DBModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<StudentContext, StudentMigrations.Configuration>());
}
public DbSet<Students> Students {get; set;}
}
I am trying to reduce the use of statics in code and therefore, if I could get the first option to work, that'd be great.
Turns out that the connection string is cached in a readonly string for MigrateDatabaseToLatestVersion from this answer. I just had to update the class to:
public class StudentContext : DbContext
{
public StudentContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DBModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<StudentContext, StudentMigrations.Configuration>(true)); //Passing true here to reuse the client context triggering the migration
}
public DbSet<Student> Students {get; set;}
}
We have to specify the entity connection string. in DbContext
SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
{
DataSource = "SOURAV-PC", // Server name
InitialCatalog = "efDB", //Database
UserID = "sourav", //Username
Password = "mypassword", //Password
};
//Build an Entity Framework connection string
EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/testModel.csdl|res://*/testModel.ssdl|res://*/testModel.msl",
ProviderConnectionString = sqlString.ToString()
};
return entityString.ConnectionString;
}

EntityFramework 6 Code-based configuration of connection string for database-first

I'm attempting to make an existing application work without an app.config (it is required due to a very specific environment). Problem is that it's heavily relying on EntityFramework 6 to work with an SQL-Server.
I'm trying to use a code-based configuration, but I can't figure out how to provide a correct connection string through my configuration class.
I made a configuration class:
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
SetDefaultConnectionFactory(new MyConnectionFactory());
SetProviderServices("System.Data.SqlClient", System.Data.Entity.SqlServer.SqlProviderServices.Instance);
}
}
Then provided it to my DbContext (Generated by EF automatically from bd):
[DbConfigurationType(typeof(MyConfiguration))]
public partial class TestModelEntities
{
}
With a custom connection factory:
public class MyConnectionFactory : IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
var newConnStringBuilder = new SqlConnectionStringBuilder
{
UserID = "user",
Password = "pass",
InitialCatalog = "databaseName",
DataSource = "serverName"
};
var entityConnectionBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
ProviderConnectionString = newConnStringBuilder.ToString(),
Metadata = #"res://*/TestModel.csdl|
res://*/TestModel.ssdl|
res://*/TestModel.msl"
};
var newDbConnect = new EntityConnection(entityConnectionBuilder.ToString());
return newDbConnect;
}
}
However. When I test it, I get an UnintentionalCodeFirstException. Why? What am I missing?
You should provide connection string to your context via :base(connectionString). Create a class as below:
public class ConnectionStringBuilder
{
public static string Construct()
{
var newConnStringBuilder = new SqlConnectionStringBuilder
{
UserID = "user",
Password = "pass",
InitialCatalog = "databaseName",
DataSource = "serverName"
};
var entityConnectionBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
ProviderConnectionString = newConnStringBuilder.ToString(),
Metadata = #"res://*/TestModel.csdl|
res://*/TestModel.ssdl|
res://*/TestModel.msl"
};
return entityConnectionBuilder.ToString();
}
}
Then modify your Context constructor to look like this:
public DbContext()
: base(ConnectionStringBuilder.Construct())
{
}
It should work fine now. (source)

"The operation cannot be completed because the DbContext has been disposed" #2

I'm building a simple ASP.NET API using EF and Oracle Database. When I want to get all elements from a database table the response (500) says "The operation cannot be completed because the DbContext has been disposed".
Well, I've tried to solve this problem before to post it here. But I can't. My Controller Code is as follows.
public class PruebasController : ApiController
{
//Prueba[] pruebas = new Prueba[]
//{
// new Prueba { Name = "Tomato Soup"},
// new Prueba { Name = "Yo-yo"},
// new Prueba { Name = "Hammer"}
//};
public IQueryable<Prueba> GetAllPruebas()
{
Database.SetInitializer(new DropCreateDatabaseAlways<OracleDbContext>());
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas;
}
}
}
(As you see, I have a "pruebas" List and when I return it the http service works)
And this is my OracleDbContext
public class OracleDbContext : DbContext
{
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
You are returning an IQueryable object. Once you return, you exit your Using statement, which closes your Context. You need to enumerate using .ToList() before you exit your using statement. This will execute the query while the context is still open.
Change it to this:
public List<Prueba> GetAllPruebas()
{
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas.ToList();
}
}
Also, you should add your initializer in the constructor of your context, not your GetAllPruebas method, like this:
public class OracleDbContext : DbContext
{
public OracleDbContext()
{
Database.SetInitializer<OracleDbContext>(new DropCreateDatabaseAlways<OracleDbContext>());
}
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
Problem Solved. I wrote CreateDatabaseIfNotExists instead DropCreateDatabaseAlways in the Database.SetInitializer and it works.

Categories