Entity Framework 和 Enums(枚举)aka EFExtensions





0/5 (0投票)
Entity Framework 对枚举的支持大概是最受期待的功能了,而且它居然不支持,这很奇怪。本文将展示一种使用模板来实现它的方法。
您想如何编写它
User foundUser = context.Users.FirstOrDefault(x =>
x.Username == "OneUsername" &&
x.CountryCode == CountryCodeEnum.UK &&
x.AccessLevel == AccessLevelEnum.Admin &&
x.Active);
当 CountryCode
的类型为 string
,AccessLevel
为 int
,而 Active
是 string
类型,可以取 “Y
” 或 “N
”。
这里进行了两个重要的演示。首先是 enum
的支持,它可以是数值类型或 string
类型。其次,对 Oracle 数据库中可能存在的非标准布尔值(“Y
” 和 “N
”)的支持。
幕后
如果您对实现原理不太感兴趣,可以跳到 先决条件。
在我上一篇文章中,我描述了如何添加对自定义布尔值(如“Y
”和“N
”)的支持,但我并没有真正解释它是如何工作的。
对于我们希望进行自定义映射的每个属性,我们都会创建一个复杂类型,该属性将成为一个复杂属性。新复杂类型的单个属性必须按照约定重命名为 Value
,以便我们可以为其创建模板并通过反射轻松找到它(如果您愿意,也可以称之为硬编码)。
POCO 模板被调整为在生成的类中为复杂类型插入自定义代码。这些代码处理到 enum
的隐式转换,进行复杂类型与 enum
比较的操作符,重写 object 类的方法,辅助方法等。模板会检查 Entity Developer 在复杂类型上设置的属性。
对于自定义布尔值映射,POCO 模板被调整为检查复杂类型的 DbBool
名称,因此这里不需要额外的属性。
最重要的是,ObjectSet<>
被包装在一个 ObjectSetWrapper
中,它将在表达式树中,用实际的 Value
属性替换直接使用复杂类型的成员属性,例如:x.CountryCode => x.CountryCode.Value
。对于布尔值,它将是:x.Active => x.Active.Value == “Y”
。
还有一个 EnumMapper
,它可以处理 enum
与将替换在表达式树中的值之间的转换。为了支持 string
值,我创建了一个新属性,用于 enum
。
public enum CountryCodeEnum
{
[EnumValue(Value = "US")]
USA,
[EnumValue(Value = "UK")]
UK
}
EnumMapper
在将 enum
映射到值时会检查该属性,如果找到该属性,则设置 string
值,如果没有找到属性,则设置数值。
我希望这涵盖了所有重要方面。
必备组件
不幸的是,我找不到任何优雅地处理 EF 默认设计器的方法,所以我选择了 Entity Developer,来自 devart.com。它还捆绑了 dotConnect 产品,一个适用于各种数据库的 ADO.NET 提供程序。我使用它是因为它支持模型中的属性。MSSQL 有免费版本,但对您可以导入的实体数量有限制。我建议您从一开始就使用 Entity Developer 来创建您的模型,因为我在打开用 EF 默认设计器创建的模型时遇到了一些兼容性问题。
第二个先决条件是,您必须使用 Visual Studio 默认 T4 模板生成的 POCO 对象。否则(例如,如果您使用的是 Entity Developer 中的模板),您就需要自己调整 EFExtensions
附带的包含模板。
第三,从 github 这里下载最新版本的 EFExtensions
库。
动手实践
我们已经准备好了一切,现在让我们开始吧。首先,将下载的 EFExtensions
库导入到定义您模型的项目中。或者,如果您打算修改它,可以获取项目源代码并引用它。
在 Entity Developer 中打开您的模型。转到 Model -> Settings。在左侧,有一个树状导航,选择 Attributes。现在点击 Add.. 并找到 EFExtensions
程序集(如果您选择使用带源代码的项目,请在您的 bin\debug 目录中搜索它)。找到它后,点击 OK。取消选中 EFExtensions.EnumValueAttribute
,因为它不需要。您应该看到类似这样的内容
接下来,转到您想要支持 enum
的属性。右键单击,选择 Migrate..,新窗口中的默认选择是 New complex type,这正是我们需要的。输入一个名称,我建议您设置一个约定,将所有要支持 enum
的复杂类型命名为 “Db<Name>Enum
”。
点击 OK 后,属性将被重命名为复杂类型的名称,您不需要这样做,所以将其重命名回来。在 Model Explorer 的 Complex Types 下,找到您刚刚创建的类型,并将其单个属性的名称更改为 Value
,请将此视为约定。对我来说,它看起来是
选择复杂类型,然后在 Properties 面板中找到 Attributes
属性。选择它,然后点击 (Collection) 右侧的小按钮,在新窗口中选择 DbEnumAttribute
,然后点击指向右侧的箭头按钮,这样它就会被添加到 Selected Attributes 列表中。现在在 Properties 列表中,设置 Target Enum
Type 的完整名称(包括完整的命名空间)。这就是您想要支持您复杂属性的 enum
。在我的例子中,它是 Experimental.CountryCodeEnum
。在图像中,您可以看到需要操作的箭头。
点击 OK 后,您可以继续处理下一个想要支持 enum
或自定义布尔值的属性。
要将属性映射到映射到 Y 和 N 的自定义布尔值,您只需创建一个名为 DbBool
的复杂类型,并带有一个也名为 Value
的单个属性。这里不需要添加属性。
如果您需要将实体中的更多字段映射到相同的复杂类型,请选择 Migrate..,然后选择 Existing complex type。
变得更脏
修改模型以支持 enum
很好,但您可以稍后完成。现在,我们必须深入 T4 模板,以正确生成复杂类型的代码,并自动包装 ObjectSets
。如 先决条件 部分所述,它支持 Visual Studio 中的默认 POCO 模板。对于其他模板的支持,您可能需要修改 EFExtensions.ttinclude
并进行调整。
在 EFExtensions
文件夹中,找到 EFExtensions.ttinclude 文件并将其复制到您的 POCO 模板目录中。您不必将其包含在项目中,只需让它在同一个文件夹中。如果这不起作用,请尝试将其移动到项目的根文件夹。
使用默认 POCO 模板,您有 2 个 .tt 文件,一个以 .Context.tt 结尾。打开该文件并找到
using System;
using System.Data.Objects;
using System.Data.EntityClient;
添加 using EFExtensions;
。接下来,替换
<#=Accessibility.ForReadOnlyProperty(entitySet)#>
ObjectSet<<#=code.Escape(entitySet.ElementType)#>> <#=code.Escape(entitySet)#>
{
get { return <#=code.FieldName(entitySet) #> ??
(<#=code.FieldName(entitySet)#> =
CreateObjectSet<<#=code.Escape(entitySet.ElementType)#>>
("<#=entitySet.Name#>")); }
}
private ObjectSet<<#=code.Escape(entitySet.ElementType)#>>
<#=code.FieldName(entitySet)#>;
用
<#=Accessibility.ForReadOnlyProperty(entitySet)#>
ObjectSetWrapper<<#=code.Escape(entitySet.ElementType)#>>
<#=code.Escape(entitySet)#>
{
get { return <#=code.FieldName(entitySet) #> ??
(<#=code.FieldName(entitySet)#> =
new ObjectSetWrapper<<#=code.Escape(entitySet.ElementType)#>>
(CreateObjectSet<<#=code.Escape(entitySet.ElementType)#>>
("<#=entitySet.Name#>"))); }
}
private ObjectSetWrapper<<#=code.Escape
(entitySet.ElementType)#>> <#=code.FieldName(entitySet)#>;
现在打开另一个文件。在最顶部,找到
<#@ template language="C#"
debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
添加 EFExtensions.ttinclude
<#@ template language="C#"
debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#@ include file="EFExtensions.ttinclude"#><#@
找到写入复杂类型类名的那一行
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
添加代码以在复杂类型具有 DbEnumAttribute
属性时插入接口
<#=Accessibility.ForType(complex)#> partial class
<#=code.Escape(complex)#> <#=CheckDbEnum(complex) ?
": " + GetDbEnumInterface(complex) : ""#>
现在向下滚动并找到包含 region.End();
的行,然后在之后是 EndNamespace(namespaceName);
类似这样的
region.End();
#>
}
<#
EndNamespace(namespaceName);
}
在 region.End();
之后立即插入 WriteDbBoolSupport(complex);
和 WriteEnumSupport(complex);
。
region.End();
WriteDbBoolSupport(complex);
WriteEnumSupport(complex);
#>
}
<#
EndNamespace(namespaceName);
}
这里将插入额外代码。您现在可以保存并关闭文件了。
差不多了
这样,复杂类型就被创建了,自定义代码正在生成……还有什么?啊,是的,当您想为 enum
使用 string
值时,就像我为 CountryCodeEnum
所做的那样,UK 对应 “UK
”,USA 对应 “US
”,所以它不是 ToString()
映射。为了获得这些 string
值,请在每个 enum
常量上添加 EnumValueAttribute(Value = “您想要的值”)
。让我们回顾一下我之前的例子
public enum CountryCodeEnum
{
[EnumValue(Value = "US")]
USA,
[EnumValue(Value = "UK")]
UK
}
如果未设置属性,则值将与执行 (int)CountryCodeEnum.USA
相同。如果您有 enum
在另一个项目中,不要忘记添加对 EFExtensions
的引用。
让我们来谈谈用法
现在我的 User 实体具有可以赋值和比较 enum
或布尔值的复杂属性,我还能做什么,而这是 Entity Framework 开箱即用无法做到的呢?那么,怎么样
User newUser = new User
{
Active = true,
CountryCode = CountryCodeEnum.UK,
Username = "OneUsername",
AccessLevel = AccessLevelEnum.User
}
您已经看到了 CountryCodeEnum
的样子,现在是 AccessLevelEnum
public enum AccessLevelEnum
{
Deny = 0,
User = 1,
Admin = 99
}
获取所有非活动用户
IEnumerable inactiveUsers = context.Users.Where(x => !x.Active);
或者……所有来自 UK
的用户
IEnumerable inactiveUsers = context.Users.Where(x => x.CountryCode == CountryCodeEnum.UK);
而且,我还可以将其与 string
值进行比较
IEnumerable inactiveUsers = context.Users.Where(x => x.CountryCode.Value == "UK");
后记
恭喜您,现在您知道如何在 LINQ to Entities 中使用 enum
了。另外,我想提一下 Davy Landman 的帖子,它帮助我找到了解决方案的缺失部分:为 Entity Framework 中的实体添加枚举属性支持。但不幸的是,提出的解决方案仅支持数值类型的 enum
。如果您发现任何错误,或想改进解决方案,可以在 github 上进行,欢迎贡献。
说了这么多,祝您玩得开心!