Inicio rápido: creación de una aplicación de Table API con el SDK de .NET y Azure Cosmos DB
SE APLICA A:
Table API
En esta guía de inicio rápido, se muestra cómo acceder a Table API de Azure Cosmos DB desde una aplicación de .NET. Table API de Cosmos DB es un almacén de datos sin esquema que permite a las aplicaciones almacenar datos NoSQL estructurados en la nube. Dado que los datos se almacenan en un diseño sin esquema, las nuevas propiedades (columnas) se agregan automáticamente a la tabla cuando se agrega un objeto con un nuevo atributo a la tabla.
Las aplicaciones de .NET pueden acceder a Table API de Cosmos DB mediante el paquete NuGet Azure.Data.Tables. El paquete Azure.Data.Tables es una biblioteca de .NET Standard 2.0 que funciona con aplicaciones de .NET Framework (4.7.2 y versiones posteriores) y .NET Core (2.0 y versiones posteriores).
Requisitos previos
La aplicación de ejemplo está escrita en .NET Core 3.1, aunque los principios se aplican tanto a las aplicaciones de .NET Framework como a las de .NET Core. Puede usar Visual Studio, Visual Studio para Mac o Visual Studio Code como IDE.
Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.
Aplicación de ejemplo
La aplicación de ejemplo de este tutorial se puede clonar o descargar desde el repositorio https://github.com/Azure-Samples/msdocs-azure-data-tables-sdk-dotnet. En el repositorio de ejemplo se incluyen tanto una aplicación de inicio como una completa.
git clone https://github.com/Azure-Samples/msdocs-azure-data-tables-sdk-dotnet
La aplicación de ejemplo usa datos meteorológicos como ejemplo para demostrar las funcionalidades de Table API. Los objetos que representan observaciones meteorológicas se almacenan y recuperan mediante Table API, incluido el almacenamiento de objetos con propiedades adicionales para mostrar las funcionalidades sin esquema de Table API.
1 - Creación de una cuenta de Azure Cosmos DB
En primer lugar, debe crear una cuenta de Table API de Cosmos DB que contendrá las tablas usadas en la aplicación. Esto se puede realizar mediante Azure Portal, la CLI de Azure o Azure PowerShell.
Inicie sesión en Azure Portal y siga estos pasos para crear una cuenta de Cosmos DB.
2 - Creación de una tabla
A continuación, debe crear una tabla dentro de la cuenta de Cosmos DB para que la aplicación la use. A diferencia de una base de datos tradicional, solo es necesario especificar el nombre de la tabla, no las propiedades (columnas) de la tabla. A medida que se cargan datos en la tabla, las propiedades (columnas) se crean automáticamente según sea necesario.
En Azure Portal, complete los pasos siguientes para crear una tabla dentro de la cuenta de Cosmos DB.
3 - Obtención de la cadena de conexión de Cosmos DB
Para acceder a las tablas de Cosmos DB, la aplicación necesitará la cadena de conexión de la tabla para la cuenta de almacenamiento de CosmosDB. La cadena de conexión se puede recuperar mediante Azure Portal, la CLI de Azure o Azure PowerShell.
La cadena de conexión de la cuenta de Cosmos DB se considera un secreto de aplicación y se debe proteger como cualquier otro secreto de aplicación o contraseña. En este ejemplo, se usa la herramienta Administrador de secretos para almacenar la cadena de conexión durante el desarrollo y hacer que esté disponible para la aplicación. Se puede acceder a la herramienta Administrador de secretos desde Visual Studio o desde la CLI de .NET.
Para abrir la herramienta Administrador de secretos desde Visual Studio, haga clic con el botón derecho en el proyecto y seleccione Administrar secretos de usuario en el menú contextual. Se abrirá el archivo secrets.json del proyecto. Reemplace el contenido del archivo por el código JSON siguiente, sustituyendo la cadena de conexión de la tabla de Cosmos DB.
{
"ConnectionStrings": {
"CosmosTableApi": "<cosmos db table connection string>"
}
}
4 - Instalación del paquete NuGet Azure.Data.Tables
Para acceder a Table API de Cosmos DB desde una aplicación de .NET, instale el paquete NuGet Azure.Data.Tables.
Install-Package Azure.Data.Tables
5 - Configuración del cliente de Table en el archivo Startup.cs
Azure SDK se comunica con Azure mediante objetos de cliente para ejecutar diferentes operaciones en Azure. El objeto TableClient es el objeto que se usa para comunicarse con Table API de Cosmos DB.
Normalmente, una aplicación creará un único objeto TableClient por cada tabla que se usará en toda la aplicación. Para ello, se recomienda usar la inserción de dependencias (DI) y registrar el objeto TableClient como singleton. Para más información sobre el uso de la inserción de dependencias con Azure SDK, consulte Inserción de dependencias con Azure SDK para .NET.
En el archivo Startup.cs de la aplicación, edite el método ConfigureServices() para que coincida con el siguiente fragmento de código:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new ValidationFilter());
});
var connectionString = Configuration.GetConnectionString("CosmosTableApi");
services.AddSingleton<TableClient>(new TableClient(connectionString, "WeatherData"));
services.AddSingleton<TablesService>();
}
También tendrá que agregar la siguiente instrucción using en la parte superior del archivo Startup.cs.
using Azure.Data.Tables;
6 - Implementación de las operaciones de tablas de Cosmos DB
Todas las operaciones de tablas de Cosmos DB de la aplicación de ejemplo se implementan en la clase TableService ubicada en el directorio Services. Tendrá que importar los espacios de nombres Azure y Azure.Data.Tables en la parte superior de este archivo para trabajar con los objetos del paquete Azure.Data.Tables del SDK.
using Azure;
using Azure.Data.Tables;
Al principio de la clase TableService, agregue una variable de miembro para el objeto TableClient y un constructor para permitir que el objeto TableClient se inyecte en la clase.
private TableClient _tableClient;
public TablesService(TableClient tableClient)
{
_tableClient = tableClient;
}
Obtención de filas de una tabla
La clase TableClient contiene un método llamado Query que permite seleccionar filas de la tabla. En este ejemplo, dado que no se pasan parámetros al método, se seleccionarán todas las filas de la tabla.
El método también toma un parámetro genérico de tipo ITableEntity que especifica cómo se devolverán los datos de la clase del modelo. En este caso, se usa la clase integrada TableEntity, lo que significa que el método Query devolverá una colección Pageable<TableEntity> como resultado.
public IEnumerable<WeatherDataModel> GetAllRows()
{
Pageable<TableEntity> entities = _tableClient.Query<TableEntity>();
return entities.Select(e => MapTableEntityToWeatherDataModel(e));
}
La clase TableEntity definida en el paquete Azure.Data.Tables tiene propiedades para los valores de clave de partición y clave de fila de la tabla. Juntos, estos dos valores forman una clave única para la fila de la tabla. En esta aplicación de ejemplo, el nombre de la estación meteorológica (ciudad) se almacena en la clave de partición y la fecha y hora de la observación se almacenan en la clave de fila. Todas las demás propiedades (temperatura, humedad, velocidad del viento) se almacenan en un diccionario en el objeto TableEntity.
Es habitual asignar un objeto TableEntity a un objeto de su propia definición. La aplicación de ejemplo define la clase WeatherDataModel en el directorio Models para este propósito. Esta clase tiene propiedades para el nombre de la estación y la fecha de observación a las que se asignarán la clave de partición y la clave de fila, lo que proporciona nombres de propiedad más significativos para estos valores. A continuación, usa un diccionario para almacenar todas las demás propiedades en el objeto. Se trata de un patrón común al trabajar con el almacenamiento de tablas, ya que una fila puede tener un número arbitrario de propiedades y queremos que nuestros objetos del modelo puedan capturar todas ellas. Esta clase también contiene métodos para enumerar las propiedades de la clase.
public class WeatherDataModel
{
// Captures all of the weather data properties -- temp, humidity, wind speed, etc
private Dictionary<string, object> _properties = new Dictionary<string, object>();
public string StationName { get; set; }
public string ObservationDate { get; set; }
public DateTimeOffset? Timestamp { get; set; }
public string Etag { get; set; }
public object this[string name]
{
get => ( ContainsProperty(name)) ? _properties[name] : null;
set => _properties[name] = value;
}
public ICollection<string> PropertyNames => _properties.Keys;
public int PropertyCount => _properties.Count;
public bool ContainsProperty(string name) => _properties.ContainsKey(name);
}
El método MapTableEntityToWeatherDataModel se usa para asignar un objeto TableEntity a un objeto WeatherDataModel. El objeto TableEntity contiene la propiedad Keys para obtener todos los nombres de propiedad incluidos en la tabla para el objeto (de hecho, los nombres de columna de esta fila de la tabla). El método MapTableEntityToWeatherDataModel asigna directamente las propiedades PartitionKey, RowKey, Timestamp y Etag y, a continuación, usa la propiedad Keys para recorrer en iteración las demás propiedades del objeto TableEntity y asignar esas propiedades al objeto WeatherDataModel, menos las propiedades que ya se han asignado directamente.
Edite el código del método MapTableEntityToWeatherDataModel para que coincida con el siguiente bloque de código.
public WeatherDataModel MapTableEntityToWeatherDataModel(TableEntity entity)
{
WeatherDataModel observation = new WeatherDataModel();
observation.StationName = entity.PartitionKey;
observation.ObservationDate = entity.RowKey;
observation.Timestamp = entity.Timestamp;
observation.Etag = entity.ETag.ToString();
var measurements = entity.Keys.Where(key => !EXCLUDE_TABLE_ENTITY_KEYS.Contains(key));
foreach (var key in measurements)
{
observation[key] = entity[key];
}
return observation;
}
Filtrado de las filas devueltas de una tabla
Para filtrar las filas devueltas de una tabla, puede pasar una cadena de filtro de estilo OData al método Query. Por ejemplo, si quisiera obtener todas las lecturas meteorológicas de Chicago entre la medianoche del 1 de julio de 2021 y la medianoche del 2 de julio de 2021 (ambos incluidos), pasaría la siguiente cadena de filtro.
PartitionKey eq 'Chicago' and RowKey ge '2021-07-01 12:00 AM' and RowKey le '2021-07-02 12:00 AM'
Puede ver todos los operadores de filtrado de OData en el sitio web de OData, en la sección Opción de consulta del sistema de filtrado.
En la aplicación de ejemplo, el objeto FilterResultsInputModel está diseñado para capturar los criterios de filtrado proporcionados por el usuario.
public class FilterResultsInputModel : IValidatableObject
{
public string PartitionKey { get; set; }
public string RowKeyDateStart { get; set; }
public string RowKeyTimeStart { get; set; }
public string RowKeyDateEnd { get; set; }
public string RowKeyTimeEnd { get; set; }
[Range(-100, +200)]
public double? MinTemperature { get; set; }
[Range(-100,200)]
public double? MaxTemperature { get; set; }
[Range(0, 300)]
public double? MinPrecipitation { get; set; }
[Range(0,300)]
public double? MaxPrecipitation { get; set; }
}
Cuando se pasa este objeto al método GetFilteredRows de la clase TableService, crea una cadena de filtrado para cada valor de propiedad que no sea NULL. A continuación, crea una cadena de filtrado combinada combinando todos los valores con una cláusula "and". Esta cadena de filtrado combinada se pasa al método Query en el objeto TableClient y solo se devolverán las filas que coincidan con la cadena de filtrado. Puede usar un método similar en el código para construir cadenas de filtrado adecuadas según sea necesario para la aplicación.
public IEnumerable<WeatherDataModel> GetFilteredRows(FilterResultsInputModel inputModel)
{
List<string> filters = new List<string>();
if (!String.IsNullOrEmpty(inputModel.PartitionKey))
filters.Add($"PartitionKey eq '{inputModel.PartitionKey}'");
if (!String.IsNullOrEmpty(inputModel.RowKeyDateStart) && !String.IsNullOrEmpty(inputModel.RowKeyTimeStart))
filters.Add($"RowKey ge '{inputModel.RowKeyDateStart} {inputModel.RowKeyTimeStart}'");
if (!String.IsNullOrEmpty(inputModel.RowKeyDateEnd) && !String.IsNullOrEmpty(inputModel.RowKeyTimeEnd))
filters.Add($"RowKey le '{inputModel.RowKeyDateEnd} {inputModel.RowKeyTimeEnd}'");
if (inputModel.MinTemperature.HasValue)
filters.Add($"Temperature ge {inputModel.MinTemperature.Value}");
if (inputModel.MaxTemperature.HasValue)
filters.Add($"Temperature le {inputModel.MaxTemperature.Value}");
if (inputModel.MinPrecipitation.HasValue)
filters.Add($"Precipitation ge {inputModel.MinTemperature.Value}");
if (inputModel.MaxPrecipitation.HasValue)
filters.Add($"Precipitation le {inputModel.MaxTemperature.Value}");
string filter = String.Join(" and ", filters);
Pageable<TableEntity> entities = _tableClient.Query<TableEntity>(filter);
return entities.Select(e => MapTableEntityToWeatherDataModel(e));
}
Inserción de datos mediante un objeto TableEntity
La manera más sencilla de agregar datos a una tabla es mediante el uso de un objeto TableEntity. En este ejemplo, los datos se asignan desde un objeto del modelo de entrada a un objeto TableEntity. Las propiedades del objeto de entrada que representan el nombre de la estación meteorológica y la fecha y hora de observación se asignan a las propiedades PartitionKey y RowKey respectivamente, que juntas forman una clave única para la fila de la tabla. A continuación, las propiedades adicionales del objeto del modelo de entrada se asignan a las propiedades del diccionario en el objeto TableEntity. Por último, se usa el método AddEntity del objeto TableClient para insertar los datos en la tabla.
Modifique la clase InsertTableEntity de la aplicación de ejemplo para que contenga el código siguiente.
public void InsertTableEntity(WeatherInputModel model)
{
TableEntity entity = new TableEntity();
entity.PartitionKey = model.StationName;
entity.RowKey = $"{model.ObservationDate} {model.ObservationTime}";
// The other values are added like a items to a dictionary
entity["Temperature"] = model.Temperature;
entity["Humidity"] = model.Humidity;
entity["Barometer"] = model.Barometer;
entity["WindDirection"] = model.WindDirection;
entity["WindSpeed"] = model.WindSpeed;
entity["Precipitation"] = model.Precipitation;
_tableClient.AddEntity(entity);
}
Actualizar/insertar (upsert) datos mediante un objeto TableEntity
Si intenta insertar una fila en una tabla con una combinación de clave de partición y clave de fila que ya existe en esa tabla, recibirá un error. Por este motivo, a menudo es preferible usar UpsertEntity en lugar del método AddEntity al agregar filas a una tabla. Si la combinación de clave de partición y clave de fila especificada ya existe en la tabla, el método UpsertEntity actualizará la fila existente. De lo contrario, se agregará la fila a la tabla.
public void UpsertTableEntity(WeatherInputModel model)
{
TableEntity entity = new TableEntity();
entity.PartitionKey = model.StationName;
entity.RowKey = $"{model.ObservationDate} {model.ObservationTime}";
// The other values are added like a items to a dictionary
entity["Temperature"] = model.Temperature;
entity["Humidity"] = model.Humidity;
entity["Barometer"] = model.Barometer;
entity["WindDirection"] = model.WindDirection;
entity["WindSpeed"] = model.WindSpeed;
entity["Precipitation"] = model.Precipitation;
_tableClient.UpsertEntity(entity);
}
Insertar o actualizar/insertar (upsert) datos con propiedades variables
Una de las ventajas de usar Table API de Cosmos DB es que, si un objeto que se carga en una tabla contiene nuevas propiedades, esas propiedades se agregan automáticamente a la tabla y los valores almacenados en Cosmos DB. No es necesario ejecutar instrucciones DDL como ALTER TABLE para agregar columnas como en una base de datos tradicional.
Este modelo proporciona flexibilidad a la aplicación cuando se trabaja con orígenes de datos que pueden agregar o modificar qué datos se deben capturar a lo largo del tiempo o cuando distintas entradas proporcionan datos diferentes a la aplicación. En la aplicación de ejemplo, podemos simular una estación meteorológica que envía no solo los datos meteorológicos básicos, sino también algunos valores adicionales. Cuando un objeto con estas nuevas propiedades se almacena en la tabla por primera vez, las propiedades correspondientes (columnas) se agregarán automáticamente a la tabla.
En la aplicación de ejemplo, la clase ExpandableWeatherObject se basa en un diccionario interno para admitir cualquier conjunto de propiedades en el objeto. Esta clase representa un patrón típico para los casos en los que un objeto debe contener un conjunto arbitrario de propiedades.
public class ExpandableWeatherObject
{
public Dictionary<string, object> _properties = new Dictionary<string, object>();
public string StationName { get; set; }
public string ObservationDate { get; set; }
public object this[string name]
{
get => (ContainsProperty(name)) ? _properties[name] : null;
set => _properties[name] = value;
}
public ICollection<string> PropertyNames => _properties.Keys;
public int PropertyCount => _properties.Count;
public bool ContainsProperty(string name) => _properties.ContainsKey(name);
}
Para insertar o actualizar/insertar (upsert) este tipo de objeto mediante Table API, asigne las propiedades del objeto ampliable a un objeto TableEntity y use los métodos AddEntity o UpsertEntity en el objeto TableClient según corresponda.
public void InsertExpandableData(ExpandableWeatherObject weatherObject)
{
TableEntity entity = new TableEntity();
entity.PartitionKey = weatherObject.StationName;
entity.RowKey = weatherObject.ObservationDate;
foreach (string propertyName in weatherObject.PropertyNames)
{
var value = weatherObject[propertyName];
entity[propertyName] = value;
}
_tableClient.AddEntity(entity);
}
public void UpsertExpandableData(ExpandableWeatherObject weatherObject)
{
TableEntity entity = new TableEntity();
entity.PartitionKey = weatherObject.StationName;
entity.RowKey = weatherObject.ObservationDate;
foreach (string propertyName in weatherObject.PropertyNames)
{
var value = weatherObject[propertyName];
entity[propertyName] = value;
}
_tableClient.UpsertEntity(entity);
}
Actualización de una entidad
Las entidades se pueden actualizar llamando al método UpdateEntity en el objeto TableClient. Dado que una entidad (fila) almacenada mediante Table API podría contener cualquier conjunto arbitrario de propiedades, a menudo resulta útil crear un objeto de actualización basado en un objeto de diccionario similar al objeto ExpandableWeatherObject mencionado anteriormente. En este caso, la única diferencia es la adición de la propiedad Etag, que se usa para el control de la simultaneidad durante las actualizaciones.
public class UpdateWeatherObject
{
public Dictionary<string, object> _properties = new Dictionary<string, object>();
public string StationName { get; set; }
public string ObservationDate { get; set; }
public string Etag { get; set; }
public object this[string name]
{
get => (ContainsProperty(name)) ? _properties[name] : null;
set => _properties[name] = value;
}
public ICollection<string> PropertyNames => _properties.Keys;
public int PropertyCount => _properties.Count;
public bool ContainsProperty(string name) => _properties.ContainsKey(name);
}
En la aplicación de ejemplo, este objeto se pasa al método UpdateEntity de la clase TableService. Este método carga primero la entidad existente de Table API mediante el método GetEntity de TableClient. A continuación, actualiza ese objeto de entidad y usa el método UpdateEntity para guardar las actualizaciones en la base de datos. Observe cómo el método UpdateEntity toma la etiqueta ETag actual del objeto para garantizar que el objeto no haya cambiado desde que se cargó inicialmente. Si desea actualizar la entidad independientemente, puede pasar un valor de Etag.Any al método UpdateEntity.
public void UpdateEntity(UpdateWeatherObject weatherObject)
{
string partitionKey = weatherObject.StationName;
string rowKey = weatherObject.ObservationDate;
// Use the partition key and row key to get the entity
TableEntity entity = _tableClient.GetEntity<TableEntity>(partitionKey, rowKey).Value;
foreach (string propertyName in weatherObject.PropertyNames)
{
var value = weatherObject[propertyName];
entity[propertyName] = value;
}
_tableClient.UpdateEntity(entity, new ETag(weatherObject.Etag));
}
Eliminación de una entidad
Para quitar una entidad de una tabla, llame al método DeleteEntity del objeto TableClient con la clave de partición y la clave de fila del objeto.
public void RemoveEntity(string partitionKey, string rowKey)
{
_tableClient.DeleteEntity(partitionKey, rowKey);
}
7 - Ejecución del código
Ejecute la aplicación de ejemplo para interactuar con Table API de Cosmos DB. La primera vez que ejecute la aplicación, no habrá datos porque la tabla está vacía. Use cualquiera de los botones de la parte superior de la aplicación para agregar datos a la tabla.
Al seleccionar el botón Insert using Table Entity (Insertar mediante TableEntity), se abre un cuadro de diálogo que le permite insertar o actualizar/insertar (upsert) una fila nueva mediante un objeto TableEntity.
Al seleccionar el botón Insert using Expandable Data (Insertar mediante datos ampliables), se abre un cuadro de diálogo que le permite insertar un objeto con propiedades personalizadas, lo que muestra cómo Table API de Cosmos DB agrega automáticamente propiedades (columnas) a la tabla cuando es necesario. Use el botón Add Custom Field (Agregar campo personalizado) para agregar una o varias propiedades nuevas y demostrar esta funcionalidad.
Use el botón Insert Sample Data (Insertar datos de ejemplo) para cargar algunos datos de ejemplo en la tabla de Cosmos DB.
Seleccione el elemento Filter Results (Filtrar resultados) en el menú superior para ir a la página Filter Results (Filtrar resultados). En esta página, rellene los criterios de filtrado para mostrar cómo se puede crear y pasar una cláusula de filtro a Table API de Cosmos DB.
Limpieza de recursos
Cuando haya terminado con la aplicación de ejemplo, debe quitar todos los recursos de Azure relacionados con este artículo de la cuenta de Azure. Para ello, elimine el grupo de recursos.
Se puede eliminar un grupo de recursos mediante Azure Portal haciendo lo siguiente.
Pasos siguientes
En esta guía de inicio rápido, ha obtenido información sobre cómo crear una cuenta de Azure Cosmos DB, crear una tabla mediante el Explorador de datos y ejecutar una aplicación. Ahora ya puede consultar los datos mediante Table API.