【问题标题】:Entity Framework adding functionality to Properties实体框架向属性添加功能
【发布时间】:2015-02-18 19:20:43
【问题描述】:

在一个旧的 WPF 项目中,我有一个具有如下属性的类:

    private string _name = "";
    public string Name
    {
        get { return _name; }
        set
        {
            string cleanName = clsStringManip.CleanText(value, true);
            if (cleanName != _name)
            {
                _name = cleanName;
            }
        }
    }

每次名称更改时,我都会确保该值是“清理”的。将它放在属性中可以确保我永远不会忘记在对象上设置属性之前清理字符串。

现在我正在使用 MVC5 和使用 DatabaseFirst 的 EntityFramework6.1 重新创建这个系统。

所以所有属性都是由 EF 自动生成的。那么如何在不编辑自动生成代码的情况下将等效的 CleanText 函数添加到我的属性中呢? - 因为我下次更改数据库并重新同步时会丢失这些更改。

我只能通过 Google 找到一种通过 MetadataType 和部分类添加数据注释的方法,但这并不能回答我的问题。

我尝试将上述代码添加到部分类中,但收到错误:

类型 XXX 已包含名称的定义

我能想到的唯一方法是创建一堆 SetProperty() 函数,但这很脏,你永远无法确保其他开发人员(或我自己)会记得使用它们。

【问题讨论】:

  • 什么版本的 EF?代码优先、模型优先还是数据库优先?
  • 嗨 EF 6.1,MVC 5,数据库优先
  • 您可以考虑使用代码优先。我认为修改 t4 代码生成模板来进行如此深刻的更改并不是一个好主意。
  • 我首先考虑的是代码,但我的数据库是一个庞大的数据库,而且由于我是 MVC 和 EF 的新手,不幸的是我并没有真正掌握学习曲线的时间表。深奥是什么意思? - 你将如何实现相同类型的事情?
  • 将默认生成自动属性更改为这些受保护的设置器是相当重要的。您必须在许多地方修改 t4 模板(例如,还生成成员变量),并且可能只针对选定的属性。使用 CF,这要容易得多。您可以从数据库中创建 CF 模型以开始使用。

标签: c# asp.net-mvc entity-framework entity-framework-6


【解决方案1】:

免责声明:我还没有使用过 EF 6。

让我分两部分回答这个问题。首先,我会告诉你如何做到这一点。然后我会告诉你为什么我认为你不应该这样做。 :-)

如何:

正如您所发现的,您无法创建另一个 Name 属性。您需要修改 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 方法就只是一个空操作。 (部分方法必须返回 void,因此使用 ref 参数)。

为什么不呢?

这种方法的优点是对开发者完全透明。该属性只是神奇地改变了。但是有几个缺点:

一些控件期望如果他们调用 name = "123",如果他们得到名称,它是 "123",如果发生这种情况将会失败。值正在改变,但没有触发 PropertyChanged 事件。如果您确实触发了PropertyChanged,那么他们有时会将值更改回来。这可能会导致无限循环。

没有反馈给用户。他们输入了一个东西,它看起来是对的,但现在它说的是不同的东西。某些控件可能会显示更改,而其他控件则不会。

没有反馈给开发者。监视窗口似乎会改变值。而且在哪里可以看到验证规则并不明显。

实体框架本身在从数据库加载数据时使用这些方法。因此,如果数据库已经包含不符合清理规则的值,它将在从数据库加载时清理它们。这可能会使 LINQ 查询行为异常,具体取决于 SQL 服务器上运行的逻辑以及 C# 代码中运行的逻辑。 SQL 代码将看到一个值,C# 将看到另一个。

您可能还想了解 Entity-Framework 的更改跟踪在这种情况下的作用。如果属性集在从数据库加载值时进行清理,它是否认为对实体进行了更改? .Save() 调用会将其写回数据库吗?这是否会导致从未打算更改数据库的代码突然这样做?

替代

我建议不要这样做,而是创建一个Validate() 方法来查看每个属性并返回指示错误的错误。您甚至可以创建一个Cleanup() 方法来修复错误。这意味着清理不再透明,因此开发人员必须显式调用它们。但这是一件好事:代码不会在他们没有意识到的情况下改变值。编写业务逻辑或 UI 的人知道值会在什么时候发生变化,并且可以获得原因列表。

【讨论】:

  • 很棒的答案@Moby 谢谢。你所有的观点都是完全有效的。当我看到您的模板更改时,我虽然“很棒”,但是在阅读了您的“为什么不”部分之后,我可以看到出于您陈述的所有原因,我应该手动调用清理代码。是的,很长一段时间我/我们/每个人都会忘记在某个时候调用 clean up,但至少它会很清楚,我们可以修复脏字符串并确保在需要的地方添加 clean 调用。
【解决方案2】:

实现此目的的唯一方法是创建一个您在应用程序中实际使用的新属性。也许您可以在设计器中隐藏原始属性。您使用的实际属性可能如下所示:

public string ExternalName
{
    get { return Name; }
    set
    {
        string cleanName = clsStringManip.CleanText(value, true);
        if (cleanName != Name)
        {
            Name = cleanName;
        }
    }
}

作为替代方案,您可以使用 POCO 类:

【讨论】:

  • 是的,这是我的想法,但使用的是属性而不是 Setter 方法。
  • 是codefirst但使用现有数据库基本上只是创建代码以匹配现有数据库?我想我必须小心地将所有内容命名为数据库,否则我会冒与数据库不匹配的风险 - 我认为这肯定会发生在某个地方。
  • 我添加了不同的指南。这是一个很好的分步教程。 Visual Studio 将从现有数据库中为您生成整个 POCO 模型,并为您完成所有逆向工程
  • 感谢您的指南 - 它看起来相当简单,我正在考虑它 - 它会让我进入我想要的代码优先情况。作为 MVC EF 和 Codefirst 的新手。一旦我将我的数据库逆向工程到 POCO 模型,我是否应该“忘记”我的数据库并在代码中做所有事情?
  • @Rick 是的,逆向工程在一开始就意味着一次性的事情。然后,您可以继续使用代码和 EF 迁移之类的东西将数据库更新到较新的版本。我已经使用了所有三种方法,一旦你习惯了,代码优先会容易得多。你可能需要几个小时来试驾一下,看看它是如何为你工作的。
【解决方案3】:
  1. partial 添加到生成的类中。
  2. 将生成的类中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. 在您控制的partial class 中将CleanName 重命名为Name
  3. 4 中的警告变为“记住每次重新生成 POCO 时都执行步骤 1、2、和 5”。

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

【讨论】:

  • 很好地解决了这个问题。我想记住这样做会很容易,因为每次我重新生成时都会遇到编译错误。我可能会忘记的一点是调用 CleanName 而不是 Name。
  • 是的,如果您最终使用其中任何一个,请在 cmets 中告诉我。
猜你喜欢
  • 1970-01-01
  • 2011-04-27
  • 1970-01-01
  • 1970-01-01
  • 2011-07-06
  • 1970-01-01
  • 1970-01-01
  • 2011-05-26
  • 1970-01-01
相关资源
最近更新 更多