Using Dataannotations with Entity Framework

Using DataAnnotations with Entity Framework

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.

Using Entity Framework Data Annotations to Achieve One to Many Relationship With Eager Loading

You are having troubles because in the one-to-many relationship, the foreign key should be defined at the many side and should relate to the primary key of the one side.

Your model should be something like this:

public class Part {

[Key]
public string PartID { get; set; }
public string PartNumber { get; set; }

[ForeignKey("MaterialID")]
public virtual Material Material { get; set; }
public int MaterialID { get; set; }

public virtual ICollection<QualityMeasurement> Qualities { get; set; }
}

public class QualityMeasurement {

[Key]
public int QualityID { get; set; }

[ForeignKey("PartID")]
public virtual Part Part { get; set; }
public string PartID { get; set; }

public double UnitWeight { get; set; }
}

Entity Framework 6 Reusing Data Annotations

Here's a solution I've come up with after considering the other possible solutions posed. It's based on finding the following article...

http://jendaperl.blogspot.co.uk/2010/10/attributeproviderattribute-rendered.html

My reasons for this over others are stated at the bottom of this post.

Having an existing Data Model class of...

public class UserAccount
{
[Display(Name = "Username", Prompt = "Login As"), Required()]
string UserName { get; set; }
[Display(Name = "Password", Prompt = "Password"), Required(), DataType(DataType.Password), StringLength(255, MinimumLength = 7, ErrorMessage = "The password must be at least 7 characters")]
string Password { get; set; }
}

wouldn't it be great if we could just copy the property attributes into a view that use it, maybe override them if we need to so that the views that use this class don't need to be revisted on a simple attribute change. Here's my solutions. Create a new attribute as below...

using System.ComponentModel;

namespace MyApp.ViewModels
{
public class AttributesFromAttribute : AttributeProviderAttribute
{
public AttributesFromAttribute(Type type, string property)
: base(type.AssemblyQualifiedName, property)
{
}

public T GetInheritedAttributeOfType<T>() where T : System.Attribute
{
Dictionary<string,object> attrs = Type.GetType(this.TypeName).GetProperty(this.PropertyName).GetCustomAttributes(true).ToDictionary(a => a.GetType().Name, a => a);
return attrs.Values.OfType<T>().FirstOrDefault();
}

}
}

now you can just add the following to the relevant property in the view model class...

[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "UserName")]

e.g...

public class RegisterViewModel
{
public UserAccount UserAccount { get; set; }
public RegisterViewModel()
{
UserAccount = new UserAccount();
}

[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "UserName")]
string UserName { get; set; }
[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "Password")]
string Password { get; set; }

[AttributesFrom(typeof(MyApp.DataModel.UserAccount), "Password")]
[Display(Name = "Confirm Password", Prompt = "Confirm Password"), Compare("Password", ErrorMessage = "Your confirmation doesn't match.")]
public string PasswordConfirmation { get; set; }

}

This then gives copying of attributes that can be overridden (as with PasswordConfirmation above) allowing for multiple data models in the same viewmodel and if you need to access the inherited attributes from code, you can do so using the GetInheritedAttributeOfType method. For example...

public static class AttrHelper
{
public static T GetAttributeOfType<T>(this ViewDataDictionary viewData) where T : System.Attribute
{
var metadata = viewData.ModelMetadata;
var prop = metadata.ContainerType.GetProperty(metadata.PropertyName);
var attrs = prop.GetCustomAttributes(false);

// Try and get the attribute directly from the property.
T ret = attrs.OfType<T>().FirstOrDefault();

// If there isn't one, look at inherited attribute info if there is any.
if(ret == default(T))
{
AttributesFromAttribute inheritedAttributes = attrs.OfType<AttributesFromAttribute>().FirstOrDefault();
if (inheritedAttributes != null)
{
ret = inheritedAttributes.GetInheritedAttributeOfType<T>();
}
}

// return what we've found.
return ret;
}
}

This can be called from an Editor Template for example...

var dataTypeAttr = AttrHelper.GetAttributeOfType<DataTypeAttribute>(ViewData);

