miércoles, 14 de enero de 2015

DataAnnotations Cheat Sheet

He buscado por todo lado un Cheat Sheet como este para tener todas las opciones a mano pero no pude encontrar nada. Asi que arme uno propio, espero que les sirva de ayuda:


DataAnnotations Cheat Sheet v1.0


Nombre: AssociationAttribute

Descripcion: Especifica que un miembro de la entidad representa una relación, como una relacion de llave foranea.
Uso: [Association(Name, OtherKey, ThisKey)]
Ejemplo:
[Table(Name = "Clientes")]
public partial class Cliente
{
    [Column(IsPrimaryKey = true)]
    public string ClienteID;
    // ...
    private EntitySet<Pedido> _Pedidos;
    [Association(Storage = "_Pedidos", OtherKey = "ClienteID")]
    public EntitySet<Pedido> Pedidos

    {
        get { return this._Pedidos; }
        set { this._Pedidos.Assign(value); }
    }
}

Nombre: BindableTypeAttribute
Descripcion: Especifica si es que un tipo es tipicamente usado para hacer un   binding.
Uso: [BindableType(IsBindable = bool)]
Ejemplo:
  [BindableType(IsBindable = false)]
  public enum EstadoDeEntidad
  {
    Detached = 1,
    SinCambios = 2,
    Adicionado = 4,
    Borrado = 8,
    Modificado= 16,
  }

Nombre: CompareAttribute
Descripcion: Provee un atributo que compara dos propiedades.
Uso: [Compare(OtraPropiedad)]
Ejemplo:
public class Visitante
{
    public string EmailAddress { get; set; }
    [Compare("EmailAddress", ErrorMessage="Las  direcciones de correo no igualan." )]
    public string ConfirmEmailAddress { get; set; }
}

Nombre: ConcurrencyCheckAttribute
Descripcion: Se usa para especificar que una propiedad/columna tiene un modo "fixed" de concurrencia en el modelo EDM. Un modo de concurrecncia "fixed" significa que esta propiedad es parte de la revision de concurre3ncia de la entidad durante operaciones de guardado y solo se plica a propiedades escalares.
Uso: [ConcurrencyCheckAttribute]
Ejemplo:
  public class  Book
    {
        public long BookId { get; set; }
        public virtual string Title { get; set; }
        public Author Author { get; set; }
        [ConcurrencyCheckAttribute]
        [TimestampAttribute]
        public byte[] TimeStamp { get; set; }
    }

Nombre: CreditCardAttribute
Descripcion: Especifica que el campo es un numero de tarjeta de credito.
Uso: [CreditCardAttribute]
Ejemplo:
public class Buyer
{
    public string Nombre { get; set; }
    [CreditCardAttribute]
    public string CardNumber { get; set; }
}

Nombre: CustomValidationAttribute
Descripcion: Especifica que se usara un metodo de validacion a la medida para validar un campo.
Uso: [CustomValidation(typeof(Field), "Eval_function")]
Ejemplo:
public class CategoryMetadata
   {
      [CustomValidation(typeof(Category), "TestCategoryName")]
      public object CategoryName { get; set; }
   }
 
public static ValidationResult TestCategoryName(string pNewName, ValidationContext pValidationContext)
   {
      if (Regex.IsMatch(pNewName, @"^\d")) // cannot start with a digit
         return new ValidationResult("Cannot start with a digit", new List<string> { "CategoryName" });
      return ValidationResult.Success;
   }

Nombre: DataTypeAttribute
Descripcion: Specifica el nombre de tipo adicional para asociar al campo de dato.
Uso: [DataType(DataType.*)]
Ejemplo:
public class DataTypeEntity
{
    [DataType(DataType.Date, ErrorMessage = "Ingresaruna fecha valida (ej: 2/14/2011)")]
    public DateTime SaleDate { get; set; }

    [DataType(DataType.EmailAddress)]
    public string EmailAddress { get; set; }
}

