动态更改模型 - 数据库优先

时间:2018-02-28 09:33:30

标签: .net entity-framework entity-framework-6

我的数据库是使用数据优先方法的EF 6.0的MS-SQL。 我正在与数百个数据库同步(表格方案几乎在所有数据库上相同),在需要时动态更改连接字符串。

我的问题是某些数据库的方案与其他数据库略有不同。 在所有这些中我有一个表X,它有一列Y,Y可以是一个位或一个字节。

sql tables shown

EF基于数据库生成了一个模型类,该数据库的列Y 定义为byte。因此在查询时,它显然会引发异常。

  

' Y'桌子上的财产' X'无法设置为   System.Boolean值。您必须将值设置为System.Byte。

exception image

有没有办法,在Database-First方法中动态更改模型来解决此问题?或者可能在返回值之前将其返回到一个字节,然后再将其分配给模型?防止例外?

5 个答案:

答案 0 :(得分:5)

有一种方法可以在数据库优先完成。简而言之:创建两组映射和模型文件,并在配置文件中选择一组。

模型文件

创建EDMX时,EF会创建三个文件:

  • 商店模型(* .ssdl)。
  • 类(或概念)模型(* .csdl)。
  • 这两个模型之间的映射(* .msl)。

这些文件作为资源文件嵌入到已编译的程序集中,通常您不需要知道它们的存在。在运行时,EF将从程序集加载文件,由配置文件的连接字符串中的资源路径指示,通常看起来像......

metadata=res://*/...

可以将另一组资源文件嵌入到程序集中并相应地修改连接字符串,但实现此目的需要几个步骤。

为简洁起见,我将参考"映射和模型文件" as"模型文件"。

添加两组模型文件

第1步 - 创建第一组

创建第一组文件只不过是创建EDMX。我使用了一个非常简单的数据库表:

CREATE TABLE [dbo].[Person](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
    [IsActive] [bit] NOT NULL,
    CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED ([Id] ASC))
ALTER TABLE [dbo].[Person] ADD  CONSTRAINT [DF_Person_IsActive]  DEFAULT ((1)) FOR [IsActive]

在一个简单的C#控制台应用程序中,我在此表中创建了一个EDMX。

第2步 - 添加部分文件

在我的情况下,只创建了一个Person类:

public partial class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

