EF Core - Create a migration without connection string - c#

I have been ranging across multiple questions, tutorials and examples for something that fits this problem.
What if I don't know my connection string at the time I want to create my first initial migration? Given I am given the opportunity to set the connection string at the time of instantiating context eg:
var connection = #"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
As described in the docs..
If you need to have a connection string to run migrations then for those situations where you don't have one (Tenant database where you get a connection string from a user account) how do you run an initial migration??
Do you create a dummy connection string to create the migration.. seems dodgy to me. I would love some input on this.

You can implement IDesignTimeDbContextFactory that will be used instead your host builder in the design time. UseSqlServer now has a parameterless overload.
It will look like this and has to be in the same assembly as the db context or in the startup project:
public class MyAppDbContextFactory: IDesignTimeDbContextFactory<MyAppDbContext>
{
public MyAppDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<MyAppDbContext>();
optionsBuilder.UseSqlServer();
return new MyAppDbContext(optionsBuilder.Options);
}
}
More details can be found here:
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory

Related

Empty DBContext at startup

I want to make a test database that erase its content each time the program starts in a ASP.Net/Blazor project or end.
What should I add to be sure that all the changes I made during runtime get erased when program stops ?
Edit : I'm also seeding data with OnModelCreating method of DbContext and I want the data seeded to be kept.
Since Blazor only works on ASP.NET Core 3, I assume you use EF Core, not just Entity Framework.
EF Core was designed with unit testing in mind. The Testing chapter in the EF Core documentation explains how you to use the in-memory provider or SQLite provider in in-memory mode for testing.
Assuming your contexts have a constructor that accepts DbContextOptions, you can configure their provider from the outside, either during testing or because you want to use different connection strings.
Given this DbContext
public class BloggingContext : DbContext
{
public BloggingContext()
{ }
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
...
}
Using one provider or another is as simple as creating the options and calling the constructor.
In-Memory provider
The in-memory provider is essentially a dictionary, so it can't cover complex query scenarios. It's easier to setup and use though.
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseInMemoryDatabase(databaseName: "Add_writes_to_database")
.Options;
// Run the test against one instance of the context
using (var context = new BloggingContext(options))
{
var service = new BlogService(context);
service.Add("https://example.com");
context.SaveChanges();
}
SQLite in-memory provider
Using the SQLite provider is almost the same. An in-memory database is created and then the code is essentially the same as before. When the connection closes, the database and the data in it are gone.
using(var connection = new SqliteConnection("DataSource=:memory:"))
{
connection.Open();
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new BloggingContext(options))
{
context.Database.EnsureCreated();
}
// Run the test against one instance of the context
using (var context = new BloggingContext(options))
{
var service = new BlogService(context);
service.Add("https://example.com");
context.SaveChanges();
}
}

Share connection between DbContext objects in Entity Framework 6 Database first approach