Nombre: DisplayAttribute
Descripcion: Provee un atributo general que permite especificar cadenas localizables para los tipos y miembros de una clase.
Uso: [Display(Name = "*")]
Ejemplo:
public class Vendedor
{
    [Required]
    [DisplayName("Nombre Completo :")]
    public string Nombre { get; set; }

    [Display(Name = "Direccion de Correo")]
    public string DireccionEmail { get; set; }
}

Nombre: DisplayColumnAttribute
Descripcion: Especifieca la columna que se muestra en la tabla como una columna de llave Foránea.
Uso: [DisplayColumn("Campo a usar como Llave Foranea, Campo a usar para ordenar, ascendente/descendente)]
Ejemplo:
[DisplayColumn("Ciudad", "CodigoPostal", false)]
public partial class Direccion
{

}

Nombre: DisplayFormatAttribute
Descripcion: Especifica como se mostraran y formatearan los campos de datos mediante ASP.NET Dynamic Data.
Uso: [DisplayFormat(DataFormatString="*")]
Ejemplo:
public class Venta
{
    // Mostrar el dcampo de tipo monedacon el formato $1,345.50.
    [DisplayFormat(DataFormatString="{0:C}")]
    public object CostoEstandar;
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy hh:mm}")]
    public DateTime MiFecha { get; set; }
}

Nombre: EditableAttribute
Descripcion: Indica si un campo es o no editable.
Uso: [Editable(AllowEdit=bool,AllowInitialValue=bool)]
Ejemplo:
public class Persona
{
[Editable(AllowEdit=false)]
 public object Nombre{ get; set; }
 }

Nombre: EmailAddressAttribute
Descripcion: Valida una direccion de correo electronico.
Uso: [EmailAddress]
Ejemplo:
public class empleado
{
public string Nombre{get; set;}
[EmailAddress]
public string Notificacion{get; set;}
}

Nombre: EnumDataTypeAttribute
Descripcion: Permite mapear una enumeracion del .NET Framework a un campo de datos.
Uso: [EnumDataType(typeof(Enum Type))]
Ejemplo:
public enum eNivReorden {
        cero= 0,
        cinco= 5,
        diez=10,
        quince=15,
        veinte=20,
        veinticinco=25,
        treinta=30
    }
 public class Product_MD {
            [EnumDataType(typeof(eNivReorden ))]
            public object NivelDeReorden { get; set; }
        }

Nombre: FileExtensionsAttribute
Descripcion: Valida extensiones de archivos.
Uso: [FileExtensions(Extensions="*")]
Ejemplo:
public class Cliente{
    [FileExtensions(Extensions = "csv,txt",ErrorMessage = "Debe escoger un archivo .csv")]
    public string ImportFile{ get; set; }
}

Nombre: FilterUIHintAttribute
Descripcion: Representa un atributo usado para especificar el comportamiento de filtrado de una columna.
Uso: [FilterUIHint("El nombre de el control a usar para filtrado.")]
Ejemplo:
MetadataType(typeof(Product_MD))]
public partial class Product {
    private class Product_MD {
        [FilterUIHint("MultiForeignKey")]
        public object Category { get; set; }
        [FilterUIHint("BooleanRadio")]
        public object Discontinued { get; set; }
    }
}

Nombre: KeyAttribute
Descripcion: Indica una o mas propiedades que Identifican a una entidad.
Uso: [Key]
Ejemplo:
public class Cliente{
    [Key]
    public int Id { get; set; }
    public string nombre { get; set; }
}

Nombre: MaxLengthAttribute
Descripción: Especifica la longitud máxima de array o cadena que se permite en una propiedad.
Uso: [MaxLength(Largo)]
Ejemplo:
public class Cliente
{
    public virtual string IdCliente { get; set; }
    [MaxLength(50, ErrorMessage="El nombre no puede ser mayor a 50 chars" )]
    public virtual string NombreDeCompania{ get; set; }
}

Nombre: MetadataTypeAttribute
Descripción: Especifica la clase metadata a asociar con la clase del Modelo de datos.Indica que esta clase tienen una Clase Metadata Asociada. tiene un paramtro Type para espcificar que tipo contiene la metadata de la clase
Uso: [MetadataType(typeof(MetaData))]
Ejemplo:
public class CRMTypeMetadata
{
    [ScaffoldColumn(false)]
    public int TypeID { get; set; }
 
