Aquí, finalmente, agregaré proyectos para utilizar procedimientos almacenados en lugar de sentencias construidas en el código.
De paso, y por recomendación de un mayor experto que yo en Entity Framework, agregué «AsNoTracking()» a la consulta LINQ de Entity Framework establecida en Generando una API REST (JSON) [2].
El procedimiento Almacenado.
Este es el procedimiento almacenado que recibe, el Id de país, la fecha desde y la fecha hasta, y la página a mostrar.
Es el procedimiento almacenado el responsable de establecer valores adecuados a los parámetros de fecha, en lugar de establecerlos desde el componente en C#.
Exactamente igual, pero con «FOR JSON PATH» al final, se usa en el proyecto que utiliza código puro.
El Cambio en Entity Framework
Basado en la propuesta y comentario, el código queda como sigue:
public IEnumerable<OwidCovidDatum> GetCountryData(
intCountryId,
DateTime?fromDate=null,
DateTime?toDate=null,
intPage=1)
{
fromDate=fromDate??
(from el in _context.OwidCovidData orderby el.Date select el.Date).FirstOrDefault();
toDate=toDate??
(from el in _context.OwidCovidData orderby el.Date descendingselect el.Date).FirstOrDefault();
return (from OwidCovidDatum el in
_context.OwidCovidData
.Where(x=> x.Date>=fromDate && x.Date<=toDate && x.CountriesId==CountryId)
.Skip((Page-1)*100)
.Take(100) select el).AsNoTracking().ToList();
}
Dapper Usando Procedimientos Almacenados
Utilizamos la capacidad de ejecución de procedimientos almacenados de Dapper, que es capaz de asignar valores a los parámetros por coincidencia de nombres.
publicasyncTask<string>GetCountryData(intCountryId,DateTime?fromDate=null,DateTime?toDate=null,intPage=1){varresult=await_dal.GetDataAsync("[Owid Covid Data Get By Country]",new{fromDate,toDate,CountryId,Page});stringjson=JsonSerializer.Serialize(result,newJsonSerializerOptions(){WriteIndented=true,ReferenceHandler=ReferenceHandler.Preserve});returnjson;}
Código usando Procedimientos Almacenados
En el caso del código directo, asignamos los parámetros uno a uno, especificando además el tipo de dato, que permite una mayor especificidad.
publicasyncTask<string>GetCountryData(intCountryId,DateTime?fromDate=null,DateTime?toDate=null,intPage=1){varcommand=_dal.CreateCommand("[Owid Covid Data Get By Country JSON]");command.Parameters.Add("@fromDate",System.Data.SqlDbType.SmallDateTime).Value=fromDate;command.Parameters.Add("@toDate",System.Data.SqlDbType.SmallDateTime).Value=toDate;command.Parameters.Add("@CountryId",System.Data.SqlDbType.Int).Value=CountryId;command.Parameters.Add("@skip",System.Data.SqlDbType.Int).Value=Page;varjson=await_dal.GetJSONDataAsync(command);returnjson;}
Rendimiento
El gráfico muestra que, aún cuando se utilizan características que mejoran la efectividad, la simpleza del código mejora el rendimiento.
O sea, para mejor respuesta al usuario, se deberá invertir más tiempo de los desarrolladores en mejorar su desarrollo.
Como detalle, las llamadas de procedimiento almacenado, realizan directamente llamadas al mismo, en lugar de utilizar, como vimos en la publicación anterior, sp_executesql.
EXEC[Owid Covid Data Get By Country]@fromDate=NULL,@toDate=NULL,@CountryId=4,@Page=3;
Let’s consider another requirement, to evaluate how you can best take advantage of the features of the Entity Framework and emulate that functionality in cases where it cannot be used, or it is more convenient to do something else.
In this example, we are using the same database explained in Data-for-demos
The requirement
You need to get the statistical information of cases, vaccinations, etc. By country, between certain dates, with the following conditions:
If no start date is entered, the first available date is used.
If the end date is not entered, the last available date is used.
The information must be returned in batches of 100 entries, so the requested page number must be received.
In this case, it will be implemented in the “Country» controller
Entity Framework.
The code leverages EF’s Fluent capabilities to nest conditions. Similarly, below, the Entity Framework generates a statement according to the data engine in use, in this case, SQL Server.
publicasync Task<ActionResult<IEnumerable<OwidCovidDatum>>> GetCountryData(
intCountryId,
DateTime?fromDate=null,
DateTime?toDate=null,
intPage=1)
{
fromDate=fromDate??
(from el in _context.OwidCovidData orderby el.Date select el.Date).FirstOrDefault();
toDate=toDate??
(from el in _context.OwidCovidData orderby el.Date descendingselect el.Date).FirstOrDefault();
return (from OwidCovidDatum el in
_context.OwidCovidData
.Where(x=> x.Date>=fromDate && x.Date<=toDate && x.CountriesId==CountryId)
.Skip((Page-1)*100)
.Take(100) select el).ToList();
}
Dapper
Using the returned DapperRows, we implement the call with an SQL statement that is almost the same as the one automatically generated by EF.
As in the previous example, we create a parameterized Command object that returns a string of characters with the resulting JSON, implemented in the SQL statement.
Consideremos otro requisito, para evaluar como puede aprovecharse mejor las características de Entity Framework y emular esa funcionalidad en los casos en que no se pueda utilizar, o sea más conveniente otra forma de realizar la tarea.
En este ejemplo, estamos utilizando la misma base de datos explicada en Datos-para-demos
El requisito
Se necesita obtener la información estadística de casos, vacunaciones, etc. Por país, entre determinadas fechas, con las siguientes condiciones:
Si no se consigna fecha de inicio, se usa la primera disponible.
Si no se consigna la fecha de fin, se usa la última disponible.
Se debe retornar la información en lotes de a 100 entradas, con lo cual, se deberá recibir el número de página solicitado.
En este caso, se implementará en el controlador «Country«
Entity Framework.
El código aprovecha las funcionalidades Fluent de EF para anidar las condiciones. Igualmente, por debajo, Entity Framework genera una sentencia acorde al motor de datos en uso, en este caso, SQL Server.
publicasync Task<ActionResult<IEnumerable<OwidCovidDatum>>> GetCountryData(
intCountryId,
DateTime?fromDate=null,
DateTime?toDate=null,
intPage=1)
{
fromDate=fromDate??
(from el in _context.OwidCovidData orderby el.Date select el.Date).FirstOrDefault();
toDate=toDate??
(from el in _context.OwidCovidData orderby el.Date descendingselect el.Date).FirstOrDefault();
return (from OwidCovidDatum el in
_context.OwidCovidData
.Where(x=> x.Date>=fromDate && x.Date<=toDate && x.CountriesId==CountryId)
.Skip((Page-1)*100)
.Take(100) select el).ToList();
}
Dapper
Utilizando los DapperRow de retorno, implementamos la llamada con una sentencia SQL que es casi igual a la generada automáticamente por EF.
Al igual que en el ejemplo previo, creamos un objeto Command con parámetros que retorne una cadena de caracteres con el JSON resultante, implementado en la sentencia SQL.
In the previous post,Paging data, NOT client side. | Universidad Net, I described how to use OFFSET-FETCH pair to paginate data in the server side. In this one, I will describe an example of how to use it in a Web Assembly App.
Note: To test this code, you must create a new Blazor app in Visual Studio.
The Model.
The data model will be a class with two properties, one for the list of items and other with the page’s information.
This classes will be added in the Shared project of the Application.
The first class vary depending on the data you want to display, but the second one will be always the same, enhanced by some code.
Since the pager class will be a standardized and enhanced version of the data retrieved by using the stored procedure, and could be used in different projects, let’s define an interface for it.
IPagesInfo interface.
This is the code for the Interface.
publicinterfaceIPagesInfo
{
#region Properties
///<summary>/// Gets or sets the current page.....///</summary>
System.Int32 CurrentPage { get; set; }
///<summary>/// Gets the current first page number..///</summary>Int32 FirstPageNumber { get; }
///<summary>/// Gets the HasNextGroup/// Gets a value indicating whether this instance has next page..///</summary>Boolean HasNextGroup { get; }
///<summary>/// Gets the HasPreviousGroup/// Gets a value indicating whether this instance has previous page..///</summary>Boolean HasPreviousGroup { get; }
///<summary>/// Gets the last page number..///</summary>Int32 LastPageNumber { get; }
///<summary>/// Gets or sets the number of links to show..///</summary>Int32 NumberOfLinks { get; set; }
///<summary>/// Gets or sets the size of the page.....///</summary>
System.Int32 PageSize { get; set; }
///<summary>/// Gets or sets the Qty of rows skipped from the top of the select.....///</summary>
System.Int32 Skip { get; set; }
///<summary>/// Gets or sets the Qty of rows taken.....///</summary>
System.Int32 Take { get; set; }
///<summary>/// Gets or sets the amount of items to display.///</summary>
System.Int32 TotalItems { get; set; }
///<summary>/// Gets the total pages available to display..///</summary>Int32 TotalPages { get; }
///<summary>/// Gets the page number for the previous group start.///</summary>///<value>/// The page number.///</value>Int32 PreviousGroupStart { get; }
///<summary>/// Gets the page number for the next group start.///</summary>///<value>/// The page number.///</value>Int32 NextGroupStart { get; }
#endregion
}
PagerInfo class.
Here, you have the code for the class implementing the IPagesInfo interface.
Notice the class is responsible of the calculations about page numbers displayed, if there are next or previous groups of pages, etc.
publicclassPagerInfo : IPagesInfo
{
#region Fields
///<summary>/// Defines the lastPageNumber.///</summary>internalInt32 lastPageNumber =0;
#endregion#region Properties
publicInt32 TotalPages
{
get=> (Int32)Math.Ceiling(TotalItems / (Double)PageSize);
}
///<summary>/// Gets the page number for the previous group start.///</summary>///<value>/// The page number.///</value>publicInt32 PreviousGroupStart => LastPageNumber - NumberOfLinks;
///<summary>/// Gets the page number for the next group start.///</summary>///<value>/// The page number.///</value>publicInt32 NextGroupStart => LastPageNumber +1;
///<summary>/// Gets or sets the current page.///</summary>public System.Int32 CurrentPage { get; set; }
///<summary>/// Gets the current first page number..///</summary>publicInt32 FirstPageNumber
{
get=> LastPageNumber - (NumberOfLinks -1);
}
///<summary>/// Gets a value indicating whether this instance has next page.///</summary>publicBoolean HasNextGroup
{
get=> CurrentPage + NumberOfLinks < TotalPages;
}
///<summary>/// Gets a value indicating whether this instance has previous page.///</summary>publicBoolean HasPreviousGroup
{
get=> CurrentPage > NumberOfLinks;
}
///<summary>/// Gets the last page number..///</summary>publicInt32 LastPageNumber
{
get
{
lastPageNumber = (Int32)Math.Ceiling((Double)CurrentPage / NumberOfLinks) * NumberOfLinks;
if (lastPageNumber > TotalPages)
{
lastPageNumber = TotalPages;
}
return lastPageNumber;
}
}
///<summary>/// Gets or sets the number of links to show..///</summary>publicInt32 NumberOfLinks { get; set; } =10;
///<summary>/// Gets or sets the size of the page.....///</summary>public System.Int32 PageSize { get; set; }
///<summary>/// Gets or sets the Qty of rows skipped from the top of the select.....///</summary>public System.Int32 Skip { get; set; }
///<summary>/// Gets or sets the Qty of rows taken.....///</summary>public System.Int32 Take { get; set; }
///<summary>/// Gets or sets the amount of items to display.///</summary>public System.Int32 TotalItems { get; set; }
///<summary>/// Gets the total pages available to display..///</summary>#endregion
}
The Data Class.
This class will contain your data and the IPagesInfo properties. In this sample, it will be called PersonsPager.
Note: You can easily create it by executing the stored procedure, copying the result and in a new code window (for example, an empty one created for PersonsPager), paste it by the Edit – Paste Special-Past JSON as Classes.
Then replace the names autogenerated by your own and change the type for the second one for the PagesInfo class.
The pasted code names the main class as Rootobject, which has been renamed to PersonsPager.
The Pager class will be changed by PagesInfo, and the Pager class defined could be removed.
This is the final code for the PersonsPager class.
You must add a Razor Component. In this sample, it is called Pager.razor.
In the UI code, I use an UL tag, adding a button for previous group of pages (when the user moves beyond the first set of page numbers), buttons for the different page numbers, and a button for the next group as well.
For the buttons, the pagination, page-item, and page-link classes are used.
<navclass="text-center"><ulclass="pagination">@{//Previous page group button.stringclassName="page-item "+ (!PagesInfo.HasPreviousGroup ?" disabled":""); //If there is in previous group, the button will be disabled<liclass="@className"><buttonclass="page-link "@onclick="@(()=>
{
if (PagesInfo.HasPreviousGroup)
ChangePage(PagesInfo.PreviousGroupStart);
}
)">
⏪
</button></li>}@*Buttons for page numbers*@@for (Int32i= PagesInfo.FirstPageNumber; i<= PagesInfo.LastPageNumber; i++)
{
intpageSelector=i;
className="page-item "+ (i== PagesInfo.CurrentPage ?" active":"");
<liclass="@className"><buttonclass="page-link"@onclick="@(()=>ChangePage(pageSelector))">@string.Format("{0:00}", pageSelector)</button></li>
}
@{//Next page group button.className="page-item "+ (!PagesInfo.HasNextGroup ?" disabled":"");
<liclass="@className"><buttonclass="page-link "@onclick="@(()=>
{ if (PagesInfo.HasNextGroup)
ChangePage(PagesInfo.NextGroupStart);
}
)">
⏩
</button></li>}</ul></nav>
In the code section, parameters are defined for:
an instance of a class implementing the IPagerInfo
a value to persist the selected page
an EventCallback to notify the client page about the changes in the selection by the user.
Finally, the ChangePage function called every time the user clicks ant of the buttons is defined to change the selected page and notify the client page.
Bien, como fue comentado en la publicación anterior, vamos a comenzar a crear nuestra propia biblioteca de herramientas y elementos de soporte (a algunos les ha dado por llamarlo «Framework» ).
Aprovechando las ventajas de diseño de Visual Studio (por cierto, estoy usando VS2017), el gráfico representa el esquema base de la solución.
Este esquema lo iremos ampliando con otras bibliotecas a futuro. (Por cierto, son bibliotecas, aunque la mayoría les digan librerías… que librerías son las que venden libros ).
Para facilitar la comprensión del código, la misma solución está desarrollada en paralelo, en C# y en VB. Lo interesante es que son perfectamente intercambiables (y mezclables, puedes usar una dll de un idioma y otra de otro, y funcionarán perfectamente).
Common.
Primero lo primero. O sea, elementos comunes que podrán ser utilizados por cualesquiera de nuestras bibliotecas
En esta biblioteca incluimos toda la implementación de los valores con nombre, a saber:
INameValue
NameValue<T>
NameValueList
Agregamos algunos atributos que seguramente usaremos a futuro (para implementar, por ejemplo, modelos de datos)
DataFieldAttribute
DataKeyAttribute
DataRelationAttribute
DefaultValueEnum
Y una interfaz que nos permitirá luego diferenciar errores cuando sean propios del sistema (excepciones), o los que implementen esta interfaz, aquellos que deben ser informados al usuario.
IUserMessageError
Tools
Como segundo proyecto de nuestro entorno de trabajo, creamos la biblioteca de Herramientas, que nos será útil para muchas acciones comunes a cualquier proyecto. En ella, que irá evolucionando seguramente, definiremos aquellos métodos que nos serán útiles repetidas veces.
Lo importante es, darse cuenta a tiempo, cuándo una función que escribimos, podría ser útil en otro caso .
Tenemos en esta biblioteca, las siguientes clases:
Reflection: de la que hablamos hace unas publicaciones atrás, que nos permite evaluar características de nuestras propias clases.
Logger: como su nombre indica, esta clase permitirá escribir en un log, sea este un archivo físico o el de Aplicación del Sistema Operativo.
Interaction: Este es un caso particular… me encontré en más de una ocasión, que cierta funcionalidad implementada en uno de los lenguajes de programación, no tenía correspondencia en el otro. Raro, pero a veces pasa. Como ejemplo, una de las funciones útiles en VB que no existe en C#. CallByName y que en esta clase, podemos implementar. Para mantener coherencia entre distintas versiones de bibliotecas, aun cuando en VB esa función existe como nativa, la mantenemos implementada en esta biblioteca también.
Queda aquí como ejemplo, ambas versiones de esta función, para que quede claro el concepto. Por lo demás, el código está perfectamente comentado. (en inglés, para que sirva a cualquiera que lo desee utilizar, sin barreras de idioma).
En la próxima, una biblioteca que plantea algún desafío interesante, y una herramienta de Visual Studio que nos permite subsanarlo… y la publicación del conjunto completo de código.
Donde nos ponemos a investigar un poco que tienen nuestros objetos.
Donde nos ponemos a investigar un poco que tienen nuestros objetos.
Motivación.
Para muchas de las cosas que venimos explicando, acerca de generalizaciones de código, métodos compartidos y demás, cuando programemos, seguramente querremos interactuar con dichas generalizaciones, pero de un modo más específico.
Por ejemplo, en la anterior publicación, hablábamos de Atributos.
¿Cómo hago para saber si una propiedad tiene determinado atributo?
¿Cómo obtengo los miembros que tienen cierto atributo?
El espacio de nombres System.Reflection.
El .Net Framework implementa este espacio de nombres específico, para poder investigar los objetos.
Con él, a partir del Type de un objeto, se puede obtener información de sus miembros, sus atributos, su jerarquía de herencia etc.
El espacio expone objetos que sirven como descriptores de las características de un tipo, como, por Ejemplo:
Assembly
MemberInfo
PropertyInfo
Dichos objetos se obtienen, como fue dicho, del Type de un objeto o instancia utilizando el método GetType()
Obtener el tipo
Existen distintos métodos para obtener el tipo, dependiendo si se tiene o no una instancia del mismos.
Los métodos, todos retornando un objeto Type, se describen en el siguiente cuadro
C#
VB
Con Instancia
variable.GetType()
variable.GetType
Sin Instancia
typeof(Nombre_De_Clase)
GetType(Nombre_de_Clase)
Funciones.
En muchos casos, utilizaremos LinQ sobre objetos para obtener información (lo cual facilita la codificación y acelera el proceso).
Comprobar Propiedades.
Comprobar si tiene un atributo
privatestaticbool HasAttribute(PropertyInfo p, string attributeName) { var attrs = p.GetCustomAttributesData(); if (attrs.Count >0) { var attrs2 = (from aa in attrs where aa.AttributeType.Name == attributeName select aa).ToList(); return attrs2.Count >0; } returnfalse; }
PrivateSharedFunction HasAttribute(p AsPropertyInfo, attributeFullName AsString) AsBoolean Dim attrs = p.GetCustomAttributesData If attrs.Count >0Then Dim attrs2 = (From aa In attrs Where aa.AttributeType.Name = attributeFullName).ToList Return attrs2.Count >0 EndIf ReturnFalse EndFunction
Como se ve, se aplica sobre un objeto PropertyInfo y filtra directamente por nombre.
Obtener los atributos de una propiedad
publicstaticNameValueList GetAttributes(PropertyInfo property) { NameValueList result =newNameValueList(); var v = property.GetCustomAttributesData(); foreach (var a_loopVariable in v) { var a = a_loopVariable; foreach (var named_loopVariable in a.NamedArguments) { var named = named_loopVariable; NameValue<object> nv =newNameValue<object>(named.MemberName) { RawValue = named.TypedValue.Value }; result.Add(nv); } } return result; }
PublicSharedFunction GetAttributes(ByVal [property] AsPropertyInfo) As DSCommon.NameValueList Dim result AsNew DSCommon.NameValueList Dim v = [property].GetCustomAttributesData() ForEach a In v ForEach named In a.NamedArguments Dim nv AsNew DSCommon.NameValue(OfObject)(named.MemberName) With {.RawValue = named.TypedValue.Value} result.Add(nv) Next Next Return result EndFunction
En este caso, la función retorna todos los atributos asignados a un PropertyInfo (o sea, a una propiedad), y retorna nuestro NameValueList con los valores obtenidos.
Obtener miembros específicos por atributo de un tipo
Obtener las propiedades que poseen un determinado atributo.
publicstaticList<PropertyInfo> GetPropertiesByAttribute(Type type, Attribute attribute) { string s = attribute.GetType().ToString(); var v = (from p in type.GetProperties() where (from aa in p.GetCustomAttributes(true) where aa.ToString() == s select aa).Count() >0select p); List<PropertyInfo> l =newList<PropertyInfo>(); l.AddRange(v); return l; }
PublicSharedFunction GetPropertiesByAttribute(ByVal type AsType, ByVal attribute AsAttribute) AsList(OfPropertyInfo) Dim s AsString= attribute.GetType.ToString Dim v = (From p AsPropertyInfoIn type.GetProperties Where (From aa In p.GetCustomAttributes(True) Where aa.ToString = s).Count >0) Dim l AsNewList(OfPropertyInfo) l.AddRange(v) Return l EndFunction
En este caso, la función se aplica sobre un tipo, del cual se obtienen las propiedades. De ellas, aquellas que tengan asignado un determinado atributo.
Obtener miembros específicos por nombre del atributo de un tipo
publicstaticList<PropertyInfo> GetPropertiesByAttribute( Type type, string attributeFullName) var v = (from p in type.GetProperties() where (HasAttribute(p, attributeFullName)) select p); List<PropertyInfo> l =newList<PropertyInfo>(); l.AddRange(v); return l; }
PublicSharedFunction GetPropertiesByAttribute(ByVal type AsType, ByVal attributeFullName AsString) AsList(OfPropertyInfo) Dim v = (From p AsPropertyInfoIn type.GetProperties Where (HasAttribute(p, attributeFullName))) Dim l AsNewList(OfPropertyInfo) l.AddRange(v) Return l EndFunction
En este caso, en lugar de utilizar el atributo propiamente dicho, se utiliza su nombre.
Comprobar clases.
Obtener los nombres de las clases que heredan de otra (o implementan una interfaz)
publicstaticstring[] GetClasesOfType<T>(System.Reflection.Assembly assembly) { Type theI =typeof(T); var elems = (from el in assembly.GetTypes() where theI.IsAssignableFrom(el) select el.Name).ToArray(); return elems; }
PublicSharedFunction GetClasesOfType(OfT)(assembly As System.Reflection.Assembly) AsString() Dim theI AsType=GetType(T) Dim elems = (From el In assembly.GetTypes() Where theI.IsAssignableFrom(el) Select el.Name).ToArray() Return elems EndFunction
Terminamos teniendo una nueva herramienta
Agregaremos entonces una clase a nuestra biblioteca de herramientas (Tools), que podríamos llamar, precisamente, Reflection, para exponer estos métodos.
Nótese que todos están declarados como static (Shared en VB), para poder utilizarlas sin necesitar crear una instancia de la clase.
En la próxima entrega, repasaremos el espacio de nombre Tools y lo complementaremos con más cositas
Siguiendo con la biblioteca de útiles, veamos ahora de tener una clase que nos permita almacenar y manipular varias instancias de la clase NameValue de la publicación pasada.
Personalizando una lista
Esto en realidad, puede ser tan sencillo como crear una clase que herede de la genérica List. Sin embargo, para hacerla debemos definir de que clase es esa lista y, como vimos anteriormente, estamos definiendo diferentes clases, de acuerdo al tipo de dato a almacenar.
Para ello, viene en nuestra ayuda la interfaz INameValue.
Public Class NameValueList
Inherits List(Of INameValue)
Y ya la tenemos.
Sin embargo, podríamos agregar algunas funcionalidades como el agregado de un elemento, dependiendo del tipo de dato.
Esto permitiría tener un método genérico que “traduzca” tipos de datos, por ejemplo, cuando necesitamos transformar desde otros entornos (como datos venidos desde bases de datos)
Automatizando el agregado de elementos
Definamos pues, un método que cree una instancia de la clase NameValue, y le asigne el valor recibido:
Private Function CreateNameValue(
name As String,
value As Object,
type As Type) As INameValue
Como vemos, el valor se recibe como de tipo objeto, que admitirá cualquier valor, y en parámetro independiente, el tipo deseado.
Lo que retorna la función es la interfaz genérica INameValue.
Internamente, la función define una variable para crear el valor de retorno, y obtenemos, como cadena de caracteres, el nombre del tipo a utilizar.
Dim retvalue As INameValue
Dim sName As String = type.Name
Luego, seleccionamos basados en dicho nombre, para crear la instancia a retornar, utilizando la sentencia Select Case
Select Case sName
Case "BigInt"
retvalue =
New NameValue(Of Int64)(name)
Case "Binary"
retvalue =
New NameValue(Of Byte())(name)
Case "Bit"
retvalue =
New NameValue(Of Boolean)(name)
(la lista es bastante más larga).
Una ventaja de la sentencia Select es que nos permite ejecutar una acción, al seleccionar uno de varios valores en la misma sentencia. Así, por ejemplo, para los valores de cadenas de caracteres, podemos agruparlos en un solo case. Además, en el ejemplo, vemos que no solo usamos los tipos de datos propios de .Net, sino también, otros como VarChar, NVarchar, Text, que son propios de bases de datos:
Case "NChar",
"NText",
"NVarChar",
"Text",
"VarChar",
"Xml",
"String"
retvalue =
New NameValue(Of String)(name)
Ante la ocurrencia no cubierta, mostrar el error.
Aún cuando queramos ser muy detallistas, es factible que no contemplemos todos los tipos posibles. Por ello, si nos encontramos ante esa situación, es importante informarnos de ello con una excepción específica. Cuando ninguna de las opciones de selección ocurre, (el caso diferente), procedemos a ello:
Case Else
Debug.WriteLine(
String.Format(
"Case ""{0}",type.ToString))
Debug.WriteLine(
String.Format(
"retvalue = New NameValue(Of {0})(name)",
type.ToString))
Throw New NotImplementedException(
String.Format(
"Missing type={0}",
type.ToString)
)
Pero también existen los tipos que admiten valores nulos.
Como es posible recibir de esos tipos de datos, debiéramos contemplar también esa posibilidad.
para ello, detectamos si el tipo admite nulos (es Nullable), y utilizamos un select similar, para dichos valores, dejando el ya definido, para los que no lo admiten, encapsulando esta decisión, obviamente, con una sentencia If.
Dim isNullable As Boolean =
type.FullName.StartsWith("System.Nullable")
If isNullable Then
'Get the base type Name
Dim splitter = Split(type.FullName, "[")
splitter = splitter(2).Split(",")
sName = splitter(0).Replace("System.", "")
End If
If isNullable Then
Exponiendo la creación de nuevos elementos.
Todo este procedimiento lo hemos incluido en un miembro privado, solo visible dentro de la propia clase, para crear el nuevo elemento. Definamos pues un método visible externamente que además de crearlo, lo agregue a la lista.
Public Sub AddElement(
name As String,
value As Object,
type As Type)
Me.Add(
CreateNameValue(
name,
value,
type)
)
End Sub
Obteniendo un valor por su nombre.
La clase base List solo es capaz de retornar un elemento específico por su índice pero no por un literal (lo cual, por cierto, si puede hacerlo un Dictionary).
Pero elegimos List para que fuese más liviano y además, fácilmente serializable.
Además, perfectamente podemos implementar el método que nos retorne el valor por el nombre, con el siguiente código.
Public Function GetByName(
name As String) As INameValue
Return (
From el
In Me
Where
el.Name.ToUpper =
name.ToUpper
).FirstOrDefault
End Function
Eehhh si, estoy usando LinQ. (ya voy a publicar algo específico de esto enseguida)
Y así quedaría el código completito
Public Class NameValueList
Inherits List(Of INameValue)
Public Sub AddElement(
name As String,
value As Object,
type As Type)
Me.Add(
CreateNameValue(
name,
value,
type)
)
End Sub
Private Function CreateNameValue(
name As String,
value As Object,
type As Type) As INameValue
Dim retvalue As INameValue
Dim sName As String = type.Name
Dim isNullable As Boolean =
type.FullName.StartsWith("System.Nullable")
If isNullable Then
'Get the base type Name
Dim splitter = Split(type.FullName, "[")
splitter = splitter(2).Split(",")
sName = splitter(0).Replace("System.", "")
End If
If isNullable Then
Select Case sName
Case "BigInt"
retvalue = New NameValue(Of Int64?)(name)
Case "Bit"
retvalue = New NameValue(Of Boolean?)(name)
Case "Boolean"
retvalue = New NameValue(Of Boolean?)(name)
Case "Char"
retvalue = New NameValue(Of Char?)(name)
Case "DateTime"
retvalue = New NameValue(Of DateTime?)(name)
Case "Decimal"
retvalue = New NameValue(Of Decimal?)(name)
Case "Float"
retvalue = New NameValue(Of Decimal?)(name)
Case "Int"
retvalue = New NameValue(Of Integer?)(name)
Case "Money"
retvalue = New NameValue(Of Decimal?)(name)
Case "Real"
retvalue = New NameValue(Of Double?)(name)
Case "UniqueIdentifier"
retvalue = New NameValue(Of Guid?)(name)
Case "SmallDateTime"
retvalue = New NameValue(Of DateTime?)(name)
Case "SmallInt"
retvalue = New NameValue(Of Int16?)(name)
Case "SmallMoney"
retvalue = New NameValue(Of Decimal?)(name)
Case "TinyInt"
retvalue = New NameValue(Of Int16?)(name)
Case "Date", "System.DateTime"
retvalue = New NameValue(Of Date?)(name)
Case "Time"
retvalue = New NameValue(Of DateTime?)(name)
Case "DateTime2"
retvalue = New NameValue(Of DateTime?)(name)
Case "DateTimeOffset"
retvalue = New NameValue(Of TimeSpan?)(name)
Case "Int32"
retvalue = New NameValue(Of System.Int32?)(name)
Case "Int16"
retvalue = New NameValue(Of System.Int16?)(name)
Case "Int64"
retvalue = New NameValue(Of System.Int64?)(name)
Case "Double"
retvalue = New NameValue(Of System.Double?)(name)
Case Else
Debug.WriteLine(String.Format("Case ""{0}", type.ToString))
Debug.WriteLine(String.Format("retvalue = New NameValue(Of {0})(name)", type.ToString))
Throw New NotImplementedException(String.Format("Missing type={0}", type.ToString))
End Select
Else
Select Case sName
Case "BigInt"
retvalue =
New NameValue(Of Int64)(name)
Case "Binary"
retvalue =
New NameValue(Of Byte())(name)
Case "Bit"
retvalue =
New NameValue(Of Boolean)(name)
Case "Boolean"
retvalue = New NameValue(Of Boolean)(name)
Case "Char"
retvalue = New NameValue(Of Char)(name)
Case "DateTime"
retvalue = New NameValue(Of DateTime)(name)
Case "Decimal"
retvalue = New NameValue(Of Decimal)(name)
Case "Float"
retvalue = New NameValue(Of Decimal)(name)
Case "Image"
retvalue = New NameValue(Of Byte())(name)
Case "Int"
retvalue = New NameValue(Of Integer)(name)
Case "Money"
retvalue = New NameValue(Of Decimal)(name)
Case "NChar",
"NText",
"NVarChar",
"Text",
"VarChar",
"Xml",
"String"
retvalue =
New NameValue(Of String)(name)
Case "Real"
retvalue = New NameValue(Of Double)(name)
Case "UniqueIdentifier"
retvalue = New NameValue(Of Guid)(name)
Case "SmallDateTime"
retvalue = New NameValue(Of DateTime)(name)
Case "SmallInt"
retvalue = New NameValue(Of Int16)(name)
Case "SmallMoney"
retvalue = New NameValue(Of Decimal)(name)
Case "Timestamp"
retvalue = New NameValue(Of Byte())(name)
Case "TinyInt"
retvalue = New NameValue(Of Int16)(name)
Case "VarBinary"
retvalue = New NameValue(Of Byte())(name)
Case "Variant"
retvalue = New NameValue(Of Object)(name)
Case "Udt"
retvalue = New NameValue(Of Object)(name)
Case "Structured"
retvalue = New NameValue(Of Object)(name)
Case "Date", "System.DateTime"
retvalue = New NameValue(Of Date)(name)
Case "Time"
retvalue = New NameValue(Of DateTime)(name)
Case "DateTime2"
retvalue = New NameValue(Of DateTime)(name)
Case "DateTimeOffset", "TimeSpan"
retvalue = New NameValue(Of TimeSpan)(name)
Case "Int32"
retvalue = New NameValue(Of System.Int32)(name)
Case "Int16"
retvalue = New NameValue(Of System.Int16)(name)
Case "Int64"
retvalue = New NameValue(Of System.Int64)(name)
Case "Byte[]"
retvalue = New NameValue(Of System.Byte())(name)
Case "Double"
retvalue = New NameValue(Of System.Double)(name)
Case Else
Debug.WriteLine(
String.Format(
"Case ""{0}",
type.ToString))
Debug.WriteLine(
String.Format(
"retvalue = New NameValue(Of {0})(name)",
type.ToString))
Throw New NotImplementedException(
String.Format(
"Missing type={0}",
type.ToString)
)
End Select
End If
retvalue.RawValue = value
Return retvalue
End Function
Public Function GetByName(
name As String) As INameValue
Return (
From el
In Me
Where
el.Name.ToUpper =
name.ToUpper
).FirstOrDefault
End Function
End Class
El equipo de Visual Basic de Microsoft ha vuelto a las andadas y han publicado una interesante lista de trucos acerca de como compartir código entre aplicaciones Windows 8 y Windows Phone.
Donde empezamos a crear bibliotecas de herramientas útiles.
Finalmente, luego del parate de los últimos tiempos, volvemos al ruedo con cuestiones ya directamente prácticas.
Comencemos por definir algunas herramientas útiles a nuestros quehaceres diarios.
Para ello, crearemos un proyecto de elementos comunes (de nombre Common, por ejemplo), para definir allí elementos que serán útiles en muchos otros proyectos.
En los ejemplos, utilizaré mis propios proyectos como ejemplo. Por ello, todos ellos tienen un espacio de nombres común: DS 🙂
Entonces, comencemos creando el proyecto [ComoQuierasLlamarlo].Common.
O sea, te creas n Nuevo Proyecto, eliges el leguaje de programación de tu elección (yo, prefiero VB, pero da igual).
Te recomiendo que mantengas en una misma solución, todas tus bibliotecas de utilidades, para poder vincularlas desde otras soluciones y aislar el código.
Además, si utilizas Azure Team Foundation, o cualquier otro protector de código, con Git o similares, mantenerlo en una solución protegida te ayudará a nunca perder tu código.
Clases para intercambiar valores.
Muchas veces, es necesario intercambiar valores entre funciones, bibliotecas, elementos externos, etc. y queremos mantener aisladas las referencias a bibliotecas específicas (por ejemplo, a bibliotecas de bases de datos, de repositorios, etc.).
También sucede que algunas bibliotecas, (de nuevo, bases de datos, por ejemplo), no siempre utilizan los mismos tipos de datos que otras. Entonces, se podría necesitar pasar valores como tipo “Object”, o sea, de modo no tipificado.
Por otra parte ya vimos que siempre que sea factible, es mejor utilizar los tipos de datos específicos.
Y necesitaremos conocer el nombre de cada uno de los valores que queremos intercambiar.
Entonces necesitaríamos
Una clase que exponga un valor con su tipo especifico
Que posea el nombre del valor en cuestión (o sea, nombre del argumento o propiedad)
Una forma genérica de acceder al valor, como si fuera objeto.
Si además quisiéramos contenerlas en una lista, colección o diccionario, si las hiciésemos específicas, sería muy complejo definir dicha colección, dadas las diferencias de tipos.
Por ello, recurriremos a una interfaz de nuestros objetos “Valor-Nombre”:
Public Interface INameValue
Property Name As String
Property RawValue As Object
End Interface
Creando clases genéricas
Estando así las cosas, y dadas nuestras necesidades, es cuando surge la posibilidad de utilizar generalizaciones (Generics) para poder definir nuestra clase de nombre-valor, acorde a cada tipo de dato.
Una clase definida como genérica, permite definir la misma para que en el momento de crear una instancia, recién allí se defina el tipo de dato en cuestión.
Se debe declarar la clase, indicando que será de un “tipo no definido” utilizando la sintaxis (Of T). (<T> en C#)
Entonces, en cada método o función que se requiera especificar el tipo que se asignará cuando se cree la instancia, se utiliza la letra T (puede ser cualquier letra, sencillamente se usa la T de Type = Tipo)
Por ejemplo, nuestra clase expondrá el valor con el tipo específico, con la propiedad Value; entonces, la declaratoria sería:
Public Property Value As T
La propiedad Nombre
La propiedad RawValue que, al recibir un valor, debe convertirlo al tipo de datos correcto.
#Region "INameValue"
Implements INameValue
Public Property Name As String Implements INameValue.Name
Public Property Value As T
Public Property RawValue As Object Implements INameValue.RawValue
Get
Return Me.Value
End Get
Set(value As Object)
If Not value Is Nothing AndAlso Not TypeOf value Is DBNull OrElse TypeOf Me.Value Is Nullable Then
Me.Value = CType(value, T)
End If
End Set
End Property
#End Region
Además, al ser tipos no específicos, cabe que tengamos que manipular los operadores. Como ejemplo, vayan estos dos:
Public Shared Operator =(first As NameValue(Of T), second As NameValue(Of T)) As Boolean
Return first.RawValue.ToString = second.RawValue.ToString
End Operator
Public Shared Operator <>(first As NameValue(Of T), second As NameValue(Of T)) As Boolean
Return first.RawValue.ToString <> second.RawValue.ToString
End Operator
Finalmente, agreguemos un constructor para poder definir nuevas instancias pasando el nombre.
Sub New(name As String)
Me.Name = name
End Sub
El mismo código, en lenguaje C#.
public class NameValue<T> : INameValue
{
public string Name { get; set; }
public object RawValue
{
get
{
return this.Value;
}
set
{
if (value != null && value != DBNull.Value || Nullable.GetUnderlyingType(this.Value.GetType()) != null)
{
this.Value = (T)value;
}
}
}
public T Value;
public static bool operator ==(NameValue<T> first, NameValue<T> second)
{
return first.RawValue.ToString() == second.RawValue.ToString();
}
public static bool operator !=(NameValue<T> first, NameValue<T> second)
{
return first.RawValue.ToString() != second.RawValue.ToString();
}
public NameValue(string name)
{
this.Name = name;
}
}
Cuando se trata de generalizar nuestro código, existe otra forma posible de hacer esto, que tiene algunas diferencias en cuanto a su implementación, pero además ciertos usos que nos permiten definir mecánicas, inclusive con actores esto es programadores, externos que usen otros desarrollos.
Es el caso de las interfaces. Por definirlo brevemente, un interfaz es ni más ni menos, un contrato que debemos cumplir nuestro código.
La diferencia fundamental entre un interfaz y una clase base, es que la interfaz en sí misma no contiene código de ejecución. Sencillamente lo que hace este definir cómo se verán los objetos desde fuera.
A diferencia de lo que sucede con una clase base, un objeto definido por nosotros, puede implementar varias interfaces distintas, mientras que sólo puede heredar de una clase base.
Esto ha llevado a muchas discusiones teóricas, dado que en varios entornos se pretenden heredades de múltiples clase base. Aun cuando esto podría ser útil en ciertos y determinados casos, desde mi punto de vista, esto hace que el mantenimiento de ese código resulte mucho más dificultoso.
Llegado el caso de pretenderse heredar de múltiples clases, lo que debiera hacerse es definir en cascada las herencias, de modo tal de poder implementar código del más básico hasta el más especializado, y no hacer que se hereden desde clases base distintas.
Así, en el ejemplo que estábamos utilizando, la clase Vegetal que hereda de la clase SerVivo, podría a su vez ser heredada, por una clase Árbol.
Esta podría definir nueva propiedades, como tipo de raíces, formato de la hoja, cantidad ramificaciones, etc. Y luego tendríamos otras clases que al heredar de Árbol, también estarían heredando en forma indirecta de Vegetal y también de SerVivo.
Uso de Interfaces
Una cosa distinta son las interfaces. Un interfaz es, en definitiva, una especificación de algo que se debe cumplir, esto es, que cada clase que implementa dicha interfaz, obligatoriamente debe exponer todos y cada uno de los miembros que la interfaz define.
Es en ese sentido que decimos que la interfaz es un contrato. Al existir, una biblioteca de código que conozca la interfaz, podrá acceder a cualquiera de sus métodos, aún sin tener una referencia directa, de la biblioteca de clases que implementa dichos métodos.
Esto se suele utilizar, cuando no podemos tener conocimiento claro o relación directa, con el conjunto de código con el cual vamos a tratar.
Imaginemos que por ejemplo, estamos creando un proceso, que necesita utilizar datos obtenidos de distintos orígenes, y que no sabemos a ciencia cierta, ni cuáles serán, ni cómo serán implementados.
O mejor aún, que estamos creando un componente, que sea capaz de realizar ciertas tareas, y que luego otra programadores pueden utilizar, en futuras aplicaciones o implementaciones de los orígenes de datos.
Inclusive a futuro, otro programadores de otras empresas, podrían implementar sus propios orígenes de datos sin tener que referenciar todo nuestro código, bastando sencillamente que conocieran las interfaces correctas.
Así, las interfaces podrían definirse en una biblioteca, que podría entregarse a cualquier programador externo, para que haga sus propias implementaciones de orígenes de datos, que luego nuestro componente o aplicativo pudiese utilizar sin mayores inconvenientes.
Criterios de definición de un interfaz.
Estos son los criterios básicos para la definición de cualquier interfaz.
No contiene código ejecutable.
No se pueden definir alcances: todos los miembros declarados en un interfaz son en forma predeterminada, públicos.
Se pueden definir tanto propiedades como métodos; si la propiedad es de sólo lectura o sólo escritura quedará consignado en la declaración del interfaz, y luego las clase que implemente solo podrán exponer estos métodos.
En la declaración se podrán utilizar tipo de datos personalizados, como podrían ser enumeraciones, clases personalizadas, estructuras, etc. Sin embargo, en dicho caso será conveniente, que estos tipos de datos se encuentran declarados en la misma biblioteca de clases que las interfaces, para mantenerlas aisladas del resto de los componentes, y así poder distribuirlos sin mayores inconvenientes.
Ejemplo.
El siguiente ejemplo muestra cómo sería la interfaz IVida, que se correspondería con nuestra clase base de SerVivo, si quisiéramos definir sus miembros aisladamente de la clase base.
Por convención, se suele definir el nombre de una interfaz, comenzando con I mayúscula.
Public Interface IVida
Sub Nacer()
Sub Crecer()
Sub Reproducir()
Sub Morir()
Sub GenerarEnergía()
End Interface
Una vez definida un interfaz, cualquier clase que quiera indicar que cumple este contrato, deberá implementar la interfaz.
Como suele suceder, diferentes lenguajes de programación, utilizan distintos mecanismos para indicar esto. Para Visual Basic existe una palabra reservada, implements, mientras que el C#, se define exactamente igual que una herencia esto es, indicando después del nombre de declaración de la clase, el caracter dos puntos (:) y el nombre de la interfaz o interfaces que se implementan, separados por comas. En el caso que además de implementar interfaces, se quisiera heredar de otra clase, el César la clase de la cual se hereda debe estar inmediatamente luego del carácter dos puntos.
Así quedaría entonces, la clase vegetal heredando de SerVivo e implementando IVida.
Public Class Vegetal
Inherits SerVivo
Implements IVida
Private Sub Fotosíntesis() Implements IVida.GenerarEnergía
End Sub
End Class
public class Vegetal : SerVivo,IVida
{
private void Fotosíntesis()
{
}
void IVida.GenerarEnergía()
{
Fotosíntesis();
}
Deberíamos resaltar que, mientras en Visual Basic la implementación de cada miembro de la interfaz es declarativa, utilizando la palabra reservada “Implements”, en C# la implementación se hace por coincidencia de nombre. Es por eso que, en VB, el método FotoSíntesis puede implementar directamente el GenerarEnergía de la interfaz, mientras que en C# se necesitan ambos métodos, y hacer la llamada en cascada.
Finalmente, un método que Implementa una interfaz en Visual Basic, puede ser privado (y por tanto, solo visible desde la interfaz), mientras que, nativamente, en C# debe cumplir con el alcance.
Sin embargo, como se ve en el ejemplo, el método que implementará interfaz, puedas hacerlo implícitamente, utilizando sólo el nombre del método, o como en este caso, explícitamente antecediendo al nombre del método, el nombre de la interfaz seguido por un punto.
Al hacer la llamada en cascada, el método que realmente posee el código, en nuestro ejemplo FotoSíntesis, queda declarado igualmente como privado, con lo cual estamos obteniendo el mismo resultado final.
Son sencillamente, diferentes formas de hacer la misma cosa.
En la siguiente publicación, comenzaremos a utilizar combinatoria as de estas características, en un conjunto de bibliotecas que nos permitan realizar finalmente, tareas útiles.
Segunda época del rockblog "Atascado en los 70". VIEJAS canciones y artistas PASADOS DE MODA. Tratamos al lector de usted y escribimos "rocanrol" y "roquero" con ortografía castellana.