Entity Framework 向属性添加功能
Entity Framework adding functionality to Properties
在一个旧的 WPF 项目中,我有一个 class 具有如下属性:
private string _name = "";
public string Name
{
get { return _name; }
set
{
string cleanName = clsStringManip.CleanText(value, true);
if (cleanName != _name)
{
_name = cleanName;
}
}
}
每次更改名称时,我都会确保值为 "cleaned"。把它放在 属性 确保我永远不会忘记在对象上设置 属性 之前清理字符串。
现在我正在使用 DatabaseFirst 使用 MVC5 和 EntityFramework6.1 重新创建这个系统。
所以所有属性都是由 EF 自动生成的。那么如何在不编辑 autogen 代码的情况下将等效的 CleanText 函数添加到我的属性中? - 因为下次更改数据库并重新同步时我会丢失这些更改。
我只能通过 Google 找到一种通过 MetadataType 和部分 classes 添加数据注释的方法,但这不能回答我的问题。
我试图将上面的代码添加到部分 class 但得到错误:
The type XXX already contains a definition for Name
我能想到的唯一方法是创建一堆 SetProperty() 函数,但这很脏,您永远无法确保其他开发人员(或我自己)会记得使用它们。
实现此目的的唯一方法是创建一个您在应用程序中实际使用的新 属性。也许你可以在设计器中隐藏原来的属性。您使用的实际 属性 可能如下所示:
public string ExternalName
{
get { return Name; }
set
{
string cleanName = clsStringManip.CleanText(value, true);
if (cleanName != Name)
{
Name = cleanName;
}
}
}
作为替代方案,您可以使用 POCO 类:
- 如果您想继续使用数据库优先,check this answer
- 对现有数据库使用代码优先,请参阅this detailed guide
免责声明:我还没有使用过 EF 6。
让我分两部分回答这个问题。首先,我会告诉你如何做到这一点。然后我会告诉你为什么我认为你不应该这样做。 :-)
方法:
如您所见,您无法创建另一个名称 属性。您需要修改 EF 生成代码的方式,以便它为您提供插入新代码的位置。根据您使用 EF 的方式,它通常会生成 Validate()
方法调用或 OnPropertyChanged()
调用。您可以在这些方法中做您想做的事。
如果您不能在 Validate()
或 OnPropertyChanged()
中执行此操作,您可以更改 T4 模板以生成如下内容:
private string _name = "";
public string Name
{
get { return _name; }
set
{
string cleanName = value;
Cleanup_Name(ref cleanName);
if (cleanName != _name)
{
_name = cleanName;
}
}
}
private partial void Cleanup_Name(ref string);
这为您提供了 partial method,然后您可以根据需要实施。因此,对于您想要自定义的任何 属性,您现在可以将另一个文件添加到您的项目中来执行此操作:
public partial class MyEntity {
void Cleanup_Name(ref string name)
{
// Put your logic in here to fixup the name
}
}
如果你不写上面的代码块,那么partial方法就是一个空操作。 (部分方法必须 return void,因此使用 ref 参数)。
为什么不呢?
这种方法的优点是对开发者来说是完全透明的。 属性 只是神奇地改变了。但是也有几个缺点:
一些控件期望如果他们调用 name = "123" 如果他们取回名称,它就是 "123" 并且如果发生这种情况将会失败。值正在更改,但没有触发 PropertyChanged
事件。如果您确实触发了 PropertyChanged
,那么它们有时会改回该值。这可能会导致无限循环。
没有给用户的反馈。他们输入了一个东西,看起来是对的,但现在它说的不一样了。某些控件可能会显示更改,而其他控件则不会。
没有反馈给开发者。手表 window 似乎会改变值。而且在哪里可以看到验证规则并不明显。
实体框架本身在从数据库加载数据时使用这些方法。因此,如果数据库已经包含与清理规则不匹配的值,它将在从数据库加载时清理它们。根据 SQL 服务器上的 运行 逻辑以及 C# 代码中的 运行 逻辑,这可能会使 LINQ 查询行为异常。 SQL 代码将看到一个值,C# 将看到另一个值。
您可能还想了解 Entity-Framework 的更改跟踪在这种情况下的作用。如果 属性 集在从数据库加载值时进行清理,它是否认为这是对实体的更改? .Save() 调用会将其写回数据库吗?这会导致从未打算更改数据库的代码突然这样做吗?
备选方案
我建议创建一个 Validate()
方法来查看每个 属性 和 return 错误,而不是这样做。您甚至可以创建一个 Cleanup()
方法来修复错误的地方。这意味着清理不再透明,因此开发人员必须显式调用它们。但这是一件好事:代码不会在他们没有意识到的情况下改变值。编写业务逻辑或 UI 的人知道值会在什么时候发生变化,并且可以获得原因列表。
- 将
partial
添加到生成的class。
- 将生成的 class 中
Name
的范围从 public
更改为 internal
。
- 在同一程序集中添加以下内容:
public partial class classname
{
[NotMapped]
public string CleanName
{
get { return Name; }
set
{
var cleanName = clsStringManip.CleanText(value, true);
if (cleanName != Name)
Name = cleanName;
}
}
}
- 警告:每次重新生成 POCO 时都必须记住执行步骤 1-2 ...我会认真考虑 Code First to Existing Database。
编辑
可选:
- 在生成的
classname
中将Name
重命名为InternalName
;用 [Column("Name")]
. 装饰它
- 在您控制的
partial class
中将 CleanName
重命名为 Name
。
- 4 中的警告变为 "remember to do steps 1, 2, and 5 every time you regenerate POCOs"。
这种方法的额外好处是无需修改任何客户端代码(即 Name
的使用仍然是 Name
)。我仍然会强烈考虑 Code First to Existing Database.
在一个旧的 WPF 项目中,我有一个 class 具有如下属性:
private string _name = "";
public string Name
{
get { return _name; }
set
{
string cleanName = clsStringManip.CleanText(value, true);
if (cleanName != _name)
{
_name = cleanName;
}
}
}
每次更改名称时,我都会确保值为 "cleaned"。把它放在 属性 确保我永远不会忘记在对象上设置 属性 之前清理字符串。
现在我正在使用 DatabaseFirst 使用 MVC5 和 EntityFramework6.1 重新创建这个系统。
所以所有属性都是由 EF 自动生成的。那么如何在不编辑 autogen 代码的情况下将等效的 CleanText 函数添加到我的属性中? - 因为下次更改数据库并重新同步时我会丢失这些更改。
我只能通过 Google 找到一种通过 MetadataType 和部分 classes 添加数据注释的方法,但这不能回答我的问题。
我试图将上面的代码添加到部分 class 但得到错误:
The type XXX already contains a definition for Name
我能想到的唯一方法是创建一堆 SetProperty() 函数,但这很脏,您永远无法确保其他开发人员(或我自己)会记得使用它们。
实现此目的的唯一方法是创建一个您在应用程序中实际使用的新 属性。也许你可以在设计器中隐藏原来的属性。您使用的实际 属性 可能如下所示:
public string ExternalName
{
get { return Name; }
set
{
string cleanName = clsStringManip.CleanText(value, true);
if (cleanName != Name)
{
Name = cleanName;
}
}
}
作为替代方案,您可以使用 POCO 类:
- 如果您想继续使用数据库优先,check this answer
- 对现有数据库使用代码优先,请参阅this detailed guide
免责声明:我还没有使用过 EF 6。
让我分两部分回答这个问题。首先,我会告诉你如何做到这一点。然后我会告诉你为什么我认为你不应该这样做。 :-)
方法:
如您所见,您无法创建另一个名称 属性。您需要修改 EF 生成代码的方式,以便它为您提供插入新代码的位置。根据您使用 EF 的方式,它通常会生成 Validate()
方法调用或 OnPropertyChanged()
调用。您可以在这些方法中做您想做的事。
如果您不能在 Validate()
或 OnPropertyChanged()
中执行此操作,您可以更改 T4 模板以生成如下内容:
private string _name = "";
public string Name
{
get { return _name; }
set
{
string cleanName = value;
Cleanup_Name(ref cleanName);
if (cleanName != _name)
{
_name = cleanName;
}
}
}
private partial void Cleanup_Name(ref string);
这为您提供了 partial method,然后您可以根据需要实施。因此,对于您想要自定义的任何 属性,您现在可以将另一个文件添加到您的项目中来执行此操作:
public partial class MyEntity {
void Cleanup_Name(ref string name)
{
// Put your logic in here to fixup the name
}
}
如果你不写上面的代码块,那么partial方法就是一个空操作。 (部分方法必须 return void,因此使用 ref 参数)。
为什么不呢?
这种方法的优点是对开发者来说是完全透明的。 属性 只是神奇地改变了。但是也有几个缺点:
一些控件期望如果他们调用 name = "123" 如果他们取回名称,它就是 "123" 并且如果发生这种情况将会失败。值正在更改,但没有触发 PropertyChanged
事件。如果您确实触发了 PropertyChanged
,那么它们有时会改回该值。这可能会导致无限循环。
没有给用户的反馈。他们输入了一个东西,看起来是对的,但现在它说的不一样了。某些控件可能会显示更改,而其他控件则不会。
没有反馈给开发者。手表 window 似乎会改变值。而且在哪里可以看到验证规则并不明显。
实体框架本身在从数据库加载数据时使用这些方法。因此,如果数据库已经包含与清理规则不匹配的值,它将在从数据库加载时清理它们。根据 SQL 服务器上的 运行 逻辑以及 C# 代码中的 运行 逻辑,这可能会使 LINQ 查询行为异常。 SQL 代码将看到一个值,C# 将看到另一个值。
您可能还想了解 Entity-Framework 的更改跟踪在这种情况下的作用。如果 属性 集在从数据库加载值时进行清理,它是否认为这是对实体的更改? .Save() 调用会将其写回数据库吗?这会导致从未打算更改数据库的代码突然这样做吗?
备选方案
我建议创建一个 Validate()
方法来查看每个 属性 和 return 错误,而不是这样做。您甚至可以创建一个 Cleanup()
方法来修复错误的地方。这意味着清理不再透明,因此开发人员必须显式调用它们。但这是一件好事:代码不会在他们没有意识到的情况下改变值。编写业务逻辑或 UI 的人知道值会在什么时候发生变化,并且可以获得原因列表。
- 将
partial
添加到生成的class。 - 将生成的 class 中
Name
的范围从public
更改为internal
。 - 在同一程序集中添加以下内容:
public partial class classname
{
[NotMapped]
public string CleanName
{
get { return Name; }
set
{
var cleanName = clsStringManip.CleanText(value, true);
if (cleanName != Name)
Name = cleanName;
}
}
}
- 警告:每次重新生成 POCO 时都必须记住执行步骤 1-2 ...我会认真考虑 Code First to Existing Database。
编辑
可选:
- 在生成的
classname
中将Name
重命名为InternalName
;用[Column("Name")]
. 装饰它
- 在您控制的
partial class
中将CleanName
重命名为Name
。 - 4 中的警告变为 "remember to do steps 1, 2, and 5 every time you regenerate POCOs"。
这种方法的额外好处是无需修改任何客户端代码(即 Name
的使用仍然是 Name
)。我仍然会强烈考虑 Code First to Existing Database.