    [StringLength(100)]
    public string Url { get; set; }
}
 
[MetadataType(typeof(CRMTypeMetadata))]
public partial class CRMType
{
}

Nombre: MinLengthAttribute
Descripcion: Especifica la longitud Minima de un array o cadena que esta permitido en la propiedad.
Uso: [MinLength(Length)]
Ejemplo:
public class Customer
{
    public virtual string CustomerID { get; set; }
    [MinLength(3, ErrorMessage="El nombre no puede ser tan corto." )]
    public virtual string CompanyName { get; set; }
}

Nombre: PhoneAttribute
Descripcion: Especifica que el campo de datos el un numero de teléfono que se adecua al formato usando expresiones regulares para números de teléfono.
Uso: [Phone]
Ejemplo:
public class Cliente
{
    public virtual string ClienteID { get; set; }
    [Phone]
    public virtual string Nro_Telefono{ get; set; }
}

Nombre: RangeAttribute
Descripcion: Especifica un rango numerico permitido pára el valor de la propiedad.
Uso: [Range(Double,Double)], [Range(Int32,Int32)], [Range(Type,String,String)]
Ejemplo:
public class Item
{
public int Id { get; set; }
[Range(10, 1000, ErrorMessage = "El valor de {0} debe estar entre {1} y {2}.")]
public Double Peso { get; set; };
}

Nombre: RegularExpressionAttribute
Descripcion: Especifica que un campo de datos debe cumplir con la expresion regular indicada.
Uso: [RegularExpression(@"Regular_Expresion")]
Ejemplo:
public class Customer
{
    [RegularExpression(@"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}", ErrorMessage = "Por favor entre un correo correcto")]
     public string Email { get; set; }
 }

Nombre: RequiredAttribute
Descripcion: Especifica que un campo es requerido/obligatorio.
Uso: [Required()]
Ejemplo:
public class Customer
{
    [Required]
    public virtual string CustomerID { get; set; }
    public virtual string CompanyName { get; set; }
}

Nombre: ScaffoldColumnAttribute
Descripcion: Especifica si una clase o columna de datos utiliza scaffolding.
Uso: [ScaffoldColumn(true)]
Ejemplo:
public class ProductMetadata
{
    [ScaffoldColumn(true)]
    public object ProductID;
    [ScaffoldColumn(false)]
    public object ThumbnailPhotoFileName;
}

Nombre: ScaffoldTableAttribute
Descripcion: Especifica si una clase o tabla de datos utiliza scaffolding.
Uso: [ScaffoldTable(bool)]
Ejemplo:
[MetadataType (typeof(ErrorLogMetada))]
[ScaffoldTable(false)]
public partial class ErrorLog
{
 
}

public class ErrorLogMetada
{

}

Nombre: StringLengthAttribute
Descripcion: Especifica el mínimo y máximo de caracteres permitidos en un campo de datos.
Uso: [StringLength(int maximumLength)]
Ejemplo:
public class ProductMetadata
{
    [StringLength(4, ErrorMessage = "El nombre no puede exceder 4 caracteres.")]
    public object ThumbnailPhotoFileName;

}

Nombre: TimestampAttribute
Descripcion: Especifica el tipo de datos de la columna como el que controla la version de la fila.Tambien especifica que esta columna sea incluida en la clausula Where de los comandos Update y Delete enviados a la base de datos, para verificacion de concurrencia.
Uso: [Timestamp]
Ejemplo:
public class Persona
{
    public Int64 Id { get; set; }
    public string Nombre{ get; set; }
    [Timestamp]
    public Byte[] Timestamp { get; set; }
}

Nombre: UIHintAttribute
Descripcion: Especifica un template o control de usuario que se usa para mostrar un campo de datos.
Uso: [UIHint(string uiHint)]
Ejemplo:
public partial class ProductMetadata
{
    [UIHint("UnidDisponibles")]
    [Range(100, 10000,
    ErrorMessage = "Unidades disponibles debe estar entre {1} y {2}.")]
    public object UnidDisponibles;
}

