Database First Validation - entity-framework

I have an auto-generated Entity Framework model. It was generated using a database first approach. The mid_initial column has a database defined constraint that limits the column to a maximum length of 3 characters.
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Agency.DataAccess.RegistrationModel
{
using System;
using System.Collections.Generic;
public partial class Registrant
{
public Registrant()
{
}
public int id { get; set; }
public string fname { get; set; }
public string mid_initial { get; set; }
public string lname { get; set; }
}
}
When I try and create a model with a mid_initial greater than 3 characters, a invalid state, ModelState.IsValid is returning true. Because of this db.SaveChanges is then called, which then raises DbEntityValidationException.
[HttpPost]
public ActionResult Create(Registrant registrant)
{
try
{
if (ModelState.IsValid)
{
Debug.WriteLine("Entity was valid.");
db.Registrants.Add(registrant);
db.SaveChanges();
return RedirectToAction("Index");
}
return View("Create", registrant);
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Debug.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Debug.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
return View(registrant);
}
}
Why is the ModelState.IsValid method returning true? It would seem that my model is not aware of the maximum length constraint. How do I make it aware?

EF db-first can't infer constraints from database.
Use the MaxLenght data annotation attribute:
public partial class Registrant
{
public Registrant()
{
}
public int id { get; set; }
public string fname { get; set; }
[MaxLength(3, ErrorMessage = "")]
public string mid_initial { get; set; }
public string lname { get; set; }
}
Note: this class is a auto generated class and every time you update and save your model (.EDMX file), this code will be overwritten and you'll loose your attributes.
To avoid that, you should extend your classes with some partial classes with the same name and same namespace as your auto-generated classes. If you need examples to show you how, tell me to put it in answer.

MVC is EF-agnostic, and as such doesn't implicitly attempt to validate the model using EF validation to populate its ModelState.
You have four basic solutions I can think of right now:
Hook them up yourself, for example using MVC filters, DbContext.GetValidationErrors and ModelState.
Find and use third-party code that does this already.
Validate the code separately using facilities that MVC can use, for example using DataAnnotations. You may try to generate them automatically by modifying the EF T4 template. Note that this is still technically redundant (the code will be validated twice, once by MVC, once by EF).
Submit a patch for MVC so that it can support EF explicitly (as a soft dependency) and make it all just work (both projects are open source) -- or downvote me because they already did so and I never knew it.

Related

Entity Framework CORE Seeding Joining table

