Entity Framework Core的属性没有setter方法

7
在我的.NET Core 3.1应用程序中,我想要封装实体中的属性:
public class Sample : AuditableEntity
{
    public Sample(string name)
    {
        Name = name;
    }

    public int Id { get; }

    public string Name { get; }
}

所以我已经移除了所有公共的 setter 方法,因此在我的代码中某个位置,当我想要检查是否存在这样的示例时

_context.Samples.Any(r => r.Name == name)

这行代码导致了错误:System.InvalidOperationException:'没有适合实体类型“Sample”的构造函数。以下构造函数具有无法绑定到实体类型属性的参数:无法在'Sample(string name)'中绑定'name'。

因此,我添加了一个空构造函数的代码。

public class Sample : AuditableEntity
{
    public Sample() { } // Added empty constructor here

    public Sample(string name)
    {
        Name = name;
    }

    public int Id { get; }

    public string Name { get; }
}

现在这行代码会导致错误:System.InvalidOperationException:“LINQ 表达式'DbSet<Sample>.Any(s => s.Name == __name_0)'无法翻译。请将查询重写为可以翻译的形式,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 之一的调用来显式地切换到客户端评估。有关更多信息,请参见 https://go.microsoft.com/fwlink/?linkid=2101038。”

但是如果我给Name添加私有设置(或公共),那么一切都能正常工作(即使没有空构造函数)。

public class Sample : AuditableEntity
{
    public Sample(string name)
    {
        Name = name;
    }

    public int Id { get; }

    public string Name { get; private set; } // added setter, removed empty constructor
}

有人能够解释一下为什么这个setter是必需的吗?比如,Id属性并不需要这个setter。

2
可能 EF 使用反射来初始化新对象。这样,private setter 就可以像 public setter 一样通过反射调用。 - Farhad Jabiyev
1
Julie Lerman有一段精彩的讲话,详细解释了使用EF实现DDD的方法。链接在此:https://www.youtube.com/watch?v=Z62cbp61Bb8&feature=youtu.be&t=1191。 - panoskarajohn
1
@FarhadJabiyev 那完全正确! - panoskarajohn
2个回答

9
这与Farhad Jabiyev在评论中提到的Reflection有关。EF找不到该属性,因为它被隐藏了。当你将其设置为private set时,EF通过反射访问它,所以一切正常。
这可以通过后备字段完成,具体请参考https://learn.microsoft.com/en-us/ef/core/modeling/backing-field

来自上述链接:后备字段允许EF读取和/或写入字段而不是属性。这在类中使用封装限制应用程序代码访问数据的语义时很有用,但值应从数据库读取和/或写入而不使用这些限制/增强。

这意味着您需要通过fluent API向后备字段添加映射,如下所示。
modelBuilder.Entity<Sample >()
            .Property(b => b.Name)
            .HasField("_name"); // this would have to be the name of the backing field

要访问自动属性的后备字段,您可以使用以下内容: 是否可以访问自动实现属性背后的后备字段?

我会直接添加它,这样会更容易。因此,我的类将看起来像这样。你需要上面的映射,而且映射解决了我的属性是私有的问题。如果没有映射,这将失败。

public class Sample : AuditableEntity
{
    private string _name; //now EF has access to this property with the Mapping added
    public Sample(string name)
    {
        _name = name;
    }

    public int Id { get; } 

    public string Name => _name;
}

请参考Lerman的方法->https://www.youtube.com/watch?v=Z62cbp61Bb8&feature=youtu.be&t=1191

使用后备字段解决方案是否比使用私有setter有任何优势?我认为没有,除非更加明确,但会增加更多样板代码。 - DiPix
1
不,我也不这么认为。事实上,在映射中使用字符串字面量应该让你望而却步。在我的研究中,我甚至试图使您的字段真正只读。因此,您只能在构造函数中实例化,但没有运气。不过,我找到了一个非常有趣的解决方案-> https://dev59.com/fHNA5IYBdhLWcg3wNa-T#1050804 - panoskarajohn

4

有人能解释一下为什么需要这个setter吗?例如,Id不需要那个setter。

实际上两者都需要setter。解释包含在EF Core 包含和排除属性 文档主题中,很简单:

按照惯例,所有具有getter和setter的公共属性都将包含在模型中。

它没有说为什么,但这并不重要,因为这是“按设计”工作的方式。

因此,要么将私有setter添加到属性中,要么使用流畅API显式地将它们包含在实体模型中(从而覆盖EF Core约定),例如,对于只有getter属性的Sample类:

modelBuilder.Entity<Sample>(builder =>
{
    builder.Property(e => e.Id);
    builder.Property(e => e.Name);
});

我个人认为添加私有 setter 更加简单且易于避免错误。

你说得对,我有这个 builder.Property(e => e.Id);。这样它就可以为Id字段工作了。 - DiPix
另外值得一提的是,如果我们仅使用EF Core约定(如您的示例),而不设置私有setter,那么当对象已经初始化时,我们就无法编辑属性。 https://dev59.com/_loV5IYBdhLWcg3wCq8n,因此最好的解决方案似乎是保留私有setter或像@panoskarajohn提到的那样使用后备字段。 - DiPix
1
我们正在失去编辑的可能性 - 当然,这是只读属性的行为(相当于只读字段)。使用显式可写的后备字段与使用带有私有setter的自动属性相同 - EF Core将无论如何使用相关联的后备字段。 - Ivan Stoev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接