I am trying to share a connection in between 2 different DB context objects using EF 6 .
I have got the code mentioned here
https://msdn.microsoft.com/en-us/data/dn456843.aspx
to work for a single DB Context but as soon as I try to share the connection with another DB Context object I am running into trouble. Below is what I have done so for.
public class MyUnitOfWork
{
// BAD CODE : having static connection / transaction is bad design , i use DI to implement this properly without need for static fields , this code is used here to avoid having to mention my DI configuration;
public static EntityConnection _commonConnection ;
public static System.Data.Entity.DbContextTransaction _commonTransaction;
// Generic function to create DBContext object based on T supplied.
public static T GetDbContext<T>()
{
if(_commonConnection == null)
{
// generates a generic connection string
_commonConnection = new EntityConnection(DbContextBase.GenerateConnectionString());
_connection.Open();
_commonTransaction = _connection.BeginTransaction(System.Data.IsolationLevel.Snapshot);
}
T myContextObject = (T)Activator.CreateInstance(typeof(T), new object[1] { _connection });
myContextObject .Database.UseTransaction(_transaction);
return myContextObject;
}
}
The code for generation of connection string is mentioned below:
string GenerateConnectionString()
{
var entityBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
ProviderConnectionString = SharedDatabase.SQLBase.DBconnection + "multipleactiveresultsets=True;" ,
Metadata = #"res://*/;"
};
return entityBuilder ;
}
I can then call the GetDbContext from different places in my function like so
GetById(int id)
{
var dbcontext = MyUnitOfWork.GetDbContext<OrdersDbContext>();
return dbcontext.Orders.Where(.......);
}
GetCustomerInfo( int id)
{
var dbcontext = MyUnitOfWork.GetDbContext<CustomerDbContext>();
return dbcontext.Customer.Where(.......);
}
Because I am using the generic metadata portion , I am getting errors in entity framework "cannot have more than one entity with same name regardless of the namespace"
However if I specify the name of the .csdl/.ssdl file , the connection ( and hence the transaction ) can no more be common and I have to create a connection and Transaction for each DBContext ( this is what i wanted to avoid )
It seems I have hit a block . Is there a way for me to use the same connection without getting the duplicate entities error ? Changing the names of entities to be different is not an option for me as it will be a very time consuming change which I would have to do across 30 + db context / EDMX files with huge production impact.
As seen in Working with Transactions (EF6 Onwards), use the following code to provide an existing transaction to your context :
string GenerateConnectionString()
{
return SharedDatabase.SQLBase.DBconnection + "multipleactiveresultsets=True;";
}
public class MyUnitOfWork
{
SqlConnection _commonConnection;
DbTransaction _commonTransaction;
// Generic function to create DBContext object based on T supplied.
public T GetDbContext<T>()
{
if (_commonConnection == null)
{
// generates a generic connection string
_commonConnection = new SqlConnection(DbContextBase.GenerateConnectionString());
_commonConnection.Open();
_commonTransaction = _connection.BeginTransaction(IsolationLevel.Snapshot);
}
MetadataWorkspace workspace = new MetadataWorkspace(
string.Format("res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl;", typeof(T).Name).Split('|'),
new Assembly[] { Assembly.GetExecutingAssembly() });
var connection = new EntityConnection(workspace, _commonConnection);
T myContextObject = (T)Activator.CreateInstance(typeof(T), new object[] { connection });
myContextObject.Database.UseTransaction(_commonTransaction);
return myContextObject;
}
}

How can I call a SQL Stored Procedure using EntityFramework 7 and Asp.Net 5

