1

I´ve searched for some topics, but did not find a concrete solution to my problem.

My app is a c# commercial app. I´m using EF 4.1 database first. I generate the model from a development database connection and that create a Model.edmx file and all EF stuff pretty fine.

My goal is to deliver an appplication to the customer and let him freely create the database and database user at his own. Do do it, at runtime I would get the username, password, database connection and schema name parameters to connect to customer database. In that way, to deploy the application all I need is to ask the customer to create a database and add the database parameters to the app config file.

So, myy goal is to change the connection string and schema parameter at runtime, without changing att all the auto generated edmx file, not touching the VS generated code.

I have looked around and found:

For EF earlier versions:

Changing schema name on runtime - Entity Framework

http://efmodeladapter.codeplex.com

All other posts are going around that. I tryed even to use the first post code with no success.

But I´ve seen that EF 4.1 comes with better support tools to do it, but I could not find references or examples to it. It´s important not to change the auto generated code from VS.

I´m pretty new to EF, so I would like to ask for help on accomplish the following tasks: a) Change connection string at runtime adding my username, password and database server/port parameters b) Change database schema

I´m using Oracle as a database server (that makes things worst as Oracle mixes schema and users together).

Community
  • 1
  • 1
Mendes
  • 17,489
  • 35
  • 150
  • 263

1 Answers1

4

Actually, I needed solution for this too. I quickly whipped up solution, which works nicely. I didn't find much information about this in Internet, so I am not sure about the "EF 4.1 comes with better support tools to do it".

The concrete example "Changing schema name on runtime - Entity framework" didn't entirely work for me, however with some minor modifications I got it working.

Here is a DatabaseUtils class that can do it:

internal static class DatabaseUtils
{
    /// <summary>
    /// Builds the connection string for Entity framework.
    /// </summary>
    /// <returns></returns>
    public static EntityConnection BuildConnection(BuildConnectionParams buildConnectionParams)
    {
        var sqlBuilder = new SqlConnectionStringBuilder
            {
                DataSource = buildConnectionParams.ServerName,
                InitialCatalog = buildConnectionParams.DatabaseName,
                IntegratedSecurity = true
            };

        var providerString = sqlBuilder.ToString();
        var entityBuilder = new EntityConnectionStringBuilder
        {
            Provider = buildConnectionParams.ProviderName,
            ProviderConnectionString = providerString,
            Metadata = string.Format(@"res://*/{0}.csdl|
                        res://*/{0}.ssdl|
                        res://*/{0}.msl", buildConnectionParams.ModelName)
        };

        return CreateConnection(buildConnectionParams.SchemaName, entityBuilder, buildConnectionParams.ModelName);
    }


    /// <summary>
    /// Creates the EntityConnection, based on new schema & existing connectionString
    /// </summary>
    /// <param name="schemaName">Name of the schema.</param>
    /// <param name="connectionBuilder"></param>
    /// <param name="modelName">Name of the model.</param>
    /// <returns></returns>
    private static EntityConnection CreateConnection(string schemaName, EntityConnectionStringBuilder connectionBuilder, string modelName)
    {
        Func<string, Stream> generateStream =
            extension => Assembly.GetExecutingAssembly().GetManifestResourceStream(string.Concat(modelName, extension));

        Action<IEnumerable<Stream>> disposeCollection = streams =>
        {
            if (streams == null)
                return;

            foreach (var stream in streams.Where(stream => stream != null))
                stream.Dispose();
        };

        var conceptualReader = generateStream(".csdl");
        var mappingReader = generateStream(".msl");
        var storageReader = generateStream(".ssdl");

        if (conceptualReader == null || mappingReader == null || storageReader == null)
        {
            disposeCollection(new[] { conceptualReader, mappingReader, storageReader });
            return null;
        }

        var storageXml = XElement.Load(storageReader);

        foreach (var entitySet in storageXml.Descendants())
        {
            var schemaAttribute = entitySet.Attributes("Schema").FirstOrDefault();
            if (schemaAttribute != null)
                schemaAttribute.SetValue(schemaName);
        }

        storageXml.CreateReader();

        var workspace = new MetadataWorkspace();

        var storageCollection = new StoreItemCollection(new[] { storageXml.CreateReader() });
        var conceptualCollection = new EdmItemCollection(new[] { XmlReader.Create(conceptualReader) });
        var mappingCollection = new StorageMappingItemCollection(conceptualCollection, 
                                                                storageCollection,
                                                                new[] { XmlReader.Create(mappingReader) });

        workspace.RegisterItemCollection(conceptualCollection);
        workspace.RegisterItemCollection(storageCollection);
        workspace.RegisterItemCollection(mappingCollection);

        var connection = DbProviderFactories.GetFactory(connectionBuilder.Provider).CreateConnection();
        if (connection == null)
        {
            disposeCollection(new[] { conceptualReader, mappingReader, storageReader });
            return null;
        }

        connection.ConnectionString = connectionBuilder.ProviderConnectionString;
        return new EntityConnection(workspace, connection);
    }
}

Usage:

/// <summary>
/// Initializes a new instance of the <see cref="DynamicAQDContext"/> class.
/// </summary>
public DynamicAQDContext()
{
    var entityConnection = DatabaseUtils.BuildConnection(new BuildConnectionParams
    {
        ProviderName = "System.Data.SqlClient",
        ServerName = "localhost\\",
        DatabaseName = "",
        ModelName = "YOURMODEL",
        SchemaName = "SCHEMA"
    });

    if(entityConnection == null)
        throw new Exception("Can't create EntityConnection");

    _entities = new LINKEntities(entityConnection);
}

more info can be found here: http://bestplayah.com/entity-framework-dynamic-schema-changes-using-database-first-approach/

Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
  • How to you associate the newly created _entities with these ones that EF created for you in the model (in case you use Database first) ? – Mendes Mar 06 '14 at 23:19
  • What do you mean newly created entities? There is no "newly created entities". Create a partial class for your EF entities that can initialize your entities from "EntityConnection" class. You associate it with using partial classes that is part of the same EF entity class. – Erti-Chris Eelmaa Mar 07 '14 at 10:00
  • @Erti-Chris - Have you by chance updated CreateConnection to use metadata loading delegates as the warning from using RegisterItemCollection suggests after also saying RegisterItemCollection is an obsolete method call? – Dave Jul 18 '14 at 13:54
  • Seems there is a redundant "storageXml.CreateReader();" before creating the new MetadataWorkspace. – Dave Oct 27 '14 at 15:59
  • Excellent answer, have been looking for this all over the place. There are several answers for Code-First multi-tenancy but this I think is the only one I have found which does it for Database/Model First, other than a messy solution with interceptors. Only change I would suggest in above code is removing the extra "storageXml.CreateReader()" as suggested by @Dave and to use "var workspace = new MetadataWorkspace(()=>conceptualCollection, ()=>storageCollection, ()=>mappingCollection);" instead of the RegisterItem calls which are obsolete – Shailesh Jan 05 '15 at 20:22