I am working on .NET CORE 6 along with EF CORE 7. I need to seed data in joining table but unable to do so and get error.
I am seed FileTypeId but not sure why EF core migration throwing error...
error
The seed entity for entity type 'JobFileType' cannot be added because it has the navigation 'FileType' set. To seed relationships, add the entity seed to 'JobFileType' and specify the foreign key values {'FileTypeId'}. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the involved property values.
ClassA
public class JobProfile
{
public JobProfile()
{
this.JobFileTypes = new HashSet<JobFileType>();
}
public Guid JobProfileId { get; set; }
public string Name { get; set; }
public ICollection<JobFileType>? JobFileTypes { get; set; }
}
ClassB
public class FileType
{
public FileType()
{
this.JobFileTypes = new HashSet<JobFileType>();
}
public Guid FileTypeId { get; set; }
public string Extension { get; set; } = string.Empty;
public ICollection<JobFileType>? JobFileTypes { get; set; }
}
Joing Table
public class JobFileType
{
public Guid JobFileTypeId { get; set; }
public Guid JobProfileId { get; set; }
public JobProfile JobProfile { get; set; } = new JobProfile();
public Guid FileTypeId { get; set; }
public FileType FileType { get; set; } = new FileType();
}
Seed Extension
public static class JobFileTypeSeed
{
public static void Seed(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<JobFileType>()
.HasData(
new JobFileType {JobFileTypeId = Guid.Parse("aaa"), JobProfileId = Guid.Parse("ccc"), FileTypeId = Guid.Parse("yyy") },
new JobFileType { JobFileTypeId = Guid.Parse("bbb"), JobProfileId = Guid.Parse("ccc"), FileTypeId = Guid.Parse("zzz") }
);
}
}
config
internal class JobFileTypeConfiguration : IEntityTypeConfiguration<JobFileType>
{
public void Configure(EntityTypeBuilder<JobFileType> builder)
{
builder.ToTable("JobFileType", "dbo");
builder.HasKey(column => column.JobFileTypeId);
builder
.HasOne(jobFileType => jobFileType.JobProfile)
.WithMany(jobProfile => jobProfile.JobFileTypes)
.HasForeignKey(jobFileType => jobFileType.JobProfileId);
builder
.HasOne(jobFileType => jobFileType.FileType)
.WithMany(fileType => fileType.JobFileTypes)
.HasForeignKey(jobFileType => jobFileType.FileTypeId);
}
}
There is not much to say about the concrete issue (which btw is not specific to joining entity, but any entity model seeding):
I am seed FileTypeId but not sure why EF core migration throwing error...
as the cause of the issue is included at the beginning of the error message:
because it has the navigation 'FileType' set.
And your entity has
public FileType FileType { get; set; } = new FileType();
// ^ ^ ^
// the problem
and the same for
public JobProfile JobProfile { get; set; } = new JobProfile();
which will be the next error if you resolve the original.
Remove both navigation property initializers (= new ...) and the problem will be gone.
As a general rule, you should never initialize reference navigation properties because it causes many side effects and/or improper behaviors (not only for seeding, but also eager/lazy/explicit data loading). Initializing collection navigation properties is arbitrary, but ok. Only reference navigation property initialization must be avoided. For more info, see EF codefirst : Should I initialize navigation properties? - quite old EF topic, but still applies.
If you are trying to resolve NRT warnings (as I guess), initializing with new is definitely not a proper way. One reason I don't like NRT is because it is forcing people to use "workarounds" for preventing compiler warnings, which in fact break the primary functionality. Specifically in EF Core, enabling NRT also changes the optional/required attribute of some properties, hence database column types (most noticeable for string properties/columns and reference navigations). You could read more about this in the Working with Nullable Reference Types topic in the official EF Core documentation, but in general I would just disable NRT for EF entity model classes.
The proper order is to set the "master data" first and then try to set the join table, as you would expect.
The defaulting
{get;set;} = new Something();
Could be the offending declaration, since any instance upon creation will have the relation JobFileType already set

How to ignore properties marked with [IgnoreDataMember] when calling REST service

I am consuming a REST Xml service.
I have all the necessary classes to do this, (supplied by the dev who wrote the service) but at my end I have to save some of the responses to the DB to perform the tasks of the app I am writing.
So I have marked some of these classes I need to put in the DB as partial and extended them so that I can inherit from a DbEntity class which specifies an ID property so I can use EF to save them to the DB thus:
public interface IDbEntity
{
int ID { get; set; }
}
[Serializable]
public class DbEntity : IDbEntity
{
[IgnoreDataMember]
[XmlIgnore]
public int ID { get; set; }
}
the problem I am facing now, is that when the service call is being de-serialized I get the error
Error in line 1 position 113. 'Element' 'ElementName' from namespace '' is not expected. Expecting element '_x003C_ID_x003E_k__BackingField'
I am simply making the call like this:
var response = await client.PostAsXmlAsync<TReq>("Some/API/Call", req);
TResp val = await msg.Content.ReadAsAsync<TResp>(response)
all the properties in the original classes have Orders specified with their DataMember attributes and I have clearly marked my DB properties to be Ignored, but to no avail.
is there any way I can get this to work? - ie getting the DataContractSerializer to actually ignore the properties I have marked to be ignored when de-serializing?
as an aside, these ignored properties are also being passed to the service when making a call - does IgnoreDataMember actually do anything?
seems that the way to do this is like this
public interface IDbEntity
{
int ID { get; set; }
}
[Serializable]
[DataContract]
public class DbEntity : IDbEntity
{
[XmlIgnore]
public int ID { get; set; }
}
so basically adding the DataContract Attribute but omitting the DataMember attribute on the item you don't want
don't know how I missed that first time around. seems its opt in rather than opt out in this instance.

Update object when using entity splitting - code first

