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 的人知道值会在什么时候发生变化,并且可以获得原因列表。

  1. partial添加到生成的class。
  2. 将生成的 class 中 Name 的范围从 public 更改为 internal
  3. 在同一程序集中添加以下内容:

public partial class classname
{
    [NotMapped]
    public string CleanName
    {
        get { return Name; }
        set
        {
            var cleanName = clsStringManip.CleanText(value, true);
            if (cleanName != Name)
                Name = cleanName;
        }
    }
}
  1. 警告:每次重新生成 POCO 时都必须记住执行步骤 1-2 ...我会认真考虑 Code First to Existing Database

编辑

可选:

  1. 在生成的classname中将Name重命名为InternalName;用 [Column("Name")].
  2. 装饰它
  3. 在您控制的 partial class 中将 CleanName 重命名为 Name
  4. 4 中的警告变为 "remember to do steps 1, 2, and 5 every time you regenerate POCOs"。

这种方法的额外好处是无需修改任何客户端代码(即 Name 的使用仍然是 Name)。我仍然会强烈考虑 Code First to Existing Database.