Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework Core properties without setters

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.

like image 971
DiPix Avatar asked Dec 29 '19 14:12

DiPix


1 Answers

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://docs.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

like image 122
panoskarajohn Avatar answered Nov 15 '22 05:11

panoskarajohn