我正在与数百个数据库同步(表格方案在所有数据库上几乎相同),在需要时动态更改连接字符串.
我的问题是一些数据库的方案与其他数据库略有不同.
在所有这些中我有一个表X,它有一列Y,Y可以是一个位,也可以是一个字节.
EF基于数据库生成了一个模型类,该数据库的列Y定义为byte.所以在查询时,它显然会引发异常.
The ‘Y’ property on table ‘X’ could not be set to a
System.Boolean value. you must set the value to System.Byte.
有没有办法,在Database-First方法中动态更改模型来解决此问题?或者可能在返回值之前将其返回到一个字节,然后再将其分配给模型?防止例外?
解决方法
模型文件
创建EDMX时,EF会创建三个文件:
>商店模型(* .ssdl).
>类(或概念)模型(* .csdl).
>这两个模型之间的映射(* .msl).
这些文件作为资源文件嵌入到已编译的程序集中,通常您不需要知道它们的存在.在运行时,EF将从程序集加载文件,由配置文件的连接字符串中的资源路径指示,通常看起来像……
Metadata=res://*/...
可以将另一组资源文件嵌入到程序集中并相应地修改连接字符串,但实现此目的需要几个步骤.
第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.
在我的例子中,只创建了一个Person类:
public partial class Person { public int Id { get; set; } public string Name { get; set; } public bool IsActive { get; set; } }
在EF中,属性IsActive必须映射到位数据库字段,因此不可能简单地将其映射到byte(或tinyint)字段,如您已经发现的那样.我们必须添加第二个属性来支持字节字段:
partial class Person { public byte IsActiveByte { get; set; } }
主要的挑战是如何根据数据类型将这两个属性中的任何一个映射到数据库中的一个字段.
第3步 – 复制并修改第二组
现在,第一组的模型文件嵌入在程序集中.我们希望将它们作为常规文件提供,以便复制和修改它们.这可以通过临时将“Metadata Artifact Processing”设置从其默认设置(嵌入输出程序集)更改为Copy to Output Directory来完成.现在构建项目并在bin / Debug文件夹中找到三个文件.
将“元数据工件处理”设置恢复为默认设置,将文件移动到项目的根目录并将其复制到第二组.我最终得到了这些文件,其中“BitModel”文件是原件:
BitModel.csdl BitModel.msl BitModel.ssdl ByteModel.csdl ByteModel.msl ByteModel.ssdl
对于支持Person.IsActiveByte属性的ByteModel文件,我进行了这些更改(原始行/编辑行):
> csdl:
<Property Name="IsActive" Type="Boolean" Nullable="false" /> <Property Name="IsActiveByte" Type="Byte" Nullable="false" />
> ssdl:
<Property Name="IsActive" Type="bit" Nullable="false" /> <Property Name="IsActive" Type="tinyint" Nullable="false" />
> msl:
<ScalarProperty Name="IsActive" ColumnName="IsActive" /> <ScalarProperty Name="IsActiveByte" ColumnName="IsActive" />
第4步 – 将第二组嵌入为资源
下一步是将ByteModel文件添加到项目中,并在其属性中将“Build Action”设置为“Embedded Resource”.重建项目.
这些文件的嵌入方式与EF最初的方式略有不同.检查反汇编程序中的.exe文件,显示其资源名称为< namespace>.< filename>,在我的情况下:BitOrBye.ByteModel.csdl等.
第5步 – 添加连接字符串
EF为项目添加了一个连接字符串,看起来像……
<add name="DbBitContext" connectionString="Metadata=res://*/BitModel.csdl |res://*/BitModel.ssdl |res://*/BitModel.msl; provider=System.Data.sqlClient; provider connection string="data source=.\sql2016;initial catalog=DbBit;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" 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是.
输入先前的连接字符串,并将上下文映射到位字段,因此现在可以使用连接字符串来确定支持哪种类型的数据库,“BitModel”或“ByteModel”.
限制
在LINQ-to-Entities查询中,只能查询映射的属性.例如,像…这样的查询
context.People.Where(p => p.Id > 10).Select(p => p.Name).ToList()
……没关系但是当“BitModel”处于活动状态时,查询就像……
context.People.Where(p => p.IsActiveByte == 1).Select(p => p.Name).ToList()
…将抛出臭名昭着LINQ to Entities异常不支持指定的类型成员’IsActiveByte’.
当然你已经有了这个限制.您可能希望将未映射的属性添加到类中,这些属性将bye和bit属性的值传递给您将在应用程序代码中使用的一个属性.
一种可能的出路是使用EntityFramework.DynamicFilters
.这个小宝石使您可以在可以打开和关闭的上下文中定义全局过滤器.因此,可以定义两个全局过滤器…
modelBuilder.Filter("IsActiveBit",(Person p) => p.IsActive,true); modelBuilder.Filter("IsActiveByte",(Person p) => p.IsActiveByte,1);