Nombre: UrlAttribute
Descripcion: Provee validacion para un campo tipo URL.
Uso: [Url]
Ejemplo:
public class MiModelo
{
    [Url]
    public string ServerAddress {get; set;}
}

Cualquier contribución para mejorar estos ejemplos sera bien recibida.

Copiar filas en la misma tabla solo cambiando el Id (TSQL)

Vamos a analizar diferentes escenarios en los cuales necesitamos copiar/clonar filas de una tabla a esa misma tabla, Solo cambiando el valor de un campo, Que en este caso sera el campo Id, Usando Microsoft SQL Server.

Supongamos que tenemos el AntId (Id original Id de el que queremos copiar las filas, en este caso este Id puede estar en varias filas o solo en una) y el NuevoId ( El Id nuevo que queremos poner en las filas que vamos a copiar/clonar)

Caso 1 - conocemos el nombre de la tabla y de todos sus campos:

El primer caso es de una tabla de la cual sabemos de antemano su nombre y su estructura, así que podemos escribir una consulta como la siguiente:

DECLARE @OldId int
DECLARE @NuevoId int
SET @AntId =13456 -- ejem.
SET @NuevoId =45687 -- ejem.

INSERT INTO MiTabla(Id,ColumnaA,ColumnaB,ColumnaC)
SELECT @NuevoId ,ColumnaA,ColumnaB,ColumnaC
FROM MyTable WHERE Id=@AntId 


Caso 2 - Conocemos el nombre de la tabla pero no de los campos:

En este caso nos vemos forzados a usar una tabla temporal para poder actualizar el ID antes de copiar las filas sin conocer el resto de las columnas:

DECLARE @AntId int
DECLARE @NuevoId int
SET @AntId =13456 -- ejm.
SET @NuevoId =45687 -- ejm.

--Copiar la filas que queremos a la tabla temporal
SELECT INTO #Temp FROM MiTabla WHERE Id=@AntId 

--Actualizar el Id en la tabla temporal    
UPDATE #Temp SET Id = @NuevoId 

--Copiar las filas con el nuevo Id de vuelta a MiTabla
INSERT INTO MiTabla  SELECT * FROM #Temp

