I saw a number of related questions but none of them is exactly what I am looking for.
We are using one database and need to have separated edmx files, with different Model and ObjectContext class names. This results in having multiple connections string, which are different only in metadata part.
For now I ended up doing this:
Web.config
<connectionStrings configSource="connectionStrings.config"></connectionStrings>
connectionStrings.config
<connectionStrings>
<add name="Entities" connectionString="metadata=res://*/Entity.Model.csdl|
res://*/Entity.Model.ssdl|res://*/Entity.Model.msl;
provider=CONNECTION STRING DATA GOES HERE"/>
<add name="TwoEntities" connectionString="metadata=res://*/TwoEntity.TwoModel.csdl|
res://*/TwoEntity.TwoModel.ssdl|res://*/TwoEntity.TwoModel.msl;
provider=EXACTLY THE SAME CONNECTION STRING DATA GOES HERE"/>
</connectionStrings>
In my ObjectContext derived classes I do have the default generated constructors:
public Entities()
: base("name=Entities", "Entities")
{
}
and
public TwoEntities()
: base("name=TwoEntities", "TwoEntities")
{
}
What would be very nice is not to have two connection strings in .config file, but share the same connection sting from this file and somehow override the metadata part of it in each class.
Any suggestions on how to do this?
Yes it is possible but you cannot use EF connection string from configuration. You must built connection string manually in the application. ObjectContext supports multiple overloaded constructors. One of the is accepting EntityConnection. EntityConnection in turn can be constructed from MetadataWorkspace (class created from your EF metadata files) and DbConnection. You can add custom factory method to your derived context which will build MetadataWorkspace and DbConnection from shared DB connection string and pass them in EntityConnection.
public static Entities GetContext(string connenctionString)
{
MetadataWorkspace workspace = GetWorkspace(); // This should handle workspace retrieval
DbConnection connection = new SqlConnection(connectionString); // Connection must not be openned
EntityConnection entConnection = new EntityConnection(workspace, entConnection);
return new Entities(entConnection);
}
private Entities(EntityConnextion entConnection) : base(entConnection)
{ }
You will use this factory method instead of constructor. Make sure that GetWorkspace creates MetadataWorkspace only once per metadata set and store it internally for the lifetime of the application. Its creation is time consuming.
Related
To implement a plug-in system in a AspNet Core Mvc app, I would like a non-generic method to add a data context from a list of assemblies loaded dynamically at runtime, taking a Type parameter like this:
foreach(Type tp in pluginContexts)
{
services.AddDbContext(tp, options => ...);
}
instead of the usual
services.AddDbContext<PluginDataContext>(options => ...);
That's because for dynamically loaded assemblies, I can not provide the TContext type parameter to the AddDbContextPool method, since that's statically compiled and not available at compile time.
Background
This is for a larger Asp.Net Core MVC app. The plugins must be able to both access the main database of the overall app and a separate database of their own.
Plugin assemblies, containing domain code and their private database context are to be dropped in a specified directory.
The main app loads the plugin assembly dynamically upon startup.
The way I am solving this now is to have each controller get the IConfiguration instance injected, obtain the appropriate connection string from the config, and the database context is instantiated in the controller. Not so nice but does work.
One can easily inject a general class into the Services collection with AddScoped<>, and then use it as a sort of ServiceLocator - however, that is considered an antipattern.
I looked into the source code for AddDbContext but honestly I am lost.
Is there any simple way to achieve this?
Solved it by creating an extensibility point in the plugin assembly.
Define an interface in the main app, which all plugins must implement.
public interface IPluginContextRegistration
{
void RegisterContext(ref IServiceCollection services, Action<DbContextOptionsBuilder> optionsAction);
String GetDatabaseName();
}
Create a class implementing this interface (in the plugin). It has access to the type of its private database context, thus can use the generic AddDbContext method:
public class DatabaseRegistration : IPluginContextRegistration
{
public void RegisterContext(ref IServiceCollection services, Action<DbContextOptionsBuilder> optionsAction)
{
services.AddDbContext<Test1DbContext>(optionsAction);
}
public String GetDatabaseName()
{
return "test-plugin-db";
}
}
Then in the main app ASP.Net Startup.cs file, add following code, which calls the RegisterContext() method for each plugin. For example, if you want to use Sql Server:
void RegisterPluginDbContexts(ref IServiceCollection services, List<Assembly> assemblyList)
{
IEnumerable<IPluginContextRegistration> registrars = new List<IPluginContextRegistration>();
foreach (Assembly assembly in assemblyList)
{
registrars = registrars.Concat(GetClassInstances<IPluginContextRegistration>(assembly));
}
foreach (var reg in registrars)
{
String name = reg.GetDatabaseName();
String connStr = Configuration.GetConnectionString(name);
reg.RegisterContext(ref services, options => options.UseSqlServer(connStr));
}
}
For completeness - the method "GetClassInstances" is just a helper method using Reflection to obtain an instance of classes implementing the specified interface.
So it's simple after all - no need for re-writing framework code .
I have an existing EF Core 2.2 DbContext that works fine in an ASPNET Core application as well as LinqPad. Now I am trying to add it to an Azure function. In both ASPNET and the Azure function I am using dependency injection.
The DbContext class has three constructors - an empty one, one that takes a connection string and another that takes a DbOptionsBuilder instance. The ASPNET Core app seems to invoke the one that takes the DbOptionsBuilder instance while LinqPad uses the one that takes the connection string. As I said, both of these work fine.
The Azure function app tries to use the one that takes a string, but it passes null instead of a value. This causes an error later saying that a provider hasn't been configured.
I can force the function app to use the DbOptionsBuilder constructor by removing the one that takes a string. When I do this the function app works fine. However, I can no longer use the context in LinqPad if I do.
My question is, first, how can I make the Azure function call the appropriate constructor without removing the others? Second, and less importantly, why the different behavior between the ASPNET runtime and the Azure function runtime?
EDIT
I am only running the AZ function locally at this point so it is reading the connection string from 'local.settings.json' file. This part is working.
Here is the Startup.Configure method of the function project.
public class Startup : FunctionsStartup
{
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the DI container.
/// </summary>
/// <param name="builder">The function host builder</param>
public override void Configure(IFunctionsHostBuilder builder)
{
// Add database context
string env = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT");
string connectionString = Environment.GetEnvironmentVariable($"ConnectionStrings:{env}");
builder.Services.AddDbContext<FullContext>(x => x.UseSqlServer(connectionString), ServiceLifetime.Transient);
}
}
As I said, it is reading the connection string and appears to pass it to the AddDbContext method. But something is going wrong somewhere.
EDIT 2
Here are the three constructors from my DbContext subclass. Nothing special. Also including the OnConfiguring method.
public FullContext() { }
public FullContext(string connectionString)
{
ConnectionString = connectionString;
}
public FullContext(DbContextOptions<FullContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (ConnectionString != null)
optionsBuilder.UseSqlServer(ConnectionString);
base.OnConfiguring(optionsBuilder);
}
EDIT 3
After reviewing the link #Jack Jia suggested I tried the following.
First, I create my own instance of the DbContextOptionsBuilder and specify the provider and connection string.
var options = new DbContextOptionsBuilder<FullContext>();
options.UseSqlServer(connectionString);
I then try to force the DI service to use these options. However, this fails when using the AddDbContext method - it still tries to call the wrong constructor using a null string as the parameter.
In other words, this fails:
builder.Services.AddDbContext<FullContext>(x => new FullContext(options.Options), ServiceLifetime.Transient);
but this seems to work:
builder.Services.AddTransient<FullContext>(x => new FullContext(options.Options));
Assuming I am understanding the docs correctly both calls should be forcing the DI service to use the constructor taking an DbContextOptions parameter. But this doesn't seem to be the case.
You may refer to: Service registration methods
If there are multiple constructors, you can specify one as following:
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
For example:
// Constructor1
builder.Services.AddScoped<IMyDep>(sp => new MyDep());
// Constructor2
builder.Services.AddScoped<IMyDep>(sp => new MyDep("A string!"));
// Constructor3
builder.Services.AddScoped<IClass1, Class1>();
builder.Services.AddScoped<IMyDep>(sp =>
{
IClass1 class1 = sp.GetRequiredService<IClass1>();
//class1.doSomething(...);
return new MyDep(class1);
});
So, you do not need to change the DbContext class, just specifically use different constructors in different apps.
Where are storing the connections string value?
I would check the source. Out of the box asp.net core has the a application.settings.json file configured for injection. AZ Function does not do this.
If you are using an application.settings.json then you have to configure it to load settings from that file.
Here a sample how to load a config file in DI that allows you to have similar access to the content as in asp.net core:
var config = new ConfigurationBuilder().SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("application.settings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
builder.Services.AddSingleton<IConfiguration>(config);
And getting a value in the Configure method:
string SqlConnectionString = config.GetConnectionString("SqlConnectionString");
This is done in the public override void Configure(IFunctionsHostBuilder builder).
Here is how to use DI in Azure Functions.
The other possibility I can think of is Azure Key Vault or environment variables.
I am looking to pick up MVC after previously working with web forms and I have been doing the movie tutorial over at asp.net but I am having an issue that is utterly confusing.
I have 2 connection string in my web config:
<connectionStrings>
<add name="MovieDBContext"
connectionString="Data Source=(LocalDB)\(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Movies.mdf;Integrated Security=True"
providerName="System.Data.SqlClient" />
<add name="DefaultConnection" connectionString="Data Source="(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-MvcMovie-20160408044847.mdf;Initial Catalog=aspnet-MvcMovie-20160408044847;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
I then have my context (Note I have tried setting the connection string here too after another answer on stack although I believe it should do it automatically if the context name is the same as the connection string name
public class MovieDBContext : DbContext
{
public MovieDBContext() : base("MovieDBContext"){ }
public DbSet<Movie> Movies { get; set; }
}
Whatever I do it uses the default connection string rather than the MovieDbContext connection string and for the life of me I cant work out why. If I set the data source of the default connection string to the datasource of the moviedbcontext one it works as it should and connects to the right database but I cant work out why it will ONLY use the default one (I have even tried changing the names of the connection string and the context to no avail)
Any help appreciated
Cheers
Try commenting the call to the base constructor in your derived DbContext class and just let the default constructor be called. Be sure that your web.config entry for the connection is in the root web.config file of the application and that it conforms to the expected naming conventions (see further down for more info).
public class MovieDBContext : DbContext
{
//// comment the call to the base constructor
// public MovieDBContext() : base("MovieDBContext"){ }
public DbSet<Movie> Movies { get; set; }
}
The MSDN documentation at https://msdn.microsoft.com/en-us/library/gg679577%28v=vs.103%29.aspx provides information about the convention used to determine the database name.
protected DbContext()
Constructs a new context instance using conventions to create the name of the database to which a connection will be made. By convention the name is the full name (namespace + class name) of the derived context class. For more information on how this is used to create a connection, see the remarks section for DbContext.
I created a database in Azure setting my own custom name. I then created EF 5 code first entities and added migrations. On application startup I called these two lines:
Database.DefaultConnectionFactory = new SqlConnectionFactory(connectionString);
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDataContext, MyConfiguration>());
Connection string is taken straight from Azure:
Server=tcp:xxx.database.windows.net,1433;Database=dbName;User ID=yyy;Password=zzz;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;
On fist call I expected database dbName to be filled with tables according to POCO schema.
But instead a NEW database is generated with the complete namespace name of my context:
MyService.Business.Entity.MyContext
Why will the migration not accept the database name specified in the connection string?
You can specify the Database name or connection string name in the constructor of your DbContext:
public class MyDataContext : DbContext
{
public MyDataContext: base("DbNameOrConntectionStringNameHere")
{
}
}
My experience is that, in the case where the connection string is being passed in code, rather than obtained from app.config, EF is quirky about how it obtains the connection string.
I had to add a class that inherited from IDBContectFactory
public class ContextFactory : IDbContextFactory<Context>
{
public Context Create()
{
var s = (string)AppDomain.CurrentDomain.GetData("ConnectionString");
var context = new Context(s);
return context;
}
}
Also, in order to create a migration, I needed the following in my context class
// uncomment when creating migration - comment out after migration is created
public Context() : base("ConnectionStringName"){}
Where the ConnectionStringName is set up in my app.config.
I am still mystified that I had to do this and have asked about it here
I need to have 2 families of classes (one on server and one on client side) which are identical in data structure but differs in behavior. Also I suppose that these fmailies will be enough big, thus I don't want to implement intermediate level of DTO and transformations into and from it.
I decided to move in following manner: declare shared assembly with declaration of data and services interfaces like these ones:
public interface ITest
{
string Title { get; set; }
int Value { get; set; }
}
public interface IService
{
ITest GetData();
}
Having these declarations I can implement these interfaces on server side for example basing on Entity Framework (data) and WCF (services). On the client side I can use for example Dependency Properties (data) and WCF (service).
When I started trying to implement this, I met several troubes.
First one was about server side of WCF - it simply do not want to work with interfaces as return parameters. Thanks to StackOverflow this issue was resolved like here
.
Next problem is that XML rendered by server side includes qulified assembly name of serialized on the server class.
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetDataResponse xmlns="http://tempuri.org/">
<Test z:Id="1" z:Type="Dist.Server.Model.Test" z:Assembly="Dist.Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="http://schemas.datacontract.org/2004/07/Dist.Server.Model" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<Title z:Id="2">Test</Title>
<Value>123</Value>
</Test>
</GetDataResponse>
</s:Body>
</s:Envelope>
Thus during deserialization on client side there was an attempt to load this type. As this type is inaccessible on client side, I had to implement some kind of type mapping. I found that this is quite easy as NetDataContractSerializer used for serialization supports Binder property. Thus I override this property on client side and return correct value (hardcode in meantime, but it's OK for tests).
public class NetBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName) {
var type = Type.GetType("Client.Test, Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
return type;
}
}
Now I have following picture:
- Server uses NetDataContractSerializer to serialize response. It uses actual value (calss) during serialization instead of type used in declaration of service (interface).
- Client side receives XML and starts deserialization. To resolve type, NetDataContractSerializer calls my Binder that returns correct type.
- NetDataContractSerializer creates instance of correct type and starts loading of its properties.
And here I got a trouble that I don't know how to resolve. Values of properties are not deserialized. It means that instance of class is created correctly (uninitialized instance created through reflection services), but all properties are in their default values (0 or null).
I tried to play with declaration of class on client side: mark it as [Serializable], implement ISerializable, etc., but nohing is helpful. NetDataContractSerializer requires class to be marked as [DataContract] or [Serializable]. First option leaves properties empty, second one causes exceptions like "</Test> is unexpected, expected is bla-bla-bla_Value_Ending_bla-bla-bla".
Does anybody have any suggestions on how to resolve this last step?
I can provide full sources for better understanding, but I don't know ifI can attach them here...
Thanks in advance.
You could have a look at frameworks like AutoMapper that would take care the transformation to and from DTO. This would make your life much easier.
Instead of using an interface why not create a base class containing only the data and inherit it on both sides by sharing the assembly containing this class. Playing around with the ServiceKnownType should help you fix the last issues.
You may also share the same base classes on both sides and implement the specific logic as extension methods.
Seems that problem was solved enough easily. I created own serializer and used it instead of NetDataContractSerializer. Code is quite simple:
public class MySerializer: XmlObjectSerializer
{
public override void WriteStartObject(XmlDictionaryWriter writer, object graph) {
}
public override void WriteObjectContent(XmlDictionaryWriter writer, object graph) {
var formatter = new XmlSerializer(graph.GetType());
formatter.Serialize(writer, graph);
}
public override void WriteEndObject(XmlDictionaryWriter writer) {
}
public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName) {
var realType = Type.GetType("Client.Test, Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); //temporary solution
var formatter = new XmlSerializer(realType);
return formatter.Deserialize(reader);
}
public override bool IsStartObject(XmlDictionaryReader reader) {
return true;//temporary solution
}
}
I checked SOAP that goes from server to client and it's almost the same as NetDataSerializer renders. The only difference is in attribute xmlns="".
Kai, Johann thanksfor your tries.