在EF中,属性IsActive必须映射到bit数据库字段,因此无法简单地将其映射到byte(或{{1你已经发现了。})字段。我们必须添加第二个属性来支持字节字段:

tinyint

主要的挑战是如何根据数据类型将这两个属性中的任何一个映射到数据库中的一个字段。

步骤3 - 复制并修改第二组

现在第一组的模型文件嵌入在程序集中。我们希望将它们作为常规文件提供,以便复制和修改它们。这可以通过临时更改设置"元数据工件处理"从默认(嵌入输出程序集)到复制到输出目录。现在构建项目并在bin / Debug文件夹中找到三个文件。

恢复"元数据工件处理"设置为默认值,将文件移动到项目的根目录并将其复制到第二组。我最终得到了这些文件,其中包括" BitModel"那些是原件:

partial class Person
{
    public byte IsActiveByte { get; set; }
}

对于支持BitModel.csdl BitModel.msl BitModel.ssdl ByteModel.csdl ByteModel.msl ByteModel.ssdl 属性的ByteModel文件,我进行了这些更改(原始行/编辑行):

  • CSDL:

    Person.IsActiveByte
  • SSDL:

    <Property Name="IsActive" Type="Boolean" Nullable="false" />
    <Property Name="IsActiveByte" Type="Byte" Nullable="false" />
    
  • MSL:

    <Property Name="IsActive" Type="bit" Nullable="false" />
    <Property Name="IsActive" Type="tinyint" Nullable="false" />
    

现在可以删除BitModel文件。

步骤4 - 将第二组嵌入为资源

下一步是将ByteModel文件添加到项目及其属性集&#34; Build Action&#34;到&#34;嵌入式资源&#34;。重建项目。

文件的嵌入方式与EF最初的方式略有不同。检查反汇编程序中的.exe文件显示其资源名称为<ScalarProperty Name="IsActive" ColumnName="IsActive" /> <ScalarProperty Name="IsActiveByte" ColumnName="IsActive" /> ,在我的情况下:<namespace>.<filename>等。

步骤5 - 添加连接字符串

EF为项目添加了一个连接字符串,看起来像......

BitOrBye.ByteModel.csdl

我复制了这个连接字符串并注释掉了原来的字符串。在复制的连接字符串中,我修改了资源路径:

<add name="DbBitContext" 
    connectionString="metadata=res://*/BitModel.csdl
                              |res://*/BitModel.ssdl
                              |res://*/BitModel.msl;
    provider=System.Data.SqlClient;
    provider connection string=&quot;data source=.\sql2016;initial catalog=DbBit;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;"
    providerName="System.Data.EntityClient" />

现在,程序集已准备好连接到<add name="DbBitContext" connectionString="metadata=res://*/BitOrByte.ByteModel.csdl |res://*/BitOrByte.ByteModel.ssdl |res://*/BitOrByte.ByteModel.msl; ... /> 字段为Person.IsActive的数据库。属性tinyint不再是映射属性,Person.IsActive是。

输入先前的连接字符串,并将上下文映射到Person.IsActiveByte字段,因此现在可以使用连接字符串来确定支持哪种类型的数据库,&#34; BitModel&#34;或&#34; ByteModel&#34;。

限制

在LINQ-to-Entities查询中,只能查询映射的属性。例如,像......

这样的查询
bit

......没关系。但是当&#34; BitModel&#34;是活跃的,像......这样的查询。

context.People.Where(p => p.Id > 10).Select(p => p.Name).ToList()

...将抛出臭名昭着的指定的类型成员&#39; IsActiveByte&#39; LINQ to Entities 异常不支持。

当然你已经有了这个限制。您可能希望将未映射的属性添加到类中,这些属性将bye和bit属性的值引用到您将在应用程序代码中使用的一个属性。

可能的出路是使用EntityFramework.DynamicFilters 。这个小宝石使您可以在可以打开和关闭的上下文中定义全局过滤器。因此,可以定义两个全局过滤器......

context.People.Where(p => p.IsActiveByte == 1).Select(p => p.Name).ToList()

...您将添加一个,具体取决于您连接的数据库类型,可以从连接字符串中推断出来。

答案 1 :(得分:4)

我只想给你一些选择:

选项1:

如果可能的话,调整数据库方面的架构,因为从长远来看它只会让你头疼。

选项2:

或者切换到代码优先,为一些调整腾出空间。此外,如果您依赖EDMX,无论如何都会在EF Core中删除此功能。

选项3:

给该表一个特殊的处理方式,比如,从主要的Context中排除它,并创建一个新的上下文来处理它。

最有可能的是,据我所知,除此之外,它永远不会奏效。

抱歉,我只能给你一些想法。

答案 2 :(得分:2)

在我看来,这不是db-first尝试。它是一种&#34;代码优先 - 生成类 - 来自db&#34;尝试。

  1. 生成主模式
  2. 生成代码表单主
  3. 使用生成的代码连接到不同的dbs
  4. 你做错了是使用了错误的主模式。您尝试使用错误的数据类型进行访问。 使用兼容的数据类型编写schama(或直接代码)。最简单的方法是使用仅字符串属性并在以后映射它们。

    实施例级

    [Table("dbo.G")]
    public class G
    {
        public string Id { get; set; }
        [Column("CI_BlockWithID")]
        public string CiBlockWithIdStr { get; set; }
        public int CiBlockWithId
        {
            get { return Convert.ToInt32(this.CiBlockWithIdStr); }
            set { this.CiBlockWithIdStr = value.ToString(); }
        }
    }
    

    该示例显示了代码优先片段,用于解释机制。您需要具有兼容数据类型的代码 - 在此示例中为字符串。

    问题是如何获得此代码?

    一个。代码优先(你不想做)

    湾强制代码生成器选择其他数据类型(从主模式生成)。

    请注意,强类型比使用字符串更好。这应该只显示机制。如果您正在读取TinyInt和Byte,则可以尝试将Int32用作主类型等等(取决于db-provider)。 在MySQL中,我们使用非常多的字符串来读取DateTimes和Enums。

答案 3 :(得分:1)

  1. 您是否可以在其定义的sql中的所有数据库中添加一个视图来进行从一个位到另一个位的转换?然后包括视图而不是表。您可能还必须使用存储过程来执行更新和插入。实际上,您将使用该视图使数据库显示与单个DbContext相同。

  2. 您是否可以从基本上下文继承多个DbContext,然后在需要时动态更改上下文,而不是更改连接字符串?我使用具有依赖注入的工作单元/存储库模式。我的工作单元取决于DbContext

    public class UnitOfWork 
    {
        private readonly DbContext context;
    
        public UnitOfWork(DbContext context)
        {
            this.context = context;
        }
    }
    
  3. 我定义了在应用程序启动期间应该注入工作单元的内容。如果您使用此模式,则会在当前正在切换连接字符串的位置注入正确的DbContext

答案 4 :(得分:-1)

您可以为实体创建一个分部类,并添加一个可以处理任何情况的属性:

public partial class X
{
    public int TrueY
    {
        //add verification logic here
    }
}