which will first look at the viewmodel's property attributes directly but if nothing's found it will look at the inherited attributes with it's call to GetInheritedAttributeOfType.

This works best for me because...

  1. I feel that current practice of repeating DataAnnotations in viewmodels as well as the datamodels isn't great for maintainability or reuse.

  2. Using MetadataType is also inflexible, it's all or nothing and you can't include multiple MetadataType attributes on a single ViewModel.

  3. Encapsulating the datamodels in the viewmodels without Properties is lacking as it also doesn't have the flexability. You have to include the entire encapsulated object so cannot populate the DataModel over multiple views.

Entity Framework Core Data Annotation Database Generated Values

To have to DateTime model properties that are set on INSERT and UPDATE actions I utilized a combination of Default Values via Fluent API configuration and database triggers. The annotations I mentioned in my question absolutely do not automatically configure SQL Server to generated default or updated DateTime values.

Model:

public class Foo {
// some properties

public DateTime Created { get; set; }

public DateTime LastUpdated { get; set; }

// more properties
}

Default Values:

protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Foo>()
.Property(i => i.Created)
.HasDefaultValueSql("getdate()");

modelBuilder.Entity<Foo>()
.Property(i => i.LastUpdated)
.HasDefaultValueSql("getdate()");
}

Database AFTER UPDATE Trigger:

CREATE TRIGGER [dbo].[Foo_UPDATE] ON [dbo].[Foo]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;

IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;

DECLARE @Id INT

SELECT @Id = INSERTED.Id
FROM INSERTED

UPDATE dbo.Foo
SET LastUpdated = GETDATE()
WHERE Id = @Id
END

Thanks!

Add data annotations to a class generated by entity framework

The generated class ItemRequest will always be a partial class. This allows you to write a second partial class which is marked with the necessary data annotations. In your case the partial class ItemRequest would look like this:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

//make sure the namespace is equal to the other partial class ItemRequest
namespace MvcApplication1.Models
{
[MetadataType(typeof(ItemRequestMetaData))]
public partial class ItemRequest
{
}

public class ItemRequestMetaData
{
[Required]
public int RequestId {get;set;}

//...
}
}

Why is entity framework not respecting my data annotations?

I finally found the problem. The Web.vbproj project had the NuGet packages folder included in the project. by excluding the folder from the project, my EF code/method works as expected.

How do I properly use DataAnnotations to register users with Identity in .NET 5.0?

The solution was in a link posted by @mohammad-mobasher, and this link was very helpful.

What I was missing was using @Html.ValidationSummary(false) in my view and wrapping the code in my POST route with if (ModelState.IsValid){}. @Html.ValidationSummary(false) checks for validation on the ViewModel properties and will add an unordered list with any violations to the register page if the the form is submitted and the model state is not valid.

The change to the controller looks like this:

[HttpPost]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid) // this is new
{
ApplicationUser user = new ApplicationUser { UserName = model.Email };
IdentityResult result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
}
return View(model);
}

and the view:

@using Test.ViewModels
@model RegisterViewModel

<h2>Register a new user</h2>
<hr />
@using (Html.BeginForm("Register", "Account", FormMethod.Post))
{
@Html.LabelFor(user => user.Email)
@Html.TextBoxFor(user => user.Email)
<!-- changed back to TextBoxFor, now the Validation method works for this -->

@Html.LabelFor(user => user.Password)
@Html.PasswordFor(user => user.Password)

@Html.LabelFor(user => user.ConfirmPassword)
@Html.PasswordFor(user => user.ConfirmPassword)

<input type="submit" value="Register" />
}

<!-- added this -->
@Html.ValidationSummary(false)

Data Annotations with Entity Framework 5.0 (database first)

Although it's somewhat painful, you need to create a class to use as the MetadataType for your model class.

[MetadataType(typeof(PayrollMarkupMetadata))
public partial class PayrollMarkup_State
{
...
}

public class PayrollMarkupMetadata
{
[UIHint("StatesEditor")]
public string State; // Has to have the same type and name as your model
// etc.
}


Related Topics



Leave a reply



Submit