I had a class called Document, which I split into two entities, in order to separate an expensive binary field:
[Table("Document")]
public class Document
{
[Key]
public int Id { get; set; }
... other fields ...
[Required]
public virtual DocumentBinary DocumentBinary { get; set; }
}
[Table("Document")]
public class DocumentBinary
{
[Key, ForeignKey("Document")]
public int DocumentId { get; set; }
public Document Document { get; set; }
public byte[] DocumentData { get; set; }
}
So, everything works fine, both entities share the same database table and DocumentData is only loaded when it's needed.
However, when it comes to updating the Document entity, I get an error stating that 'DocumentBinary is required'.
When I remove the [Required] attribute from DocumentBinary virtual property, I get the following error:
The entity types 'Document' and 'DocumentBinary' cannot share table 'Documents' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.
I can obviously do something like:
var test = document.DocumentBinary;
before updating the document object:
documentRepository.Update(document);
This will then load the binary data on my request and save the changes without any issues, but the whole point is that I shouldn't need to do that.
This can be achieved using the fluent API. If you remove the data annotations and in your OnModelCreating add this, it should work.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Document>().HasRequired(d => d.DocumentBinary).
WithRequiredDependent(db => db.Document);
}
I managed to resolve it by overriding my Update method in DocumentRepository:
public override void Update(Document document)
{
try
{
DataContext.Entry(document.DocumentBinary).State = EntityState.Modified; // added this line
DataContext.Entry(document).State = EntityState.Modified;
}
catch (System.Exception exception)
{
throw new EntityException("Failed to update document");
}
}
I know it probably does the same thing as me evaluating DocumentBinary by assigning it to 'test' variable, but it looks like a much cleaner solution.

ef5 database first data annotation

I am starting MVC4 with VS2012. I am also using EF5 with the "Database First" method of creating my classes.
However because the generated glasses can be regenerated I cannot put the Data Annotation details to assist with validation.
I have seen some code snippets that use MetaData and partial classes but I was wondering if anyone knows of a small compilable example that I can look at and pull apart to better understand how the vasious classes interlink.
Many many thanks for any help.
Dave
You can achieve what you need through extending models. Suppose that EF generated the following entity class for you:
namespace YourSolution
{
using System;
using System.Collections.Generic;
public partial class News
{
public int ID { get; set; }
public string Title { get; set; }
public int UserID { get; set; }
public virtual UserProfile User{ get; set; }
}
}
and you want do some work arounds to preserve your you data annotations and attributes. So, follow these steps:
First, add two classes some where (wherever you want, but it's better to be in Models) like the following:
namespace YourSolution
{
[MetadataType(typeof(NewsAttribs))]
public partial class News
{
// leave it empty.
}
public class NewsAttribs
{
// Your attribs will come here.
}
}
then add what properties and attributes you want to the second class - NewsAttribs here. :
public class NewsAttrib
{
[Display(Name = "News title")]
[Required(ErrorMessage = "Please enter the news title.")]
public string Title { get; set; }
// and other properties you want...
}
Notes:
1) The namespace of the generated entity class and your classes must be the same - here YourSolution.
2) your first class must be partial and its name must be the same as EF generated class.
Go through this and your attribs never been lost again ...

Entity Framework error when submitting empty fields

