Adicionar anotações de dados a uma classe gerada por estrutura de entidade

93

Eu tenho a seguinte classe gerada pela estrutura de entidade:

public partial class ItemRequest
{
    public int RequestId { get; set; }
    //...

Eu gostaria de tornar este um campo obrigatório

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

No entanto, como esse código é gerado, ele será eliminado. Não consigo imaginar uma maneira de criar uma classe parcial porque a propriedade é definida pela classe parcial gerada. Como posso definir a restrição de forma segura?

P.Brian.Mackey
fonte
Se sua propriedade for int, ela é por padrão necessária para modelbinder, então seu atributo [Obrigatório] não adicionará nada aqui.
Kirill Bestemyanov
@KirillBestemyanov - @ Html.ValidationMessageFor (model => model.Item.Item.ResourceTypeID) deve falhar no lado do cliente. Isso não.
P.Brian.Mackey

Respostas:

143

A classe gerada ItemRequestsempre será uma partialclasse. Isso permite que você escreva uma segunda classe parcial que é marcada com as anotações de dados necessárias. No seu caso, a classe parcial ItemRequestseria assim:

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;}

        //...
    }
}
MUG4N
fonte
11
A classe parcial não será regenerada. É por isso que é definido como parcial.
MUG4N de
você perdeu o modificador parcial? Você usa o mesmo namespace?
MUG4N
3
Usuários do .NET Core: use ModelMetadataType em vez de MetadataType.
Bob Kaufman
1
Você pode colocar a classe parcial onde quiser, desde que o namespace seja idêntico
MUG4N
40

Como o MUG4N respondeu, você pode usar classes parciais, mas será melhor usar interfaces . Neste caso, você terá erros de compilação se o modelo EF não corresponder ao modelo de validação. Assim, você pode modificar seus modelos de EF sem medo de que as regras de validação estejam desatualizadas.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace YourApplication.Models
{
    public interface IEntityMetadata
    {
        [Required]
        Int32 Id { get; set; }
    }

    [MetadataType(typeof(IEntityMetadata))]
    public partial class Entity : IEntityMetadata
    {
        /* Id property has already existed in the mapped class */
    }
}

PS Se você estiver usando um tipo de projeto que é diferente da ASP.NET MVC (quando você executa a validação manual de dados), não se esqueça de registrar seus validadores

/* Global.asax or similar */

TypeDescriptor.AddProviderTransparent(
    new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Entity), typeof(IEntityMetadata)), typeof(Entity));
dimonser
fonte
@dimonser boa solução, tentei adicionar comentários xml como este também (para aqueles campos de banco de dados que precisam de uma pequena explicação no código - ou seja, para serem exibidos no intellitype), mas não parece funcionar. Alguma ideia de como fazer isso?
Percy
Olá @Rick, você pode colocar um comentário sobre uma propriedade de interface, mas só verá quando trabalhar com uma variável de interface. Ou você pode colocar um comentário em uma aula parcial. Nesse caso, você verá quando trabalhar com uma instância de sua classe. Nenhum outro caso disponível. Portanto, você pode usar ambos para cobrir todas as situações. No primeiro caso, você pode descrever as regras de validação de campo e, no segundo caso, tentar descrever os objetivos
dimonser
Resposta muito bem pensada, mas minha preferência seria ver erros de compilação se a validação não estiver mais em sincronia com a classe de estrutura de entidade gerada automaticamente. Estou lutando para pensar em uma situação em que você queira validar uma propriedade que não está mais presente em sua classe de estrutura de entidade.
Mike
1
Isso não funciona para mim, diz que preciso implementar a interface IEntityMetadata ...
Worthy7
14

Eu encontrei uma solução como a resposta do MUG4N , mas em vez disso, aninhei a MetaDataclasse dentro da classe de entidade, reduzindo assim o número de classes em sua lista de namespaces públicos e eliminando a necessidade de ter um nome único para cada classe de metadados.

using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models 
{
    [MetadataType(typeof(MetaData))]
    public partial class ItemRequest
    {
        public class MetaData
        {
            [Required]
            public int RequestId;

            //...
        }
    }
}
Carter Medlin
fonte
Tenho usado isso em todo o meu projeto. Muito mais fácil de organizar. Também adiciono propriedades personalizadas usando [NotMapped]dentro da classe parcial quando preciso delas.
Carter Medlin
5

Esta é uma espécie de extensão para a resposta @dimonser se você regenerar seu modelo de banco de dados, terá que adicionar novamente manualmente as interfaces nessas classes.

Se você tiver estômago para isso, também pode modificar seus .ttmodelos:

Aqui está um exemplo de interfaces de geração automática em algumas classes, este é um fragmento de .ttapenas substituir o EntityClassOpeningmétodo no seu seguinte (e obviamente var stringsToMatchcom seus nomes de entidade e interfaces).

public string EntityClassOpening(EntityType entity)
{
    var stringsToMatch = new Dictionary<string,string> { { "Answer", "IJourneyAnswer" }, { "Fee", "ILegalFee" } };
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}{4}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)),
        stringsToMatch.Any(o => _code.Escape(entity).Contains(o.Key)) ? " : " + stringsToMatch.Single(o => _code.Escape(entity).Contains(o.Key)).Value : string.Empty);
}

Nenhuma pessoa normal deveria fazer isso a si mesma, foi provado na Bíblia que alguém vai para o Inferno por causa disso.

Matas Vaitkevicius
fonte
2

Não tenho certeza de como fazer o que você está pedindo, mas há uma maneira de contornar isso. Validação de dados dinâmica substituindo GetValidators de seu DataAnnotationsModelValidatorProvider personalizado. Nele você pode ler as regras para validar cada campo (de um banco de dados, arquivo de configuração, etc.) e adicionar validadores conforme necessário. Tem o valor agregado de que sua validação não está mais fortemente acoplada ao modelo e pode ser alterada sem a necessidade de reiniciar o site. Claro que pode ser um exagero para o seu caso, mas foi ideal para o nosso!

JTMon
fonte
Sim, quando implementamos essa estrutura pela primeira vez. Desde então, mudamos para NHibernate, mas isso não tem relação com a solução. Nosso código de validação funcionou como está, sem alterações (apenas a camada de acesso aos dados foi alterada).
JTMon
1

Modifique o modelo T4 adicionando as anotações necessárias; este arquivo geralmente é denominado MODELNAME.tt

descubra onde o T4 está criando a classe e os métodos para saber onde colocá-los.

     <#=codeStringGenerator.IgnoreJson(navigationProperty)#>


//create this method in file
public string IgnoreJson(NavigationProperty navigationProperty){
            string result = navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? "" : @"[JsonIgnore]
    [IgnoreDataMember]";

            return result;
        }

Você também precisará adicionar os namespaces;

<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using System.Runtime.Serialization;

Reconstrua suas classes salvando seu modelo, todos os seus métodos devem ser anotados.

tswales
fonte