7

In my .net core 3.1 application I wanted to encapsulate properties in some entity:

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

    public int Id { get; }

    public string Name { get; }
}

So I've removed all public setters and because of that somewhere in my code when I want to check whether such Sample already exists

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

that line causes the error: System.InvalidOperationException: 'No suitable constructor found for entity type 'Sample'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'name' in 'Sample(string name)'.'.

So I've added to code empty construtor

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

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

    public int Id { get; }

    public string Name { get; }
}

and now that line causes error: System.InvalidOperationException: 'The LINQ expression 'DbSet<Sample> .Any(s => s.Name == __name_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'.

But if I'll add private set to Name (or public) then everything works fine (even without empty constructor).

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
}

Can anyone explain me why this setter is required, for instance Id does not require that setter.

panoskarajohn
  • 1,846
  • 1
  • 21
  • 37
DiPix
  • 5,755
  • 15
  • 61
  • 108

2 Answers2

8

This has to do with Reflection as mentioned in the comments by Farhad Jabiyev. EF cannot find the property, it is hidden from it. When you make it private set, EF has access to it through reflection so everything works.

This can be done with a backing field https://learn.microsoft.com/en-us/ef/core/modeling/backing-field

From link above: Backing fields allow EF to read and/or write to a field rather than a property. This can be useful when encapsulation in the class is being used to restrict the use of and/or enhance the semantics around access to the data by application code, but the value should be read from and/or written to the database without using those restrictions/enhancements.

Meaning you would have to add a mapping to your backing field through fluent API like this.

modelBuilder.Entity<Sample >()
            .Property(b => b.Name)
            .HasField("_name"); // this would have to be the name of the backing field

For accessing backing fields of auto props you could use this-> Is it possible to access backing fields behind auto-implemented properties?

Me I would just add it myself, so it would be easier. So my class would look sth like this. You would need the mapping above and the mapping solves the problem that my property is private. Without the mapping this would fail.

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;
}

Please take a look at Lerman's approach-> https://www.youtube.com/watch?v=Z62cbp61Bb8&feature=youtu.be&t=1191

panoskarajohn
  • 1,846
  • 1
  • 21
  • 37
  • Is there any advantages in backing field solution over the using private setter? I think no, unless that is more explicit, but with bigger boilerplate. – DiPix Dec 29 '19 at 19:22
  • 1
    No I do not think so as well, in fact using string literal inside your mappings should repel you from using it. In my research i tried to even make your field truly readonly. So you could only instantiate only inside the constructor but no luck, i came through this though which is quite interesting -> https://stackoverflow.com/a/1050804/3902958 – panoskarajohn Dec 29 '19 at 19:28
4

Can anyone explain me why this setter is required, for instance Id does not require that setter.

Actually both require setter. The explanation is contained in EF Core Included and excluded properties documentation topic and is simple:

By convention, all public properties with a getter and a setter will be included in the model.

It doesn't say why, but that doesn't really matter because that's how it works "by design".

So either add private setters to your properties, or use fluent API to explicitly include them in the entity model (thus overriding the EF Core convention), e.g. for Sample class with getter only properties:

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

I personally find adding private setter much easier and less error prone.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • You're right, I had this `builder.Property(e => e.Id);`. That way it was working for Id field. – DiPix Dec 29 '19 at 18:51
  • Also is worth to mentioning that using only EF Core convention from your example, without private setter - we're losing possiblity to edit property when object is already initialized. https://stackoverflow.com/questions/36792392/omitted-setter-vs-private-setter so it seems that the best solution would be to just keep private setter or using backing field as @panoskarajohn already meantioned. – DiPix Dec 29 '19 at 19:16
  • 1
    "We are loosing possibility to edit" - sure, that's the behavior of getter only properties (equivalent of read only fields). Using explicit writeable backing field is the same as using auto-property with private setter - EF Core will use the associated backing field anyway. – Ivan Stoev Dec 29 '19 at 19:32