问题在于,某些数据库的结构与其他数据库略有不同。在所有数据库上,我都有一个名为X的表,该表具有一个名为Y的列,Y可以是位或字节。
EF生成了一个基于数据库的模型类,其中列Y被定义为字节。因此,在查询时,它显然会抛出异常。
在数据库优先方法中,是否有一种方式可以动态更改模型以解决此问题?或者在将返回值分配给模型之前,将其转换为字节以防止异常?表'X'上的'Y'属性无法设置为System.Boolean值。您必须将该值设置为System.Byte。
EF生成了一个基于数据库的模型类,其中列Y被定义为字节。因此,在查询时,它显然会抛出异常。
在数据库优先方法中,是否有一种方式可以动态更改模型以解决此问题?或者在将返回值分配给模型之前,将其转换为字节以防止异常?表'X'上的'Y'属性无法设置为System.Boolean值。您必须将该值设置为System.Byte。
metadata=res://*/...
可以将另一组资源文件嵌入程序集中,并相应地修改连接字符串,但需要几个步骤才能实现。
为简洁起见,我将“映射和模型文件”称为“模型文件”。
创建第一组文件就是创建一个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]
public partial class Person
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
IsActive
必须映射到bit
数据库字段,因此无法将其简单地映射到byte
(或tinyint
)字段,正如您已经发现的那样。我们必须添加第二个属性来支持字节字段:partial class Person
{
public byte IsActiveByte { get; set; }
}
BitModel.csdl
BitModel.msl
BitModel.ssdl
ByteModel.csdl
ByteModel.msl
ByteModel.ssdl
为了使ByteModel文件支持Person.IsActiveByte
属性,我进行了以下更改(原始行/编辑后的行):
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" />
现在可以删除BitModel文件。
下一步是将ByteModel文件添加到项目中,并在它们的属性中将“Build Action”设置为“嵌入式资源”,然后重新构建项目。
这些文件的嵌入方式与EF最初的方式稍有不同。在反汇编器中检查.exe文件会显示它们的资源名称为<namespace>.<filename>
,在我的情况下是: BitOrBye.ByteModel.csdl
等。
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
是。
输入之前的连接字符串和上下文映射到 bit
字段,所以现在可以使用连接字符串来确定支持哪种类型的数据库,"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()
...将会抛出臭名昭著的The specified type member 'IsActiveByte' is not supported in LINQ to Entities异常。
当然,您已经有了这个限制。您可能希望向类中添加未映射的属性,将字节和位属性的值传递到一个属性中,在应用程序代码中使用该属性。
一种可能的解决方法是使用EntityFramework.DynamicFilters。这个小宝石可以让您在上下文中定义全局过滤器,可以打开和关闭。因此,可以定义两个全局过滤器...
modelBuilder.Filter("IsActiveBit", (Person p) => p.IsActive, true);
modelBuilder.Filter("IsActiveByte", (Person p) => p.IsActiveByte, 1);
根据连接字符串可以推断出所连接的数据库类型,因此您需要添加一个相应的数据库驱动程序。
Person
类的edmx文件,但是最后保存的EDMX会根据自己的配置修改类。你可以添加一个包含IsActiveByte属性的部分类文件,并始终、始终将“BitContext”最后保存,但我认为这非常容易出错。 - Gert Arnold我想给你提供一些考虑的选择:
选择1:
如果可能的话,请在数据库端调整模式,因为从长远来看,这将只会给你带来麻烦。
选择2:
或者转而使用“代码优先”以腾出空间进行一些微调。此外,如果你依赖EDMX,这个特性在EF Core中已被删除。
选择3:
给该表格特别处理,例如,将其从主上下文中排除,并创建一个新的上下文来管理它。
除此之外,据我所知,大概不会有其他方法可以解决。
很抱歉我只能给你一些想法。
在我看来,这不是一个以数据库为先的尝试。它更像是一种“从数据库生成类的代码优先”尝试。
你所做的错误是使用了错误的主模式。你试图使用不兼容的数据类型进行访问。 编写一个具有兼容数据类型的模式(或直接编写代码)。最简单的方法是仅使用字符串属性,稍后再进行映射。
[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(); }
}
}
CI_BlockWithID
定义为varchar
,它将得到相同的异常。不会进行任何隐式转换。这不会让我进一步发展。 - Stavmvarchar
了吗?你的错误是一个字节和布尔类型之间的转换问题,它们不兼容。 - kara在 'CiBlockWithId' 上的属性无法设置为 'System.Boolean' 值。您必须将此属性设置为 'System.String' 类型的非空值。
- 将数据库列更改为 varchar。然后从数据库更新模型,最后将数据库列更改为 bit
,并尝试查询。 - StavmCould you add a view to all the databases that does the conversion from bit to byte within its defining sql? Then include the view instead of the table. You may also have to use stored procedures to do updates and inserts. Effectively you'd be using the view to make the databases appear identical to a single DbContext
.
Could you have multiple DbContext
s that inherit from a base context, then dynamically change the context when required, instead of changing connection strings? I use a Unit of Work / Repository pattern with dependency injection. My Unit of Work is dependent on a DbContext
:
public class UnitOfWork
{
private readonly DbContext context;
public UnitOfWork(DbContext context)
{
this.context = context;
}
}
DbContext
。public partial class X
{
public int TrueY
{
//add verification logic here
}
}
string
类型,然后使用部分类方法或其他方法来处理它,那么是可以实现的。 - Max