在运行时更改模式名称 - 实体框架

时间:2010-04-18 17:25:16

标签: entity-framework schema runtime

我需要在运行时更改实体的存储架构。 我跟着一篇很棒的帖子,可以在这里找到: http://blogs.microsoft.co.il/blogs/idof/archive/2008/08/22/change-entity-framework-storage-db-schema-in-runtime.aspx?CommentPosted=true#commentmessage

这非常有效,但仅适用于查询,不适用于修改。

知道为什么吗?

6 个答案:

答案 0 :(得分:24)

好吧,我正在互联网上寻找这段代码。最后我必须自己做。它基于Brandon Haynes适配器,但是此功能是您在运行时更改模式所需的全部功能 - 您无需替换自动生成的上下文构造函数。

public static EntityConnection Create(
    string schema, string connString, string model)
{
    XmlReader[] conceptualReader = new XmlReader[]
    {
        XmlReader.Create(
            Assembly
                .GetExecutingAssembly()
                .GetManifestResourceStream(model + ".csdl")
        )
    };
    XmlReader[] mappingReader = new XmlReader[]
    {
        XmlReader.Create(
            Assembly
                .GetExecutingAssembly()
                .GetManifestResourceStream(model + ".msl")
        )
    };

    var storageReader = XmlReader.Create(
        Assembly
            .GetExecutingAssembly()
            .GetManifestResourceStream(model + ".ssdl")
    );
    XNamespace storageNS = "http://schemas.microsoft.com/ado/2009/02/edm/ssdl";

    var storageXml = XElement.Load(storageReader);

    foreach (var entitySet in storageXml.Descendants(storageNS + "EntitySet"))
    {   
        var schemaAttribute = entitySet.Attributes("Schema").FirstOrDefault();
        if (schemaAttribute != null)
        {
            schemaAttribute.SetValue(schema);
        }
    }
    storageXml.CreateReader();

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

    var workspace = new MetadataWorkspace();
    workspace.RegisterItemCollection(conceptualCollection);
    workspace.RegisterItemCollection(storageCollection);
    workspace.RegisterItemCollection(mappingCollection);

    var connectionData = new EntityConnectionStringBuilder(connString);
    var connection = DbProviderFactories
        .GetFactory(connectionData.Provider)
        .CreateConnection();
    connection.ConnectionString = connectionData.ProviderConnectionString;

    return new EntityConnection(workspace, connection);
}

在实例化上下文时,应将结果EntityConnection作为参数传递。您可以修改它,因此所有ssdl模型都由此函数修改,而不仅仅是指定的模型。

答案 1 :(得分:11)

我设法通过使用位于CodePlex(由Brandon Haynes提供)的名为“实体框架运行时模型适配器”的精彩库来解决此问题,可在此处获得: http://efmodeladapter.codeplex.com/

我已经调整了一下,以满足我们的需求,而根本不需要更换设计师代码。

所以,我很好。

非常感谢,特别是布兰登,这是一份了不起的工作!

答案 2 :(得分:5)

我需要从postgres数据库导入数据。它默认使用模式“public”。所以我使用Entity Framework CTP 4“Code first”。它默认使用模式“dbo”。要在运行时更改它,我使用:

public class PublicSchemaContext : DbContext
{
    protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder builder)
    {
        builder.Entity<series_categories>().MapSingleType().ToTable("[public].[series_categories]");
    }

    public DbSet<series_categories> series_categories { get; set; }
}

适用于选择,插入,更新和删除数据。所以接下来的测试通过:

[Test]
        public void AccessToPublicSchema()
        {
            // Select
            var db = new PublicSchemaContext();
            var list = db.series_categories.ToList();
            Assert.Greater(list.Count, 0);
            Assert.IsNotNull(list.First().series_category);

            // Delete
            foreach (var item in db.series_categories.Where(c => c.series_category == "Test"))
                db.series_categories.Remove(item);
            db.SaveChanges();

            // Insert
            db.series_categories.Add(new series_categories { series_category = "Test", series_metacategory_id = 1 });
            db.SaveChanges();

            // Update
            var test = db.series_categories.Single(c => c.series_category == "Test");
            test.series_category = "Test2";
            db.SaveChanges();

            // Delete
            foreach (var item in db.series_categories.Where(c => c.series_category == "Test2"))
                db.series_categories.Remove(item);
            db.SaveChanges();
        }

答案 3 :(得分:2)

本身不是答案,而是跟进Jan Matousek创建[实体连接]方法,显示如何使用DbContext。注意DB是传递给通用存储库的DbContext类型。

 public TxRepository(bool pUseTracking, string pServer, string pDatabase, string pSchema="dbo")
{
    // make our own EF database connection string using server and database names
    string lConnectionString = BuildEFConnectionString(pServer, pDatabase);

    // do nothing special for dbo as that is the default
    if (pSchema == "dbo")
    {
        // supply dbcontext with our connection string
        mDbContext = Activator.CreateInstance(typeof(DB), lConnectionString) as DB;
    }
    else // change the schema in the edmx file before we use it!
    {
        // Create an EntityConnection and use that to create an ObjectContext,
        // then that to create a DbContext with a different default schema from that specified for the edmx file.
        // This allows us to have parallel tables in the database that we can make available using either schema or synonym renames.
        var lEntityConnection = CreateEntityConnection(pSchema, lConnectionString, "TxData");

        // create regular ObjectContext
        ObjectContext lObjectContext = new ObjectContext(lEntityConnection);

        // create a DbContext from an existing ObjectContext
        mDbContext = Activator.CreateInstance(typeof(DB), lObjectContext, true) as DB;
    }

    // finish EF setup
    SetupAndOpen(pUseTracking);
}

