Extend EF 6.2 with new mapping rules - entity-framework

NHibernate can be extended with new implementations of IUserType, so I can customize how a mapped property is read and stored to/from the database.
An example. If I want DB null varchar to load as "n/a" string, and "n/a" string to be stored as null.
How is this possible with EF 6.2?
I am looking for a solution that doesn't break the change-tracker.

As of EF 6.2, there is no such functionality provided out of the box by the library.
If you decide to move to EF Core instead, there you can use the HasConversion functionality.
However, in your case you still wouldn't be able to use it, because there is one caveat: it can't be used to convert null values. Null always gets converted to null. From docs:
A null value will never be passed to a value converter. A null in a database column is always a null in the entity instance, and vice-versa. This makes the implementation of conversions easier and allows them to be shared amongst nullable and non-nullable properties. See GitHub issue #13850 for more information.
In that case, I suggest that instead of a Value Conversion you configure your string property to have a Backing Field. Then, you can read/write to/from the private backing field, and then have a public property handling the null value.
public class Blog
{
private string _stringFromDb;
public string MyString { get; set; }
[BackingField(nameof(_stringFromDb))]
public string MyString
{
get { return _stringFromDb ?? "n/a"; }
}
public void SetMyString(string myString)
{
// put your validation code here
_stringFromDb = myString;
}
}
In EF 6.2 the closest you could have, as a workaround, is a [NotMapped] property that can be in charge of translating the property you load from the DB.
public string StringDB { get; set; }
[NotMapped]
public string StringConverted
{
get { return MyStringProperty ?? "n/a"; }
set { MyStringProperty = value }
}
If, in addition to this, you want to hide the property being mapped to your DB by making it private, it's not as straightforward as with EF Core's backing field, but you could follow this other answer for instructions on how to achieve it.

Related

How can I prevent the default value of DateTimeOffset from being inserted?

I'm using Entity Framework Core 3.1.7 and created an entity called Event, which I set up like this:
public class Event
{
public long Id { get; set; }
public DateTimeOffset FirstOccurred { get; set; }
}
The entity configuration looks like this:
builder.Property(e => e.FirstOccurred)
.IsRequired();
I use my dbContext to persist the entity like this:
await dbContext.Events.AddAsync(new Event());
In this scenario, I was incorrectly expecting that an exception would be thrown at the Database level because the value can't be null.
What actually happens is: the entity is happily persisted with FirstOccurred set to 0001-01-01T00:00:00+00:00
This makes sense, because the default value of DateTimeOffset is used.
Now my question: How could I improve my design to prevent this default value from being inserted?
Some ideas I had already:
Leave the above code as is, but make sure that wherever the entity is used, I'm setting the values correctly. Downside: no guarantee that this will be applied consistently in a team over time.
Make DateTimeOffset nullable, which in the above AddAsync() call would actually cause an SQL exception. Downside: At first glance, DateTimeOffset? FirstOccurred might be confusing because the actual DB constraints don't allow null
Remove set; for FirstOccurred and create a constructor that requires this property to be set, e.g. new Event(DateTimeOffset.Now)
I think you're on the right track with your last idea.
Remove set; for FirstOccurred and create a constructor that requires this property to be set, e.g. new Event(DateTimeOffset.Now)
It doesn't make sense to track an Event without the timestamp and it certainly doesn't make sense to use the default value for the timestamp.
Changing your model to require a value for the timestamp ensures that you are not writing default data to the record and prevents confusion from seeing a nullable model field when the corresponding table column is non-nullable.
public class Event {
public Event (DateTimeOffset firstOccurred) { FirstOcurred = firstOcurred; }
public long Id { get; set; }
public DateTimeOffset FirstOccurred { get; set; }
}
Just a note from the documentation, don't remove the set; accessor, just mark it private if you don't want the value to change after construction.
Once properties are being set via the constructor it can make sense to make some of them read-only. EF Core supports this, but there are some things to look out for:
Properties without setters are not mapped by convention. (Doing so tends to map properties that should not be mapped, such as computed properties.)
Using automatically generated key values requires a key property that is read-write, since the key value needs to be set by the key generator when inserting new entities.
An easy way to avoid these things is to use private setters.
Of course, you could also maintain the flexibility of the parameter-less constructor by overriding the default value for the property.
public class Event {
public long Id { get; set; }
public DateTimeOffset FirstOccurred { get; set; } = DateTimeOffset.Now;
}

EF Lazy load proxy interceptor

