Reputation: 6083
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.
Upvotes: 11
Views: 8402
Reputation: 1988
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
Upvotes: 10
Reputation: 205589
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.
Upvotes: 5