运行时更改架构名称 - Entity Framework

22

https://dev59.com/S2Ik5IYBdhLWcg3wPL_R - Nick
6个回答

25

嗯,我在互联网上四处寻找这段代码。最终我不得不自己动手。它基于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
我注意到创建这三个集合的实例非常昂贵(时间),因此我将修改后的工作区存储在字典中,这样我就不需要为每个上下文实例进行修改。 - Jan Matousek
我已经在封装EntityConnection的创建,所以我只需要添加覆盖模式的部分。很遗憾,类似的功能不支持开箱即用。谢谢。 - JCallico
1
不确定为什么我的命名空间不同(使用EF 5和Oracle),但是一旦我这样做了,它对我有用:XNamespace StorageNS =“http://schemas.microsoft.com/ado/2009/11/edm/ssdl”; - Lee Richardson
如果您正在使用存储过程,则需要修改Function元素中的模式,因此在更改EntitySet元素的代码之后,添加此代码以更改Function元素。// 更改函数的模式名称 foreach (var function in storageXml.Descendants(storageNS + "Function")) { var schemaAttribute = function.Attributes("Schema").FirstOrDefault(); if (schemaAttribute != null) { schemaAttribute.SetValue(schema); } } - Robert Tanenbaum
显示剩余7条评论

11

我成功地解决了这个问题,使用了一个出色的库,位于CodePlex上(由Brandon Haynes提供),名为“Entity Framework Runtime Model Adapter”,可以在此处找到: http://efmodeladapter.codeplex.com/

我稍微调整了一下它来适应我们的需求,并且完全不需要替换设计师代码。

所以,现在没问题了。

无论如何,谢谢大家,特别是Brandon,做得很棒!


5

我需要从PostgreSQL数据库中导入数据。它默认使用模式“public”。因此,我使用实体框架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();
        }

这个解决方案对我非常有效。在我的情况下,我只需要通过表前缀进行切换即可。然而,对我来说语法略有不同:modelBuilder.Entity<entity_type>().Map(x => x.ToTable(_tablePrefix + tableName)); 谢谢Serg! - Adam
很高兴听到这个消息!语法将会有所改变,因为我的代码是在Entity Framework CTP4上编写和测试的。 - Sergey Makridenkov

2

虽然不是答案,但这是对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);
}

0

我在使用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);
}

0

我已经成功将Jan Matousek的解决方案转换为可在vb.net 2013中使用实体框架6的代码。我还会尝试解释如何在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

希望这能帮助到有同样问题的任何人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接