I'm looking for a way to intercept Entity Framework's lazy load proxy implementation, or otherwise control what is returned when accessing a Navigation property that may have no value in the database.
An example of what I'm looking for is this Contact class with mailing address, business phone, etc. that may or may not have a contact person.
public partial class Contact
{
private Nullable<System.Guid> _personId;
public Nullable<System.Guid> PersonId
{
get { return _personId; }
set { SetProperty(ref _personId, value); }
}
public virtual Person Person{ get; set; }
// mailing address, other properties...
}
public partial class Person
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
private string _lastName;
public string LastName
{
get { return _lastName;}
set { SetProperty(ref _lastName;value); }
}
}
It is very useful in ASP.net Razor pages, WPF or ad-hoc reporting tools, to be able to use expressions like:
Contact c = repo.GetContact(id);
Console.WriteLine("Contact Person " + c.Person.FirstName);
Which of course fails if there is no PersonId, and hence contact.Person is null.
Tools like Ideablade Devforce have a mechanism to return a "NullEntity" for Person in this case, which allows the WriteLine to succeed. Additionally, the NullEntity for Person can be configured to have a sensible value for FirstName, like "NA".
Is there some way to override the Dynamic Proxy mechanism in EF, or otherwise intercept the reference to Person from Contact to enable this scenario?
I have investigated IDbCommandInterceptor, but that does not seem to intercept virtual navigation to individual entity properties, only navigation to entity collections.
Update _____________________________________
To elaborate on my original question, I can't modify the expression by introducing null conditional operators into the them, as these expressions are incorporated into WPF, ASP.Net Razor binding expressions, and/or report data fields, created by other developers or authors. Also, there may be multiple layers of null properties to deal with, e.g. Contact.Person.Spouse.FirstName, where either Person and/or Spouse might be a "null" property. The Devforce Ideablade implementation deals with this perfectly, but is unfortunately not an option on my current project.
you can use a null-conditional operator from c# like this
c.Person?.FirstName
This means that when Person == null , return null or otherwise return FirstName. You would still need to handle the null value
See : https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-

Order of mapping properties when loading from database

Let's say I have this code:
public bool Important { get; set; }
private bool _dependsOnImportant;
public bool DependsOnImportant
{
get;
set
{
if (value && !Important)
throw new InvalidOperationException();
_dependsOnImportant = value;
}
}
I want to protect setting DependsOnImportant to true, so that it is only possible when Important is true. This is POCO class. According to: Entity Framework 4.1 - Code first. Doesn't EF override my virtual members? my setter will be used by EF when loading object from database.
How can I be sure that Important has already been set with data from database when DependsOnImportant setter is called? Will EF be smart enough to detect that one setter is using another and load that data first? I don't think so. I think a new object is created with default constructor (setting all fields to default values) and then setters are invoked to populate the object.
So my questions are: Can we determine the order in which setters are called? But most importantly: Do we want to have that knowledge?

How do I map a char property using the Entity Framework 4.1 "code only" fluent API?

