Reputation: 95
I'm expanding my basic EF model, adding a composite class Host, that depends on four others, BlockedIp, MxRecord, SmtpGeoLocation, MailExchanger, the relationship between Host and the others is a 1:1 as they are used by other entities in the model. I want Host to reflect an abstraction of property values in these other classes as boolean ready only properties. Examples, IsPrivateIp, HasDnsRecord, IsBlocked, HasGeoLocation, HasMailExchanger, HasMxRecord.
The dependent class BlockedIp for example, has a bool IsBlocked property that is not saved to the DB, its value is based on an enum and behind that is an involved process of pattern matching rules to select the appropriate enum value.
public partial class BlockedIp
{
private string patternMatch = IpPatternMatching.None.ToString();
public int? BlockedIpId { get; set; }
public string IpAddress { get; set; }
public string IpBlockType
{
get
{
return this.patternMatch;
}
set
{
if (value == IpPatternMatching.AllPatterns.ToString())
this.patternMatch = value;
else if (value == IpPatternMatching.DoubleBlock.ToString())
this.patternMatch = value;
else if (value == IpPatternMatching.SingleIP.ToString())
this.patternMatch = value;
else if (value == IpPatternMatching.SingleBlock.ToString())
this.patternMatch = value;
else if (value == IpPatternMatching.TrippleBlock.ToString())
this.patternMatch = value;
else
this.patternMatch = IpPatternMatching.None.ToString();
}
}
public Host Host { get; set; }
public int? HostId { get; set; }
[NotMapped]
public bool IsBlocked
{
get
{
if (this.IpBlockType == IpPatternMatching.AllPatterns.ToString())
return true;
else if (this.IpBlockType == IpPatternMatching.DoubleBlock.ToString())
return true;
else if (this.IpBlockType == IpPatternMatching.SingleBlock.ToString())
return true;
else if (this.IpBlockType == IpPatternMatching.SingleIP.ToString())
return true;
else if (this.IpBlockType == IpPatternMatching.TrippleBlock.ToString())
return true;
else
return false;
}
}
The Host class, on the other hand, will save these abstracted property values to the DB, but since they depend on values in other classes, they need to be read-only. Host looks like this with its abstracted value of IsBlocked:
public partial class Host
{
public int HostId { get; set; }
public string HostDomain { get; set; }
public string HostIP { get; set; }
public string HostName { get; set; }
public bool IsBlocked
{
get
{
return this.BlockedIp.IsBlocked;
}
set { }
}
public BlockedIp BlockedIp { get; set; }
public MxRecord MxRecord { get; set; }
public SmtpGeoLocation SmtpGeolocation { get; set; }
public MailExchanger MailExchanger { get; set; }
}
When I added a migration to create Host, EF complained about IsBlocked not having a setter, so I reluctantly added an empty setter to the class. EF accepted this and the app runs, but in my gut, I can't help but believe, such a rash choice will eventually come back to haunt me. I'd prefer something more practical or elegant but have come up empty after a day and half of trying alternatives.
In my modelbuilder, I did find a reference to Metadata.SetPropertyAccessMode that looks like this:
entity.Property(e => e.IsBlocked)
.HasColumnName("IsBlocked")
.HasColumnType<bool>("bit")
.IsRequired(true)
.Metadata.SetPropertyAccessMode(PropertyAccessMode.PreferProperty);
I could map the bool IsBlocked setter to a method that does the heavy lifting needed to match a bool value combined with an IP address, to one of these enum values, but that would be a lot of work to reverse a process that is already well established.
I've yet to read an article that explains the Metadata.SetPropertyAccessMode as well as the other values sufficiently enough to inform me if I'm heading down the right path by using it. Was hoping someone here has come up against a need for a read-only property in EF Core and could offer alternatives.
Upvotes: 1
Views: 1933
Reputation: 95
I tried using backing fields in combination with the modelbuilder meta tag .Metadata.SetPropertyAccessMode, and simply ignored any value applied directly to the setter.
entity.Property(e => e.IsBlocked)
.HasColumnName("IsBlocked")
.HasColumnType<bool>("bit")
.IsRequired(true)
.Metadata.SetPropertyAccessMode(PropertyAccessMode.PreferProperty);
Having a property with no data table backing it, having it reflect values in a related table by getting those values through an EF navigation property, proved to be more effort than simply denormalizing your database and keeping the same data two tables. A task which EF makes totally painless by not having to deal with the tedious effort of rolling your own data access objects. Thanks for the nudge in the right direction...
Upvotes: 0
Reputation: 983
A proper representation of a read-only property in SQL is a calculated column. But you said you didn't want to go that way.
When you save something to a database, EF Core (and literally everyone else) expects you to read it afterwards. Therefore all C# properties must be read-write - otherwise there is no way for any ORM to fill them in (we won't consider constructor parameters at the moment :-) ). Even for calculated properties - you have to fetch them from the database into C# world.
So I don't really see any other alternative than to have a setter.
Upvotes: 1
Reputation: 88852
You can use Backing Fields for this, which also helps explain what the PropertyAccessMode is.
Upvotes: 1