I have an Asp.Net MVC 4 project using Entity Framework 5.0.
For some reason my DefaultConnectionFactory is not being called when I execute EF code from a Threadpool thread.
I setup my connection factory in the Application_Start event of my Asp.Net MVC project:
Database.DefaultConnectionFactory = new WebConnectionFactory();
This runs successfully on all data access code so far.
I have a Parallel.ForEach loop with a call to some EF code. For some reason the DefaultConnectionFactory is never hit when called. This factory is critical for my code to execute, so the call fails (it uses its default connection string).
Any ideas why this DefaultConnectionFactory would not be called when run on a new thread?
You need to get the connection that you built for each call that you are wanting to use. For example using the following code.
private static void UsingCustomConnection()
{
using (var conn = Database.DefaultConnectionFactory.CreateConnection("YourDbName"))
{
using (var context = new YourContext(conn))
{
context.Destinations.Add(new Destination {Name = "Colorado"});
context.SaveChanges();
}
}
}
You will need to setup this in YourContext
public YourContext(DbConnection connection)
: base(connection, contextOwnsConnection: false)
{
}
Related
I am writing a simple query to get all the data from a table in a .net core project. When using ToListAsync(), the process crashed without any exceptions thrown. And after changing to ToList(), the process run successfully. I am really curious about the reason behind.
The EF-Core version is 2.2.6.
The same method using TolistAsync(), failed.
public async Task<ICollection<TariffType>> GetAllTariffTypes()
{
using(var dbContext = _dbContextFactory.CreateDbContext(_appConfig.DbContextConnectionString))
{
var tariffTypes = await dbContext.TariffType.ToListAsync();
return tariffTypes;
}
}
The method using ToList(), it worked.
public ICollection<TariffType> GetAllTariffTypes()
{
using(var dbContext = _dbContextFactory.CreateDbContext(_appConfig.DbContextConnectionString))
{
var tariffTypes = dbContext.TariffType.ToList();
return tariffTypes;
}
}
I have an Entity Framework Core database defined in a separate assembly, using the IDesignTimeDbContextFactory<> pattern (i.e., I define a class, derived from IDesignTimeDbContextFactory, which has a method called CreateDbContext that returns an instance of the database context).
Because the application of which the EF Core database is a part utilizes AutoFac dependency injection, the IDesignTimeDbContextFactory<> factory class creates an AutoFac container in its constructor, and then resolves the DbContextOptionsBuilder<>-derived class which is fed into the constructor for the database context (I do this so I can control whether a local or an Azure-based SqlServer database is targeted, based on a config file setting, with passwords stored in an Azure KeyVault):
public class TemporaryDbContextFactory : IDesignTimeDbContextFactory<FitchTrustContext>
{
private readonly FitchTrustDBOptions _dbOptions;
public TemporaryDbContextFactory()
{
// OMG, I would >>never<< have thought to do this to eliminate the default logging by this
// deeply-buried package. Thanx to Bruce Chen via
// https://stackoverflow.com/questions/47982194/suppressing-console-logging-by-azure-keyvault/48016958#48016958
LoggerCallbackHandler.UseDefaultLogging = false;
var builder = new ContainerBuilder();
builder.RegisterModule<SerilogModule>();
builder.RegisterModule<KeyVaultModule>();
builder.RegisterModule<ConfigurationModule>();
builder.RegisterModule<FitchTrustDbModule>();
var container = builder.Build();
_dbOptions = container.Resolve<FitchTrustDBOptions>() ??
throw new NullReferenceException(
$"Could not resolve {typeof(FitchTrustDBOptions).Name}");
}
public FitchTrustContext CreateDbContext( string[] args )
{
return new FitchTrustContext( _dbOptions );
}
}
public class FitchTrustDBOptions : DbContextOptionsBuilder<FitchTrustContext>
{
public FitchTrustDBOptions(IFitchTrustNGConfigurationFactory configFactory, IKeyVaultManager kvMgr)
{
if (configFactory == null)
throw new NullReferenceException(nameof(configFactory));
if (kvMgr == null)
throw new NullReferenceException(nameof(kvMgr));
var scannerConfig = configFactory.GetFromDisk()
?? throw new NullReferenceException(
"Could not retrieve ScannerConfiguration from disk");
var dbConnection = scannerConfig.Database.Connections
.SingleOrDefault(c =>
c.Location.Equals(scannerConfig.Database.Location,
StringComparison.OrdinalIgnoreCase))
?? throw new ArgumentOutOfRangeException(
$"Cannot find database connection information for location '{scannerConfig.Database.Location}'");
var temp = kvMgr.GetSecret($"DatabaseCredentials--{dbConnection.Location}--Password");
var connString = String.IsNullOrEmpty(dbConnection.UserID) || String.IsNullOrEmpty(temp)
? dbConnection.ConnectionString
: $"{dbConnection.ConnectionString}; User ID={dbConnection.UserID}; Password={temp}";
this.UseSqlServer(connString,
optionsBuilder =>
optionsBuilder.MigrationsAssembly(typeof(FitchTrustContext).GetTypeInfo().Assembly.GetName()
.Name));
}
}
Needless to say, while this provides me with a lot of flexibility (I can switch from local to cloud database just by changing a single config parameter, and any required passwords are reasonably securely stored in the cloud), it can trip up the add-migration commandlet if there's a bug in the code (e.g., the wrong name of a configuration file).
To debug those kinds of problems, I've often had to resort to outputting messages to the Visual Studio output window via diagnostic WriteLine calls. That strikes me as pretty primitive (not to mention time-consuming).
Is there a way to attach a debugger to my code that's called by add-migration so I can step thru it, check values, etc? I tried inserting a Launch() debugger line in my code, but it doesn't work. It seems to throw me into add-manager codebase, for which I have no symbols loaded, and breakpoints that I try to set in my code show up as the empty red circle: they'll never be hit.
Thoughts and suggestions would be most welcome!
Add Debugger.Launch() to the beginning of the constructor to launch the just-in-time debugger. This lets you attach VS to the process and debug it like normal.
I'm a SQL guy who's tinkering with Web API and Entity Framework 6 and I keep receiving the error "The operation cannot be completed because the DbContext has been disposed" when I my code is:
namespace DataAccessLayer.Controllers
{
public class CommonController : ApiController
{
[Route("CorrespondenceTypes")]
[HttpGet]
public IQueryable GetCorrespondenceTypes()
{
using (var coreDB = new coreEntities())
{
var correspondenceType = coreDB.tblCorrespondenceTypes.Select(cor => new { cor.CorrespondenceTypeName });
return correspondenceType;
}
}
}
}
But if change my code around a little and try this it works:
namespace DataAccessLayer.Controllers
{
public class CommonController : ApiController
{
readonly coreEntities coreDB = new coreEntities();
[Route("CorrespondenceTypes")]
[HttpGet]
public IQueryable GetCorrespondenceTypes()
{
var correspondenceType = coreDB.tblCorrespondenceTypes.Select(cor => new { cor.CorrespondenceTypeName });
return correspondenceType;
}
}
}
My question is why does the second one work but not the first? Is it better practice to have a global connection string or call DBContext explicitly each time?
Your are getting error because you are returning the IQueryable for which Entity framework has yet not executed the query and DbContext has been disposed when that query needs to be executed.
Remember Entity framework will not execute query until collection is initialized or any method that does not support deferred execution. Visit this link for list of Linq deferred execution supported method.
why does the second one work but not the first?
In first code snippet you are returning an instance of IQuerable which has not executed DbQuery and then after it just fires dispose on your context (coreDB). So then after whenever your code iterate over the collection it tries to fire DbQuery but finds that context has already been destroyed so you are getting an error.
In second case when ever you are iterating over the collection coreDB context must be alive so you are not getting an error.
Is it better practice to have a global connection string or call DBContext explicitly each time?
Answer to this question is based on developers taste or his own comforts. You can use your context wrapped within using statements as below:
public IList GetCorrespondenceTypes()
{
using (var coreDB = new coreEntities())
{
var correspondenceType = coreDB.tblCorrespondenceTypes.Select(cor => new { cor.CorrespondenceTypeName });
return correspondenceType.ToList();
}
}
As shown in above code snippet if you would use ToList before returning it would execute query before your coreDB got destroyed. In this case you will have to make sure that you returned materialized response (i.e. returned response after executing the DbQuery).
Note: I have noticed most of the people choose the second way. Which targets context as an instance field or property.
In Entity framework, I have implemented generic repository and unit of work pattern.
Below is related stuff from unit of work:
public IRepository<TEntity, TKey> GetRepository<TEntity, TKey>() where TEntity : class
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
string key = String.Format("{0}|{1}", typeof(TEntity).Name, typeof(TKey).Name);
if (_repositories.ContainsKey(key))
{
return (IRepository<TEntity, TKey>)_repositories[key];
}
Type repositoryType = typeof(Repository<TEntity, TKey>);
_repositories.Add(key, Activator.CreateInstance(repositoryType, _dataContext));
return (IRepository<TEntity, TKey>)_repositories[key];
}
From Manager layer, entity framework is call as below:
IRepository<tablenameEntity int> _tableEntityRepository = _unitOfWork.GetRepository<tablenameEntity, int>();
Error is as follow:
An error occurred while starting a transaction on the provider connection. See the inner exception for details.
{"New transaction is not allowed because there are other threads running in the session."}.
Actually, I just did as below,
remove stored procedure and function from model browser and added procedure and function again.
run custom tool on edmx file.
It just work out.
I am not sure, what was the issue. did that before also, but it was not work.. now, it works and not produce after that.
Thanks
I am attempting to use Entity Framework code based migrations with my web site. I currently have a solution with multiple projects in it. There is a Web API project which I want to initialize the database and another project called the DataLayer project. I have enabled migrations in the DataLayer project and created an initial migration that I am hoping will be used to create the database if it does not exist.
Here is the configuration I got when I enabled migrations
public sealed class Configuration : DbMigrationsConfiguration<Harris.ResidentPortal.DataLayer.ResidentPortalContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(Harris.ResidentPortal.DataLayer.ResidentPortalContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
The only change I made to this after it was created was to change it from internal to public so the WebAPI could see it and use it in it's databaseinitializer. Below is the code in the code in the Application_Start that I am using to try to initialize the database
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
If I run this whether or not a database exists I get the following error
Directory lookup for the file "C:\Users\Dave\Documents\Visual Studio 2012\Projects\ResidentPortal\Harris.ResidentPortal.WebApi\App_Data\Harris.ResidentPortal.DataLayer.ResidentPortalContext.mdf" failed with the operating system error 2(The system cannot find the file specified.).
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.
It seems like it is looking in the totally wrong place for the database. It seems to have something to do with this particular way I am initializing the database because if I change the code to the following.
Database.SetInitializer(new DropCreateDatabaseAlways<ResidentPortalContext>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
The database will get correctly created where it needs to go.
I am at a loss for what is causing it. Could it be that I need to add something else to the configuration class or does it have to do with the fact that all my migration information is in the DataLayer project but I am calling this from the WebAPI project?
I have figured out how to create a dynamic connection string for this process. You need to first add this line into your EntityFramework entry on Web or App.Config instead of the line that gets put there by default.
<defaultConnectionFactory type="<Namespace>.<ConnectionStringFacotry>, <Assembly>"/>
This tells the program you have your own factory that will return a DbConnection. Below is the code I used to make my own factory. Part of this is a hack to get by the fact that a bunch of programmers work on the same set of code but some of us use SQL Express while others use full blown SQL Server. But this will give you an example to go by for what you need.
public sealed class ResidentPortalConnectionStringFactory: IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["PortalDatabase"].ConnectionString);
//save off the original catalog
string originalCatalog = builder.InitialCatalog;
//we're going to connect to the master db in case the database doesn't exist yet
builder.InitialCatalog = "master";
string masterConnectionString = builder.ToString();
//attempt to connect to the master db on the source specified in the config file
using (SqlConnection conn = new SqlConnection(masterConnectionString))
{
try
{
conn.Open();
}
catch
{
//if we can't connect, then append on \SQLEXPRESS to the data source
builder.DataSource = builder.DataSource + "\\SQLEXPRESS";
}
finally
{
conn.Close();
}
}
//set the connection string back to the original database instead of the master db
builder.InitialCatalog = originalCatalog;
DbConnection temp = SqlClientFactory.Instance.CreateConnection();
temp.ConnectionString = builder.ToString();
return temp;
}
}
Once I did that I coudl run this code in my Global.asax with no issues
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
using (ResidentPortalUnitOfWork temp = new ResidentPortalUnitOfWork())
{
temp.Context.Database.Initialize(true);
}