For last couple of days I am searching for some tutorials about how to call a Stored Procedure from inside a Web API controller method using EntityFramework 7.
All tutorials I came through are showing it the other way round, i.e. Code First approach. But I already have a database in place and I need to use it to build a Web API. Various business logic are already written as Stored Procedures and Views and I have to consume those from my Web API.
Question 1: Is this at all possible to carry on with Database First approach with EF7 and consume database objects like above?
I installed EntityFramework 6.1.3 to my package by the following NuGet command:
install-package EntityFramework
which adds version 6.1.3 to my project but immediately starts showing me error message (please see the screenshot below). I have no clue about how to resolve this.
I have another test project where in project.json I can see two entries like following:
"EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
"EntityFramework.MicrosoftSqlServer.Design": "7.0.0-rc1-final",
However, when I am searching in Nu-Get package manager, I don;t see this version! Only 6.1.3 is coming up.
My main objective is to consume already written Stored Procedures and Views from an existing database.
1) I do not want to use ADO.Net, rather I would like to use ORM using EntityFramework
2) If EntityFramework 6.1.3 has the ability to call Stored Procs and Views from already existing database, how I can resolve the error (screenshot)?
What is the best practice to achieve this?
I hope that I correctly understand your problem. You have existing STORED PROCEDURE, for example dbo.spGetSomeData, in the database, which returns the list of some items with some fields and you need to provide the data from Web API method.
The implementation could be about the following. You can define an empty DbContext like:
public class MyDbContext : DbContext
{
}
and to define appsettings.json with the connection string to the database
{
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
}
You should use Microsoft.Extensions.DependencyInjection to add MyDbContext to the
public class Startup
{
// property for holding configuration
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
.AddEnvironmentVariables();
// save the configuration in Configuration property
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<MyDbContext>(options => {
options.UseSqlServer(Configuration["ConnectionString"]);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
}
}
Now you can implement your WebApi action as the following:
[Route("api/[controller]")]
public class MyController : Controller
{
public MyDbContext _context { get; set; }
public MyController([FromServices] MyDbContext context)
{
_context = context;
}
[HttpGet]
public async IEnumerable<object> Get()
{
var returnObject = new List<dynamic>();
using (var cmd = _context.Database.GetDbConnection().CreateCommand()) {
cmd.CommandText = "exec dbo.spGetSomeData";
cmd.CommandType = CommandType.StoredProcedure;
// set some parameters of the stored procedure
cmd.Parameters.Add(new SqlParameter("#someParam",
SqlDbType.TinyInt) { Value = 1 });
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
var retObject = new List<dynamic>();
using (var dataReader = await cmd.ExecuteReaderAsync())
{
while (await dataReader.ReadAsync())
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++) {
// one can modify the next line to
// if (dataReader.IsDBNull(iFiled))
// dataRow.Add(dataReader.GetName(iFiled), dataReader[iFiled]);
// if one want don't fill the property for NULL
// returned from the database
dataRow.Add(
dataReader.GetName(iFiled),
dataReader.IsDBNull(iFiled) ? null : dataReader[iFiled] // use null instead of {}
);
}
retObject.Add((ExpandoObject)dataRow);
}
}
return retObject;
}
}
}
The above code just execute using exec dbo.spGetSomeData and use dataRader to read all results and save there in dynamic object. If you would make $.ajax call from api/My you will get the data returned from dbo.spGetSomeData, which you can directly use in JavaScript code. The above code is very transparent. The names of the fields from the dataset returned by dbo.spGetSomeData will be the names of the properties in the JavaScript code. You don't need to manage any entity classes in your C# code in any way. Your C# code have no names of fields returned from the stored procedure. Thus if you would extend/change the code of dbo.spGetSomeData (rename some fields, add new fields) you will need to adjust only your JavaScript code, but no C# code.
DbContext has a Database property, which holds a connection to the database that you can do whatever you want with:
context.Database.SqlQuery<Foo>("exec [dbo].[GetFoo] #Bar = {0}", bar);
However, rather than doing this in your Web Api actions, I would suggest either adding a method to your context or to whatever service/repository that interacts with your context. Then just call this method in your action. Ideally, you want to keep all your SQL-stuff in one place.
Just as the above answer, you could simply use the FromSQL() instead of SqlQuery<>().
context.Set().FromSql("[dbo].[GetFoo] #Bar = {0}", 45);
Using MySQL connector and Entity Framework core 2.0
My issue was that I was getting an exception like fx. Ex.Message = "The required column 'body' was not present in the results of a 'FromSql' operation.".
So, in order to fetch rows via a stored procedure in this manner, you must return all columns for that entity type which the DBSet is associated with, even if you don't need all the data for this specific call.
var result = _context.DBSetName.FromSql($"call storedProcedureName()").ToList();
OR with parameters
var result = _context.DBSetName.FromSql($"call storedProcedureName({optionalParam1})").ToList();
For Database first approach , you have to use Scaffold-DbContext command
Install Nuget packages Microsoft.EntityFrameworkCore.Tools and Microsoft.EntityFrameworkCore.SqlServer.Design
Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
but that will not get your stored procedures. It is still in the works,tracking issue #245
But, To execute the stored procedures, use FromSql method which executes RAW SQL queries
e.g.
var products= context.Products
.FromSql("EXECUTE dbo.GetProducts")
.ToList();
To use with parameters
var productCategory= "Electronics";
var product = context.Products
.FromSql("EXECUTE dbo.GetProductByCategory {0}", productCategory)
.ToList();
or
var productCategory= new SqlParameter("productCategory", "Electronics");
var product = context.Product
.FromSql("EXECUTE dbo.GetProductByName #productCategory", productCategory)
.ToList();
There are certain limitations to execute RAW SQL queries or stored procedures.You can’t use it for INSERT/UPDATE/DELETE. if you want to execute INSERT, UPDATE, DELETE queries, use the ExecuteSqlCommand
var categoryName = "Electronics";
dataContext.Database
           .ExecuteSqlCommand("dbo.InsertCategory #p0", categoryName);

Storing global variables in c#?