I have an object that has a char property:
public class Product
{
public char Code
{
get;
set;
}
}
Entity Framework doesn't seem to be able to map chars (this field is missing from the database when I create the database schema from my model objects). Is there anyway I can map the char (e.g. to a string) using the fluent API? I don't want to change the model objects as they are part of a legacy shared library.
Char is not valid primitive type for entity framework = entity framework doesn't map it. If you check CSDL reference you will see list of valid types (char is not among them).
Database char(1) is translated as string (SQL to CSDL translation). Char is described as non-unicode string with fixed length 1.
The only ugly option is second mapped property using string and your char non-mapped property will just use string[0] from that property. That is just another example how some simple type mapping or converters are damn missing in EF.
In Fluent API you can specify the database column data type using the HasColumnType method like this:
modelBuilder.Entity<Product>()
.Property(p => p.Code)
.HasColumnType("char");
According to Andre Artus' answer here, HasColumnType is available in EF4.1.
For those using Data Annotations, the ColumnAttribute can accomplish the same thing.
[Column(TypeName="char")]
public string Code { get; set; }
[Column( TypeName = "char(1)" )]
works for me with EF core 3.1.4
I have tried all the ways I have imagined and I must say the the accepted answer is the unique way to solve the problem of the char type as far as I know.
The char type isn't available for its use in EntityFramework.
Fluent API is included in this restriction.
If you try to put a char on the Property(p => p.MyCharProperty) will give you an Exception.
That means that char properties aren't available for Fluent API nor Attributes.
The easiest solution is this (as proposed by Ladislav Mrnka).
public class Product
{
public char Code { get; set; }
[Column("Code", TypeName="char")]
[MaxLength(1)]
public string CodeString
{
get { return Code.ToString(); }
set { Code = value[0]; }
}
}
One note: you can't put the property private, protected or internal. Must be public.
Fluent API version would be like this.
public class Product
{
public char Code { get; set; }
//We need the property but we will use the Fluent API to replace the attributes
public string CodeString
{
get { return Code.ToString(); }
set { Code = value[0]; }
}
}
modelBuilder.Entity<Product>().Property(p => p.Code)
.HasTypeName("char")
.HasMaxLength(1)
There is alternate ways to tackle this issue for TESTING purpose only. Make the field not null to null able for time being from design mode. Sometime it is restricted SQL Management Studio. (Change setting Tools -> Option ->Designer -> Table Database designer -> Uncheck "Prevent saving changes that required table creation"

How to change ErrorMessage property of the DataAnnotation validation in MVC2.0

My task is to change the ErrorMessage property of the DataAnnotation validation attribute in MVC2.0. For example I should be able to pass an ID instead of the actual error message for the Model property and use that ID to retrieve some content(error message) from a another service e.g database, and display that error message in the View instead of the ID. In order to do this I need to set the DataAnnotation validation attribute’s ErrorMessage property.
[StringLength(2, ErrorMessage = "EmailContentID.")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
It seems like an easy task by just overriding the DataAnnotationsModelValidatorProvider ‘s
protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
However it seems to be a complicated enough.
a. MVC DatannotationsModelValidator’s ErrorMessage property is read only. So I cannot set anything here
b. System.ComponentModel.DataAnnotationErrorMessage property(get and set) which is already set in MVC DatannotationsModelValidator so we cannot set again. If you try to set you get “The property cannot set more than once…” error message appears.
public class CustomDataAnnotationProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
IEnumerable<ModelValidator> validators = base.GetValidators(metadata, context, attributes);
foreach (ValidationAttribute validator in validators.OfType<ValidationAttribute>())
{
messageId = validator.ErrorMessage;
validator.ErrorMessage = "Error string from DB And" + messageId ;
}
//......
}
}
Can anyone please help me on this?
Here is the question: What is your motivation to changing the error message property?
Think this through very carefully, as you are heading down a path where you are obfuscating what is actually happening in the application. Certainly the database informatino is useful, but it is not really part of the validation, nor should it be.
When you head in this direction, you are essentially saying that the validation can only be invalid if there is a database problem. I see two issues with this:
It breaks the separation of concerns. You are reporting a persistance error in the model, which is not where it occurred.
The solution is not unit testable, as you must engage the database.
I don't like either of the two above.
Can you solve this? Possibly if you will create your own custom validation attribute. I would have to check and ensure that is correct. Another option is to aim for custom validation:
http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx
This article can also help you head in the direction you desire:
http://ryanrivest.com/blog/archive/2010/01/15/reusable-validation-error-message-resource-strings-for-dataannotations.aspx
Do you want to solve this? Not really if you are attempting to keep a proper separation of concerns in your application. I would not polute my validation error message (this is not valid) with a database error (I am not valid, but the database also blew up). Just my two cents.
There are built in ways to get the error message via a resource. Instead of a database lookup to get a resource at runtime, generate resources from your database and use that for your error messages.
You can then use the ErrorMessageResourceName and ErrorMessageResourceType to allow the DataAnnotation to perform a resource lookup instead of hard-coding a specific string.
public sealed class MyModel
{
[Required(
ErrorMessageResourceName="MyDescriptionResource",
ErrorMessageResourceType=typeof(MyCustomResource))]
public string Description { get; set; }
}
Also you may want to have a look at ValidationAttribute.FormatErrorMessage Method on msdn.
This method formats an error message
by using the ErrorMessageString
property. This method appends the name
of the data field that triggered the
error to the formatted error message.
You can customize how the error
message is formatted by creating a
derived class that overrides this
method.
A quick sample (and not meant to be a definitive example)
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false,
Inherited = true)]
public sealed class PostCodeValidationAttribute
: ValidationAttribute
{
public override bool IsValid(object value)
{
if( value == null )
return true;
string postCode = value as string;
if( string.IsNullOrEmpty(postCode) )
return true;
if ( !PostCode.IsValidPostCode(postCode, this.PostCodeStyle) )
return false;
return true;
}
public PostCodeStyle PostCodeStyle { get; set; }
public override string FormatErrorMessage(string name)
{
return string.Format(
"{0} is not a valid postcode for {1}", name, PostCodeStyle);
}
}
* I've omitted the PostCodeStyle enumeration as well as the PostCode class for validating a postcode.