9

Is it possible to create the entire model on the fly (Database First) approach using entity framework each time a connection string is passed in?

I tried the following:

MetaModel model = new MetaModel();
model.RegisterContext(() => new Model1(connectionString),
new ContextConfiguration()
 {
  ScaffoldAllTables = true
 });

but it keeps throwing me an error

An unhandled exception of type 'System.ArgumentException' occurred in System.Web.DynamicData.dll

Additional information: The context type 'DbContext.Model1' is not supported.

More Info:

I have all the required tables in the database which I don't have control over and if I need any new tables or columns inside old tables then the db guys run the appropriate scripts for me.

I'm trying to create a generic DLL using EF which I can use on various apps 6 including winforms and mvc web apps. I'm trying to figure out what us the best possible way to go about this.

Can I mix codefirst and databasefirst together?

Wouldn't codefirst generate tables for me which I already have?

Each of my app consist of 1 winforms and 1 web app and they share the datasource as e.g.

Test 1 Windows App & Test 1 MVC App = DB 1

Test 2 Windows App & Test 2 MVC App = DB 2

Test 3 Windows App & Test 3 MVC App = DB 3

So I need to pass in the connectionstring to the DBContext. How would my Entities work?

If any more information is required please let me know.

Community
  • 1
  • 1
Izzy
  • 6,740
  • 7
  • 40
  • 84
  • why you are doing that what you want exactly ? why you need the metamodel in code first? – Bassam Alugili Jun 21 '16 at 13:59
  • @BassamAlugili I currently have a dll which I pass in the connection string, the query or sproc and the dll takes care of it. I want to go down the route of EF and I have all the tables in the database so hence I thought maybe it's possible to create a model on the fly – Izzy Jun 22 '16 at 07:06
  • Hi @Code maybe you can refer to [this](http://stackoverflow.com/questions/12364617/ef-5-0-dynamic-connection-string) – Shawn Yan Jun 27 '16 at 08:43
  • @ShawnYan Thank you for the link, as you can see from my code snippet I already pass in the connectionstring but I need to figure how it would the Entities work? e.g. if the connectionstring changes from `A` to `B` and most the tables in the database are different, what happens then? – Izzy Jun 27 '16 at 08:46
  • Your question is unclear. `How would my Entities work?` - your entities would need to be defined at compile time, and if your database model is identical then you don't need to redefine the entities. `Wouldn't codefirst generate tables for me which I already have?` Why would you need to generate tables if your db guys run the appropriate scripts when you ask? – Maarten Jun 27 '16 at 09:20
  • About the exception: check [this](http://stackoverflow.com/a/22016040/261050). – Maarten Jun 27 '16 at 09:22
  • @Maarten Apologies if my question is unclear, For example I'm working with `DB 1` which has `Table 1` I've passed in the connectionstring to EF, how would I query `Table 1`. Would I need to set my entity the entire class and each property and then `DbSet` or something else? The database will be identical until new columns are required/changed etc on new releases. – Izzy Jun 27 '16 at 09:25
  • Wht not have 6 different db-first contexts, all fully populated from their respective databases and use a factory to choose the relevant context by name? – Dave Alperovich Jun 27 '16 at 17:39
  • Would you please add more description about what you need? Currently it's unclear for me what you are asking. I read the question and comments but I couldn't understand what do you want to do with `Table1`. It `Table1` changes in database and a new column add to it, your model in application will not have that column. The model need to be created at compile time then you will be able to write something like `.Where(x=>x.Column1 == value1)`. Currently It seems a confusion between run-time and compile time. Would you describe more about what you mean by Creating Model at Run-time? – Reza Aghaei Jun 27 '16 at 21:56
  • @RezaAghaei What the final achievement is that I want to create a DLL which can be used across multiple apps. Currently they don't use the datasource but eventually they will. So for now I want to create a DLL to avoid duplicate code. I'm confused on how to achieve this. The entities will be available to start of with but if I pass in a different connection to the original one then I need to generate the entities for that connection string in that DLL. – Izzy Jun 28 '16 at 07:38
  • Generating models at run-time doesn't make sense. For each database, you should create models at design-time and build them into reusable dlls. Those dlls can be shared across multiple projects. Also if you want to use those models for multiple instance of a database, simply use [different dynamic connection strings](http://stackoverflow.com/questions/32717832/connecting-to-database-for-multi-tenant-application/32718961#32718961). Also you can create some generic Data Access and Business Logic Layers to be reusable across multiple projects. – Reza Aghaei Jun 28 '16 at 11:05
  • As I mentioned in previous comment, currently the question seems just a confusion between run-time and design-time. I believe designing models at design-time is enough and you don't need to create models at run-time. I hope these comments or the linked answer helps you. Anyway let me know if you can share anything else to clarify the situation and I'll help you if I can :) – Reza Aghaei Jun 28 '16 at 11:08
  • @RezaAghaei Thanks for the information it has definitely helped I think I will most likely go down the route of creating a DLL for each database as you suggested. If you can explain a little bit more about the Data Access and Business Logic Layer I'll appreciate it. – Izzy Jun 28 '16 at 13:20
  • Probably the answer is not what you expected, but at least it would help you to not follow that idea. I believe following that idea would be waste of time :) – Reza Aghaei Jul 03 '16 at 20:13

3 Answers3

3

Technically you can create entity framework models at run-time, at least by creating an assembly on the fly and using apis and attributes that code first works or by creating required xml for model. But you don't need to create model classes at run-time.

In fact creating model classes at run-time is useless, because you create models to work typed both at compile-time and run-time. You create models at because you want to pass a Type1 to a method or write such typed query like .Where(x=>x.SomeFiled == SomeValue).

If you have different instances of application for different customers

If you have different instance of application for different clients, you don't need to do anything specific. Your application has been written ready to use and just use different connection strings in webconfig and appconfig for different customers.

If you have a single instance of application for all customers

In such case, which you have a multi-tenant application, you can simply add an overload to your db context constructor that accepts connection string as input. Then when you need to create an instance of your db context, you can use that overload and inject suitable username, password and database name in the connection string based on your tenant detection strategy.

public partial class SampleDbEntities
{
    public SampleDbEntities(string connectionString) : base(connectionString)
    {
    }
}

It's better to put the new overload in a partial class. Then it will not be touched each time when you update the edmx and .tt template of context run.

For example you can create connection string and context this way:

var connectionTemplate =
    @"metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;" +
    @"provider=System.Data.SqlClient;" +
    @"provider connection string=""data source={0};" +
    @"initial catalog={1};" +
    @"persist security info=True;" +
    @"user id={2};" +
    @"password={3};" +  
    @"MultipleActiveResultSets=True;App=EntityFramework""";

string connection = string.Format(connectionTemplate, 
    @"(localdb)\v11.0", @"SampleDB1", @"user1" , @"password1");

var db = new SampleDbEntities(connection);

Or maybe you want windows authentication, then instead of user id and password, use @"integrated security=True;" +.

You can detect different tenants based on different strategies, including:

  • Url of application (domain and sub domain)
  • Query string or Route values
  • Username

You can give the role of creating suitable context to a class which works based on your tenant strategy.

What should I do if a table in database changed?

Just update your edmx model at design-time and rebuild your application and redistribute it. As mentioned above, if a filed added to a table or a new table added to database and you want to write such typed query in application db.Tabe1.Where(x=>x.Field1==value1), then you need to update model from database and rebuilding your application. Regenerating model at run-time doesn't make sense.

How can I increase productivity?

I know your goal is increasing productivity, but generating models at run-time is not what you are looking for. Instead you can create some generic Data Access Layers and generic Business Logic Layers to increase productivity. For example, if you have lot's of entities which need CRUD operations, you can create a EntityBusiness<TContext, TModel> class and have generic void Create(TModel entity), IList<TModel> GetAll(object key), TModel GetByKey(object key), TModel Update(TModel entity), void DeleteByKey(object key), etc. This way you can simply create an instance of EntityBusiness<SampleDbEntities, Product> or inherit from it. The class contains all behavior for a simple CRUD operation and you can enhance it by adding support for some other useful cases like validations. As an example of creating a generic repository and unit of work, take a look at this article in asp.net mvc site.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Probably the answer was not exactly what you liked to do, but at least it will help you to move in the right direction :) – Reza Aghaei Jul 04 '16 at 13:48
0

Yes, you can specify the connection-string at run-time.

Yes, if a table is modified, then you need to update the C# class. If you use database-first, update the edmx, and save it. If you use code-first, update the C# class manually.

An example.

An entity class

public class Table1 {
    // props...
}

A DbContext

public class MyContext: DbContext {
    public MyContext (string connectionString): base(connectionString) { }
    public DbSet<Table1> Table1 { get; set; }
}

Usage...

string connectionstring1 = "someConnectionString";  // Put this in the app.config
using (var context = new MyContext(connectionstring1)) {
    var table1sFromDb1 = context.Table1.ToList();
}

string connectionstring2 = "someOtherConnectionString"; // Put this in the app.config
using (var context = new MyContext(connectionstring2)) {
    var table1sFromDb2 = context.Table1.ToList();
}
Maarten
  • 22,527
  • 3
  • 47
  • 68
  • I'll be using database first approach throughout, I'll be using 3 database each having their own tables. Using your approach wouldn't I have to create 3 separate DLL one for each DB. What I'm trying to achieve is have one generic DLL which I pass the connectionstring and generate the appropriate Entities, Is that even possible? – Izzy Jun 27 '16 at 10:19
  • @Code Even if you could generate the entities run-time, how are you going to use them from other assemblies/projects at compile-time if the entities are only generated at run-time? Very impractical IMHO. – Maarten Jun 27 '16 at 10:20
  • You do have a point. I might need to re-think this one all over. – Izzy Jun 27 '16 at 10:22
  • What do you think would be the best approach for my scenario? – Izzy Jun 27 '16 at 10:30
  • What advantages are you trying to achieve? I don't see any when you create a 'generic' assembly which you can't use in a generic way since you need to make it specific to each situation. – Maarten Jun 27 '16 at 10:33
  • The plan is to create a generic DLL now because a few months from now all the applications will be using the same data source but for now they want it to support 3 different databases – Izzy Jun 27 '16 at 10:36
0

If you have a single application, the application have to use the minimum set of Tables and Columns (i.e. Entities and Properties).
Generating the model from the database is useless because the application must use only the shared tables and columns.
In your case a good approach could be using CodeFirst (in your case CodeFirst from database) then disable model check (to check model you can still use a unit test).

bubi
  • 6,414
  • 3
  • 28
  • 45