I basically have created a class which when a user logs into a website it then queries the database and stores some settings in a List (So I have key/pair values).
The reason for this is because I want to always be able to access these settings without going to the database again.
I put these in a class and loop through the fields via a SQL query and add them to the list.
How can I then access these variables from another part of the application? or is there a better way to do this? I'm talking server side and not really client side.
Here is an example of what I had at the moment:
public static void createSystemMetaData()
{
string constring = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;
SqlConnection sql = new SqlConnection(constring);
sql.Open();
SqlCommand systemMetaData = new SqlCommand("SELECT * FROM SD_TABLES", sql);
//Set Modules
using (SqlDataReader systemMetaDataReader = systemMetaData.ExecuteReader())
{
while (systemMetaDataReader.Read())
{
var name = systemMetaDataReader.GetOrdinal("Sequence").ToString();
var value = systemMetaDataReader.GetOrdinal("Property").ToString();
var Modules = new List<KeyValuePair<string, string>>();
Modules.Add(new KeyValuePair<string, string>(name, value));
}
}
}
Thanks
Any static properties of a class will be preserved for the lifetime of the application pool, assuming you're using ASP.NET under IIS.
So a very simple class might look like:
public static class MyConfigClass
{
public static Lazy<Something> MyConfig = new Lazy<Something>(() => GetSomethings());
public static Something GetSomethings()
{
// this will only be called once in your web application
}
}
You can then consume this by simply calling
MyConfigClass.MyConfig.Value
For less users you can go with the SessionState as Bob suggested,however with more users you might need to move to a state server or load it from Data Base each time.
As others have pointed out, the risk of holding these values in global memory is that the values might change. Also, global variables are a bad design decision as you can end up with various parts of your application reading and writing to these values, which makes debugging problems harder than it need be.
A commonly adopted solution is to wrap your database access inside a facade class. This class can then cache the values if you wish to avoid hitting the database for each request. In addition, as changes are routed through the facade too, it knows when the data has changed and can empty its cache (forcing a database re-read) when this occurs. As an added bonus, it becomes possible to mock the facade in order to test code without touching the database (database access is notoriously difficult to unit test).
From the looks of things you are using universal values irrespective of users so an SqlCacheDependency would be useful here:
Make sure you setup a database dependency in web.config for the name Test
public static class CacheData {
public static List<KeyValuePair<string,string>> GetData() {
var cache = System.Web.HttpContext.Current.Cache;
SqlCacheDependency SqlDep = null;
var modules = Cache["Modules"] as List<KeyValuePair<string,string>>;
if (modules == null) {
// Because of possible exceptions thrown when this
// code runs, use Try...Catch...Finally syntax.
try {
// Instantiate SqlDep using the SqlCacheDependency constructor.
SqlDep = new SqlCacheDependency("Test", "SD_TABLES");
}
// Handle the DatabaseNotEnabledForNotificationException with
// a call to the SqlCacheDependencyAdmin.EnableNotifications method.
catch (DatabaseNotEnabledForNotificationException exDBDis) {
SqlCacheDependencyAdmin.EnableNotifications("Test");
}
// Handle the TableNotEnabledForNotificationException with
// a call to the SqlCacheDependencyAdmin.EnableTableForNotifications method.
catch (TableNotEnabledForNotificationException exTabDis) {
SqlCacheDependencyAdmin.EnableTableForNotifications("Test", "SD_TABLES");
}
finally {
// Assign a value to modules here before calling the next line
Cache.Insert("Modules", modules, SqlDep);
}
}
return modules;
}

Using mini-profiler and MVC3 - profiling connections from model class in separate dll

I've got an MVC3 project and one of the models is built as a separate class library project, for re-use in other applications.
I'm using mini-profiler and would like to find a way to profile the database connections and queries that are made from this class library and return the results to the MVC3 applciation.
Currently, in my MVC3 app, the existing models grab a connection using the following helper class:
public class SqlConnectionHelper
{
public static DbConnection GetConnection()
{
var dbconn = new SqlConnection(ConfigurationManager.ConnectionStrings["db"].ToString());
return new StackExchange.Profiling.Data.ProfiledDbConnection(dbconn, MiniProfiler.Current);
}
}
The external model can't call this function though, because it knows nothing of the MVC3 application, or of mini-profiler.
One way I thought of would be to have an IDbConnection Connection field on the external model and then pass in a ProfiledDbConnection object to this field before I call any of the model's methods. The model would then use whatever's in this field for database connections, and I should get some profiled results in the MVC3 frontend.
However, I'm not sure if this would work, or whether it's the best way of doing this. Is there a better way I'm missing?
ProfiledDbConnection isn't dapper: it is mini-profiler. We don't provide any magic that can take over all connection creation; the only thing I can suggest is to maybe expose an event in your library that can be subscribed externally - so the creation code in the library might look a bit like:
public static event SomeEventType ConnectionCreated;
static DbConnection CreateConnection() {
var conn = ExistingDbCreationCode();
var hadler = ConnectionCreated;
if(handler != null) {
var args = new SomeEventArgsType { Connection = conn };
handler(typeof(YourType), args);
conn = args.Connection;
}
return conn;
}
which could give external code the chance to do whatever they want, for example:
YourType.ConnectionCreated += (s,a) => {
a.Connection = new StackExchange.Profiling.Data.ProfiledDbConnection(
a.Connection, MiniProfiler.Current);
};

Categories