I have used the Entity Framework with VS2010 to create a simple person class with properties, firstName, lastName, and email. If I want to attach DataAnnotations like as is done in this blog post I have a small problem because my person class is dynamically generated. I could edit the dynamically generated code directly but any time I have to update my model all my validation code would get wiped out.
First instinct was to create a partial class and try to attach annotations but it complains that I'm trying to redefine the property. I'm not sure if you can make property declarations in C# like function declarations in C++. If you could that might be the answer. Here's a snippet of what I tried:
namespace PersonWeb.Models
{
public partial class Person
{
[RegularExpression(#"(\w|\.)+#(\w|\.)+", ErrorMessage = "Email is invalid")]
public string Email { get; set; }
/* ERROR: The type 'Person' already contains a definition for 'Email' */
}
}
A buddy class is more or less the direction your code snippet is journeying, except your manually coded partial Person class would have an inner class, like:
[MetadataType(typeof(Person.Metadata))]
public partial class Person {
private sealed class MetaData {
[RegularExpression(...)]
public string Email { get; set; }
}
}
Or you could have your manually partial Person class and a separate Meta class like:
[MetadataType(typeof(PersonMetaData))]
public partial class Person { }
public class PersonMetaData {
[RegularExpression(...)]
public string Email;
}
These are workarounds and having a mapped Presentation class may be more suitable.
You need to either use a metadata "buddy" class or (my preference) project onto a presentation model instead of binding views directly to entities.
Related
I'm following along at Model binder for abstract class in asp.net core mvc 2, but my model doesn't bind the HostedControls in ConcreteControlHost (see below). If I change the type of HostedControls to ConcreteControlText[], it does bind. I'd like to keep it as AbstractControl[] so that I can host multiple types of control.
I know the abstract binder is working because MainModel.Controls binds.
While debugging the binding of ConcreteControlHost, binder._propertyBinders has an entry for HostedControls as follows:
{[ModelMetadata (Property: 'ConcreteControlHost.HostedControls' Type: 'AbstractControl[]'), {Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinder<MyProject.AbstractControl>}]}
Following that property (Value.ElementBinder.Inner) eventually leads to AbstractModelBinder.
Breakpoints in AbstractModelBinder are not hit when binding the properties of ConcreteControlHost, but are when binding the properties of MainModel (as in, I get the hits for ConcreteControlHost, but not for ConcreteControlText).
This isn't related to In an Editor Template call another Editor Template with the same Model because it isn't the same model, and because everything renders correctly, it just doesn't bind. None of the ConcreteControlTexts referenced by HostedControls are referenced directly by MainModel.Controls.
public class MainModel {
public AbstractControl[] Controls;
}
public abstract class AbstractControl {
public string TypeName { get; set;}
}
public class ConcreteControlText: AbstractControl {
public string Text { get; set; }
}
public class ConcreteControlHost: AbstractControl {
public AbstractControl[] HostedControls { get; set; }
}
Does anyone see what I need to change to allow model-binding to work on ConcreteControlHost.HostedControls?
Turns out in my actual code, HostedControls had { get; private set; }. Removing the private modifier on the setter made it work.
Jeremy, thanks for looking into this.
I want to add a new property on my class, make it strongly typed so I can use it in my views and controllers, I've tried to inherit the properties, but Entity Framework or C# throws me errors...
I have this class:
public class Patient
{
public int Id { get; set; }
public string Message { get; set; }
public string FirstName { get; set; }
.....
}
which has a lot more properties in it, but shortened here.
I have a razor view, which is uses 'Patient' as it's model
using model Project.Models.Patient
So I had completed my view (or so I thought) and was asked to add functionality in the view. The functionality is to send a POST using a form of a 'Message' (a simple textarea in html). I've already got all the details I want, but this new 'Message'
So I thought, because I don't want this field in the database I could add it like this:
public class Patient
{
public int Id { get; set; }
public string Message { get; set; }
public string FirstName { get; set; }
[NotMapped]
public string Message { get; set; }
.....
}
But I'm not a fan of this, it doesn't relate to the Patient in any other way.
So I thought I could change my model in razor to something like this:
#model Project.Models.DTOs.PatientMessage
and inherit the Patient class and all it's properties (so I don't have to retype and copy past the fields again) and the new PatientMessage class would look like this:
public class PatientMessage : Patient
{
public string Message { get; set; }
}
But when I refresh my application, I receive a message stating the Application Database Context has changed, and I have to update this. I don't want to update my database, and I can't really see why I need to, it's an extra field which I don't want to include in my database.
So then I decided to make this class an 'abstract' class
public abstract class PatientMessage : Patient
{
public string Message { get; set; }
}
When I refreshed my page this time, I saw no need to update the Database, great I thought, and when I went near a page where the model was
#model Project.Models.Patient
I received this message
The abstract type 'Project.Models.DTOs.PatientMessage' has no mapped descendants and so cannot be mapped. Either remove 'Project.Models.DTOs.PatientMessage' from the model or add one or more types deriving from 'Project.Models.DTOs.PatientMessage' to the model.
MY QUESTION
Can I include this one field, without placing it on the Patient class, ideally without having to update models in my razor views, or would I have to change the models in the views and controllers and update the information to include the message and map all the details from a 'PatientMessage' to a 'Patient'
Please let me know if you need any further information.
Regards
Good day!
I have created an EF model from database using database first aproach,
and added myself several read only properties to entity class generated by EF which are not in database.
Every time I update my model adding data from new tables
I loose properties created, so I have to recreate them.
As an example in database I have property isFemale but in my class I've created
public string Gender
{
get
{
if(isFemale) return "female";
else return "male";
}
}
My question is there a way to update the model from database, leaving properties generated by me?
Thank you!
Add the properties on another Partial Class instead of the generated class. For example, if your generated class is of type Person, define another Partial class in the same project with the same namespace:
public partial class Person
{
public string Gender
{
get
{
if(isFemale) return "female";
else return "male";
}
}
}
Using partial class will solve your problem but:
All parts of partial class must be defined in the same assembly
Properties from your partial part are not persisted to the database
Properties from your partial part cannot be used in linq-to-entities
queries
read more
You could make your class partial and seperate it in two files, this is the way I use it with DatabaseFirst.
public partial class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public partial class Person
{
public string FullName {
get
{
return FirstName + " " + LastName;
}
}
}
I'm wondering if there is some new functionallity regarding data annotations in EF 5 or newer. I'm doing database-first so that as far as I know, I have to manipulate the T4 all the time which is quite a mess because with each EF-Version MS changes the default T4. So can someone tell me, if there is some alternative to things like that:
Manipulated T4:
<#=codeStringGenerator.UsingDirectives(inHeader: false, includeAnnotations: true)#>
[MetadataType(typeof(<#=code.Escape(entity)#>Metadata))]
<#=codeStringGenerator.EntityClassOpening(entity)#>
Resulting class sample if class is named 'Address':
[MetadataType(typeof(AddressMetadata))]
public partial class Address
{
Additional class for annotations in same namespace as generated 'Address'-class:
public class AddressMetadata
{
[Display(Name = "Straße")]
public string Street;
[Display(Name = "Land")]
public string Country;
[Display(Name = "PLZ")]
public string Zip;
[Display(Name = "Stadt")]
public string City;
}
Thats the way I do it today.
You don't need to change the generated code. You can add the buddy class to a partial class. Then, when the code is regenerated, you don't lose anything.
I have a partial class in a dbml file.
public partial class Comment
string email
Clearly I can't put a decorator on it because this is a generated file and you shouldn't make change in it yourself.
So I created another partial class;
public partial class Comment
[IsEmailAddress]
string email
The above doesn't work but I need something like that so I can validate the email address on the model.
You should used MetadataType like so...
[MetadataType(typeof(CommentMetadata))]
public partial class Comment {
}
public class CommentMetadata {
[IsEmailAddress]
public string email {get;set;}
}
That will allow you to add your attributes without it being overridden the next time you update your models.