--Borrar la tabla temporal     
if object_id(N'tempdb..#Temp'N'U'is not null  DROP TABLE #Temp
     
     
Caso 3 - No conocemos ni el nombre de la tabla ni de los campos:

Este escenario nos obliga a usar querys dinámicos para poder armar nuestra consulta con el nombre de la tabla, Nuestro primer intento seria algo así (y va a FALLAR)

DECLARE @NombreDeTabla varchar(32)
DECLARE @AntId int
DECLARE @NuevoId int

SET @NombreDeTabla ='MiTabla' --ejm podria llegar como parametro
SET @AntId =13456 -- ejm.
SET @NuevoId =45687 -- ejm.

SET @v_SQL = 'SELECT * INTO #Temp FROM ' + @NombreDeTabla ' WHERE Id='+  CAST(@AntId as varchar
EXEC(@v_SQL)
    
SET @v_SQL = 'UPDATE #Temp SET Id = '+  CAST(@NuevoId as varchar
EXEC(@v_SQL)

SET @v_SQL = 'INSERT INTO ' + @NombreDeTabla ' SELECT * FROM #Temp'
EXEC(@v_SQL)

--Borrar la tabla temporal    
SET @v_SQL = 'if object_id(N''tempdb..#Temp'', N''U'') is not null  DROP TABLE #Temp'
EXEC(@v_SQL)

     
Falla con errores porque el comando Exec() tienen su propio Scope ( que egoísta!) así que la tabla temporal no esta disponible para las siguientes sentencias.

La solución es usar una tabla temporal Global "##"  así estará disponible en el  Scope de el resto de los comandos Exec. 
Esto crea otro posible problema en sistemas multiusuario, porque se puede dar el caso que dos usuarios usen el proceso al mismo tiempo, y como la tabla temporal estaría disponible para ambos se pueden obtener errores o datos incorrectos. Así que necesitamos adicionar algo para que el nombre de la tabla temporal sea único para nuestro proceso, en este caso usaremos el nuevo Id que queremos usar para diferenciar el nombre de la tabla de otros usuarios concurrentes, Aunque puedes usar tu propio (y mas robusto) generador de Ids para estos casos si lo deseas.

La solución final queda así:

DECLARE @NombreDeTabla varchar(32)
DECLARE @AntId int
DECLARE @NuevoId int

SET @NombreDeTabla ='MiTabla' --ejm.
SET @AntId =13456 -- ejm.
SET @NuevoId =45687 -- ejm.

DECLARE @v_SQL varchar(1024)

SET @v_SQL = 'SELECT * INTO ##Temp'CAST(@NuevoId as varchar) +' FROM ' + @NombreDeTabla + ' WHERE Id = '+  CAST(@AntId as varchar
EXEC(@v_SQL)
    
SET @v_SQL = 'UPDATE ##Temp'CAST(@NuevoId as varchar) +' SET Id = '+  CAST(@NuevoId as varchar
EXEC(@v_SQL)

SET @v_SQL = 'INSERT INTO ' + @NombreDeTabla + ' SELECT * FROM ##Temp'CAST(@NuevoId as varchar)
EXEC(@v_SQL)
     
SET @v_SQL = 'if object_id(N''tempdb..##Temp'CAST(@NuevoId as varchar) +''', N''U'') is not null  DROP TABLE ##Temp'CAST(@NuevoId as varchar)  
EXEC(@v_SQL)
GO


Si lo ponemos como un Procedimiento Almacenado:

CREATE PROCEDURE dbo.CopiarConNuevoId
(
  @NombreDeTabla varchar(32),
  @NuevoId int,
  @AntId int
)
AS
  SET NOCOUNT ON
BEGIN
DECLARE @v_SQL varchar(1024)

SET @v_SQL = 'SELECT * INTO ##Temp'CAST(@NuevoId as varchar) +' FROM ' + @NombreDeTabla + ' WHERE Id = '+  CAST(@AntId as varchar
EXEC(@v_SQL)
    
SET @v_SQL = 'UPDATE ##Temp'CAST(@NuevoId as varchar) +' SET Id = '+  CAST(@NuevoId as varchar
EXEC(@v_SQL)

SET @v_SQL = 'INSERT INTO ' + @NombreDeTabla + ' SELECT * FROM ##Temp'CAST(@NuevoId as varchar)
EXEC(@v_SQL)
     
SET @v_SQL = 'if object_id(N''tempdb..##Temp'CAST(@NuevoId as varchar) +''', N''U'') is not null  DROP TABLE ##Temp'CAST(@NuevoId as varchar)  
EXEC(@v_SQL)
     
END
GO

Y eso es todo, espero que les sea de ayuda en alguna ocasión.

lunes, 16 de septiembre de 2013

Paginacion en CodeIgniter con Terminos de Busqueda

En este artículo tratare de explicar cómo usar la librería de paginación de CodeIgniter con un termino de búsqueda de tal manera que los enlaces de la paginación sean de la manera:

Controller\accion\termino_de_busqueda\pagina.

La librería de paginación de Codeigniter nos facilita el trabajo de mostrar largas listas de datos generando los enlaces de las paginas siguientes de manera automática, pero no tiene funciones que nos permitan incluir en este listado una búsqueda dinámica y paginar esos resultados.

El problema reside en que al incluir la búsqueda en la paginación, no existe una función específica para incluir la búsqueda en los enlaces de las paginas siguientes. Como una complicación extra solucionaremos este problema paginando el resultado de una consulta de un subconjunto de datos. Y obteniendo los enlaces de la manera:

Controller\accion\id\termino de búsqueda\pagina.

Tenemos la siguiente situación:

 - Lista de Lugares.
 - Lista de Personas que pertenecen a un lugar.
 - Se desea listar las personas pertenecientes a un lugar de manera paginada y sobre ese listado poder hacer búsquedas paginadas por apellido.

 En la pagina de listado de lugares tendremos:

 <a href="personas/ver/1"> Personas en Ciudad 1</a>
 <a href="personas/ver/2"> Personas en Ciudad 2</a>
 <a href="personas/ver/3"> Personas en Ciudad 3</a>

En el controlador de personas se tiene que tener dos acciones "ver" y "ver_busqueda", esta segunda función sirve como una función intermedia para pasar el dato de la cadena de búsqueda cuando se trate de la búsqueda paginada y la primera en caso de que no se tenga una cadena de búsqueda o sea que funcionará normalmente.

La primera función es la siguiente:
<?php
function ver($id_ciudad, $offset = 0){
       //en esta parte verificamos si la paginación es sin un termino de búsqueda
       //o si viene  de la función ver_busqueda con un termino de búsqueda
       if ( $this->session->userdata('var_busqueda')){
             $busqueda=$this->session->userdata('var_busqueda');
             $this->session->unset_userdata('var_busqueda');
       }else{
             $búsqueda='';
       }

       $this->load->model('m_personas');
      
       //Cargamos la librería de paginación
       $this->load->library('pagination');

       //Aquí definimos la estructura de los enlaces según si hay o no un termino de búsqueda
       if ($búsqueda==''){
             $config['base_url'] = base_url().'/index.php/personas/ver/'.$id_ciudad.'/';
             $config['uri_segment'] = '4';
       }else{
             $config['base_url'] = base_url().'/index.php/personas/ver_busqueda/'.$id_ciudad.'/'.$busqueda.'/';
             $config['uri_segment'] = '4';
       }

       $config['total_rows'] = $this->m_personas->nro_de_registros_personas($id_ciudad,$busqueda);
       $config['per_page'] = 25;
       $config['num_links'] =5;
       $config['next_link'] = '>';
       $config['prev_link'] = '<';
       $config['first_link'] = '<<';
       $config['last_link'] = '>>';

       $this->pagination->initialize($config);
       $data["page_links"] = $this->pagination->create_links();

       //obtener el listado de personas usando los datos de offset y  el nro de registros
       $articulos=$this->m_personas->mostrar_personas_paginado($id_ciudad,$busqueda,$offset,$config['per_page']);
       $data['personas']=$personas;
       $data['id_ciudad']=$id_ciudad;
       $this->load->vars($data);

       //cargamos nuestra vista
       $this->load->view('template');
}
?>

En esta tienen que hacer un control para verificar si es el listado es parte de la búsqueda paginada o una paginación normal y dependiendo de esto generar los enlaces para la librería de paginación.


En caso de tener no tener un dato de búsqueda el base_url de la paginación es normal, (no olvidemos que a esta url la librería le adicionara el numero de pagina), en caso de si tener un dato de búsqueda en la variable $búsqueda se debe usar la acción "ver_busqueda" la cual se incluirá en los enlaces de paginación. La función es la siguiente:

<?php

function ver_busqueda($id_ciudad,$busqueda='',$offset = 0){
       if (empty($_POST)){
                   
       }else{
             try {
                    $busqueda=$this->input->post('busqueda');
             } catch (Exception $e) {
                    $busqueda='';
             }
       }
       $this->session->set_userdata('var_busqueda', $busqueda);
       redirect('/personas/ver/'.$id_ciudad.'/'.$offset);
}
?>

Lo que hace es grabar el termino de la búsqueda en una variable de session para ser leída después, y re direccionar a la acción normal con el offset recibido correspondiente  (es necesario tener esta función ya que no hay otra manera que la función ver pueda manejar al mismo tiempo la paginación con o sin búsqueda)

Y en la vista se debe tener el listado de la variable $personas, poner los page_links, Esto ya esta fuera de el alcance de este articulo, pero el formulario para la búsqueda que se debe incluir debería ser más o menos como:

  <div align="center">
       <?php echo form_open('personas/ver_busqueda/'.$id_ciudad.'/'); ?>
       <?php echo form_input("busqueda");?>
       <?php echo form_close();?>
       </div>


Bueno, espero que les sea de utilidad y para cualquier duda estoy a su disposición.