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

发布于 2024-08-29 07:26:12 字数 426 浏览 5 评论 0原文

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

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

知道为什么吗?

I need to change the storage schema of the entities on runtime.
I've followed a wonderful post, available here:
http://blogs.microsoft.co.il/blogs/idof/archive/2008/08/22/change-entity-framework-storage-db-schema-in-runtime.aspx?CommentPosted=true#commentmessage

This works perfectly, but only for queries, not for modifications.

Any idea why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

流年已逝 2024-09-05 07:26:12

嗯,我一直在互联网上寻找这段代码。最后我不得不自己做。它基于 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模型都可以通过这个函数修改,而不仅仅是指定的一个。

Well, I was looking for this piece of code all around the Internet. In the end I had to do it myself. It's based on Brandon Haynes adapter, but this function is all you need to change the schema on runtime - and you don't need to replace the autogenerated context constructors.

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

The resulting EntityConnection should be passed as a parameter when instantiating the context. You can modify it, so all ssdl models are modified by this function, not only the specified one.

誰認得朕 2024-09-05 07:26:12

我成功地通过使用 CodePlex 中的一个出色的库(由 Brandon Haynes 提供)解决了这个问题,该库名为“Entity Framework Runtime Model Adapter”,可在此处获取:
http://efmodeladapter.codeplex.com/

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

所以,我很好。

不管怎样,谢谢,特别是布兰登,干得很棒!

I've managed to resolve this issue by using a brilliant library, located in CodePlex (courtesy of Brandon Haynes), named "Entity Framework Runtime Model Adapter", available here:
http://efmodeladapter.codeplex.com/

I've tweaked it a bit, to fit our needs and without the need of replacing the designer code at all.

So, I'm good.

Thanks anyways, and especially to Brandon, amazing job!

究竟谁懂我的在乎 2024-09-05 07:26:12

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

I need import data from postgres database. It by default use schema "public". So I use Entity Framework CTP 4 "Code first". It by default use schema "dbo". To change it in runtime I use:

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

It work for select, insert, update and delete data. So next test in pass:

[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();
        }
厌倦 2024-09-05 07:26:12

本身不是答案,而是 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);
}

Not an answer per se but a followup on Jan Matousek's Create[EntityConnection] method showing how to use from a DbContext. Note DB is the DbContext type passed to the generic repository.

 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);
}
峩卟喜欢 2024-09-05 07:26:12

我能够将 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

希望可以帮助任何遇到相同问题的人。

I was able to convert the solution from Jan Matousek to work in vb.net 2013 with entity framework 6. I will also try to explain how to use the code in vb.net.

We have a JD Edwards Database which uses different Schema's for each environment (TESTDTA, CRPDTA, PRODDTA). This makes switching between environments cumbersome as you have to manually modify the .edmx file if you want to change environments.

First step is to create a partial class that allows you to pass a value to the constructor of your entities, by default it uses the values from your config file.

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

Next create the function that will modify your store schema .ssdl file in memory.

 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

Make sure that the storageNS namespace hardcoded value matches the one used in your code, you can view this by debugging the code and examining the storageXML variable to see what was actually used.

Now you can pass a new schema name, and different database connection info at runtime when you create your entities. No more manual .edmx changes required.

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

These were the .net libraries used:

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

Hope that helps anyone out there with the same issues.

dawn曙光 2024-09-05 07:26:12

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

使用 Mono.Cecil 重写嵌入的 .ssdl 资源直接在 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);
}

I had a lot of problems getting this to work when using EF6 with an OData Data Service, so I had to find an alternate solution. In my case, I didn't really need to do it on the fly. I could get away with changing the schema when deploying to some test environments, and in the installer.

Use Mono.Cecil to rewrite the embedded .ssdl resources straight in the DLLs. This works just fine in my case.

Here is a simplified example of how you can do this:

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文