答案 4 :(得分:0)

我能够将Jan Matousek的解决方案转换为使用实体框架6在vb.net 2013中工作。我还将尝试解释如何在vb.net中使用该代码。

我们有一个JD Edwards数据库,它为每个环境使用不同的Schema(TESTDTA,CRPDTA,PRODDTA)。这使得在环境之间切换变得麻烦,因为如果要更改环境,则必须手动修改.edmx文件。

第一步是创建一个允许您将值传递给实体的构造函数的分部类,默认情况下它使用配置文件中的值。

Partial Public Class JDE_Entities
    Public Sub New(ByVal myObjectContext As ObjectContext)
        MyBase.New(myObjectContext, True)
    End Sub
End Class

接下来创建将在内存中修改商店架构.ssdl文件的函数。

 Public Function CreateObjectContext(ByVal schema As String, ByVal connString As String, ByVal model As String) As ObjectContext

    Dim myEntityConnection As EntityConnection = Nothing

    Try

        Dim conceptualReader As XmlReader = XmlReader.Create(Me.GetType().Assembly.GetManifestResourceStream(model + ".csdl"))
        Dim mappingReader As XmlReader = XmlReader.Create(Me.GetType().Assembly.GetManifestResourceStream(model + ".msl"))
        Dim storageReader As XmlReader = XmlReader.Create(Me.GetType().Assembly.GetManifestResourceStream(model + ".ssdl"))

        Dim storageNS As XNamespace = "http://schemas.microsoft.com/ado/2009/11/edm/ssdl"

        Dim storageXml = XDocument.Load(storageReader)
        Dim conceptualXml = XDocument.Load(conceptualReader)
        Dim mappingXml = XDocument.Load(mappingReader)

        For Each myItem As XElement In storageXml.Descendants(storageNS + "EntitySet")
            Dim schemaAttribute = myItem.Attributes("Schema").FirstOrDefault

            If schemaAttribute IsNot Nothing Then
                schemaAttribute.SetValue(schema)
            End If

        Next

        storageXml.Save("storage.ssdl")
        conceptualXml.Save("storage.csdl")
        mappingXml.Save("storage.msl")

        Dim storageCollection As StoreItemCollection = New StoreItemCollection("storage.ssdl")
        Dim conceptualCollection As EdmItemCollection = New EdmItemCollection("storage.csdl")

        Dim mappingCollection As StorageMappingItemCollection = New StorageMappingItemCollection(conceptualCollection, storageCollection, "storage.msl")


        Dim workspace = New MetadataWorkspace()
        workspace.RegisterItemCollection(conceptualCollection)
        workspace.RegisterItemCollection(storageCollection)
        workspace.RegisterItemCollection(mappingCollection)

        Dim connectionData = New EntityConnectionStringBuilder(connString)
        Dim connection = DbProviderFactories.GetFactory(connectionData.Provider).CreateConnection()

        connection.ConnectionString = connectionData.ProviderConnectionString

        myEntityConnection = New EntityConnection(workspace, connection)

        Return New ObjectContext(myEntityConnection)

    Catch ex As Exception

    End Try

End Function

确保storageNS名称空间硬编码值与代码中使用的值匹配,您可以通过调试代码并检查storageXML变量来查看它,以查看实际使用的内容。

现在,您可以在创建实体时在运行时传递新的模式名称和不同的数据库连接信息。不再需要手动.edmx更改。

Using Context As New JDE_Entities(CreateObjectContext("NewSchemaNameHere", ConnectionString_EntityFramework("ServerName", "DatabaseName", "UserName", "Password"), "JDE_Model"))

            Dim myWO = From a In Context.F4801 Where a.WADOCO = 400100

            If myWO IsNot Nothing Then
                For Each r In myWO
                    Me.Label1.Text = r.WADL01
                Next
            End If
        End Using

这些是使用的.net库:

Imports System.Data.Entity.Core.EntityClient
Imports System.Xml
Imports System.Data.Common
Imports System.Data.Entity.Core.Metadata.Edm
Imports System.Reflection
Imports System.Data.Entity.Core.Mapping
Imports System.Data.Entity.Core.Objects
Imports System.Data.Linq
Imports System.Xml.Linq

希望能帮助那些有同样问题的人。

答案 5 :(得分:0)

在将EF6与OData数据服务一起使用时,我遇到了很多问题,所以我必须找到一个替代解决方案。就我而言,我并不是真的需要在飞行中做到这一点。在部署到某些测试环境时,以及在安装程序中,我可以更改模式。

使用 Mono.Cecil直接在DLL中重写嵌入的var filename = "/path/to/some.dll" var replacement = "Schema=\"new_schema\""; var module = ModuleDefinition.ReadModule(filename); var ssdlResources = module.Resources.Where(x => x.Name.EndsWith(".ssdl")); foreach (var resource in ssdlResources) { var item = (EmbeddedResource)resource; string rewritten; using (var reader = new StreamReader(item.GetResourceStream())) { var text = reader.ReadToEnd(); rewritten = Regex.Replace(text, "Schema=\"old_schema\"", replacement); } var bytes = Encoding.UTF8.GetBytes(rewritten); var newResource = new EmbeddedResource(item.Name, item.Attributes, bytes); module.Resources.Remove(item); module.Resources.Add(newResource); } 资源。在我的情况下这很好用。

以下是如何执行此操作的简化示例:

{{1}}