VS 2010 Beta 2, .NET 4.
In my ASP.NET MVC 2 application, when I submit a form to an action method that accepts an object created by the entity framework, I get the following error:
Exception Details: System.Data.ConstraintException: This property cannot be set to a
null value.
Source Error:
Line 4500: OnTextChanging(value);
Line 4501: ReportPropertyChanging("Text");
Line 4502: _Text = StructuralObject.SetValidValue(value, false);
Line 4503: ReportPropertyChanged("Text");
Line 4504: OnTextChanged();
The property is called "Text" and is of type "text NOT NULL" in MS SQL 2008.
My action will check if the value is nullorempty, if it is, a model error will be added, but I get the error as soon as I submit the form.
Are you binding directly to the entity? Sure looks like it. So you have two choices:
Write a custom model binder which translates null -> empty string.
Bind to an edit model which allows nulls instead, and then change this to empty string when you copy the values to the entity in the action.
I'd choose #2, personally. I think you should always use view/edit models, and this is a great example of why.
I was having the same problem. I looked around and found a work around here. It describes the problem as being caused by the EF validation taking place before the Required field validation. It also shows how we can work around this problem by using a [DisplayFormat] Tag. Hope this will help you.
Here's the link to the question and the workaround:
Server-side validation of a REQUIRED String Property in MVC2 Entity Framework 4 does not work
Is this an issue with the MVC2 and Entity Framework 4 or is this by design? It appears that validation of EF properties works fine for datetime non-nullable (required) fields and data type validation of numeric versus string fields is working without having to use ViewModels.
I recreated the issue using with a simple FOOBAR table using a single, non-nullable varchar(50) column called barName in slq 2008. I generated the EF model from that database and quickly added a controller and a CREATE view for the FOOBAR entity. If I try to POST to the CREATE action without entering in a value for the property barName, VS steps into an exception within the designer.cs file of the model (just like the one above). When, I try to step past the exception, the validation message shows up on the form and the field is highlighted in pink.
It seems like something is not firing in the correct sequence. Because the exception occurs before VS steps into the HTTPPOST CREATE method.
I found the code from the ASP.Net MvcMusicStore sample helpful. http://mvcmusicstore.codeplex.com/releases/view/44445#DownloadId=119336
It appears that binding to the ViewModel fixes the issue.
namespace MvcMusicStore.ViewModels
{
public class StoreManagerViewModel
{
public Album Album { get; set; }
public List<Artist> Artists { get; set; }
public List<Genre> Genres { get; set; }
}
}
........
namespace MvcMusicStore.Models
{
[MetadataType(typeof(AlbumMetaData))]
public partial class Album
{
// Validation rules for the Album class
[Bind(Exclude = "AlbumId")]
public class AlbumMetaData
{
[ScaffoldColumn(false)]
public object AlbumId { get; set; }
[DisplayName("Genre")]
public object GenreId { get; set; }
[DisplayName("Artist")]
public object ArtistId { get; set; }
[Required(ErrorMessage = "An Album Title is required")]
[StringLength(160)]
public object Title { get; set; }
[DisplayName("Album Art URL")]
[StringLength(1024)]
public object AlbumArtUrl { get; set; }
[Required(ErrorMessage = "Price is required")]
[Range(0.01, 100.00, ErrorMessage="Price must be between 0.01 and 100.00")]
public object Price { get; set; }
}
}
}
Ashish Shakya's answer helped me. I added this attribute to the property and now it works.
[DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText="")]
So it looks like this:
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
[DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText="")]
public global::System.String MyProperty
{
get
{
return _MyProperty;
}
set
{
OnMyPropertyChanging(value);
ReportPropertyChanging("MyProperty");
_MyProperty = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("MyProperty");
OnMyPropertyChanged();
}
}
Import the namespace:
using System.ComponentModel.DataAnnotations;
And add the attribute property [Required]
[Required]
public global::System.String MyProperty
{
get
{
return _MyProperty;
}
set
{
OnMyPropertyChanging(value);
ReportPropertyChanging("MyProperty");
_MyProperty = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("MyProperty");
OnMyPropertyChanged();
}
}
Thus ModelState.IsValid equals false, showing error message in the validation and will not fail on the server with Null.
I had the same problem and fixed it by making false to true like this:
Line 4502:
_Text = StructuralObject.SetValidValue(value, false);
I just had the same problem myself, and came here to find the solution. However, the answer can be enhanced.
Svavar's and HackITMngr were on the right track, however combining both gives the best outcome. You don't want to go decorating the generated classes, as you risk losing your custom changes upon modifications to the EF model.
[MetadataType(typeof(MyTableMetaData))]
public partial class MyTable
{
// Validation rules for the Album class
public class MyTableMetaData
{
[DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText="")]
public string MyTextProperty { get; set; }
}
}
To settle any arguments between the two. I'd say Svavar's was the direct answer, HackITMngr was the enhancement.
Works great for me!
I set StoreGeneratedPattern property as Computed for each field and it solved the problem for me.