【问题标题】:Changing schema name on runtime - Entity Framework在运行时更改架构名称 - 实体框架
【发布时间】:2010-04-18 17:25:15
【问题描述】:

我需要在运行时更改实体的存储架构。 我关注了一篇精彩的帖子,可在此处找到: http://blogs.microsoft.co.il/blogs/idof/archive/2008/08/22/change-entity-framework-storage-db-schema-in-runtime.aspx?CommentPosted=true#commentmessage

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

知道为什么吗?

【问题讨论】:

标签: entity-framework schema runtime


【解决方案1】:

嗯,我一直在 Internet 上寻找这段代码。最后我不得不自己做。它基于 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模型都被这个函数修改了,不仅仅是指定的那个。

【讨论】:

  • 太棒了!这个解决方案对我有用。我正在使用一个 DB2 数据库,它显然认为模式更多地处于 SQLServer 数据库的级别。使用 EF5。
  • 我注意到创建这三个集合的实例非常昂贵(时间),因此我将修改后的 Worspaces 存储在 Dictionary 中,因此我不需要为每个 Context 实例化修改它。跨度>
  • 我已经在包装 EntityConnection 创建,所以我只需要添加覆盖架构的部分。太糟糕了,开箱即用不支持类似的功能。谢谢。
  • 不知道为什么我的命名空间不同(使用 EF 5 和 Oracle),但这对我有用:XNamespace StorageNS = "schemas.microsoft.com/ado/2009/11/edm/ssdl";
  • 查看我的说明如何从 DbContext 中使用它。
【解决方案2】:

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

我对其进行了一些调整,以满足我们的需求,并且根本不需要替换设计器代码。

所以,我很好。

无论如何,谢谢,尤其是布兰登,出色的工作!

【讨论】:

    【解决方案3】:

    我需要从 postgres 数据库导入数据。它默认使用模式“公共”。所以我使用实体框架 CTP 4“代码优先”。它默认使用模式“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();
            }
    

    【讨论】:

    • 这个解决方案非常适合我。就我而言,我需要做的就是切换表前缀。但是,我的语法略有不同: modelBuilder.Entity().Map(x => x.ToTable(_tablePrefix + tableName));谢谢塞尔格!
    • 很高兴听到!语法会发生变化,因为我的代码是在实体框架 CTP4 上编写和测试的。
    【解决方案4】:

    本身不是一个答案,而是 Jan Matousek 的 Create[EntityConnection] 方法的后续内容,展示了如何从 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);
    }
    

    【讨论】:

      【解决方案5】:

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

      我们有一个 JD Edwards 数据库,它为每个环境(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
      

      希望对遇到同样问题的人有所帮助。

      【讨论】:

        【解决方案6】:

        在将 EF6 与 OData 数据服务一起使用时,我遇到了很多问题,因此我必须找到一个替代解决方案。就我而言,我真的不需要即时执行此操作。在部署到某些测试环境和安装程序时,我可以避免更改架构。

        使用 Mono.Cecil 直接在DLL 中重写嵌入的.ssdl 资源。这在我的情况下工作得很好。

        这是一个简化的示例,说明如何执行此操作:

        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);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-11
          • 2011-03-10
          • 2018-01-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多