static string Read(DatabasePlan databasePlan, DocumentPlan documentPlan) { if (documentPlan.GetIdPlan.Arguments.Count == 0) { return(""); } var singleType = documentPlan.GetIdPlan.Arguments[0].FullTypeName; var singleTypeParam = documentPlan.GetIdPlan.Arguments[0].ArgumentName.Pluralize(); var inputParams = documentPlan.GetIdPlan.Arguments.Count == 1 ? $"System.Collections.Generic.IEnumerable<{singleType}> {singleTypeParam}" : $"System.Collections.Generic.IEnumerable<({documentPlan.GetIdPlan.AsInputParameters()})> ids"; var toId = documentPlan.GetIdPlan.Arguments.Count == 1 ? $"{singleTypeParam}.Select({documentPlan.GetIdPlan.FullMethodName}).Select(Cosmogenesis.Core.DbDocHelper.GetValidId)" : $"ids.Select(x => {documentPlan.GetIdPlan.FullMethodName}({documentPlan.GetIdPlan.ParametersToParametersMapping("x")})).Select(Cosmogenesis.Core.DbDocHelper.GetValidId)"; return($@" /// <summary> /// Try to load {documentPlan.ClassName} documents by id. /// Returns an array of {documentPlan.ClassName} documents (or null if not found) in the same order as the ids were provided. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<{documentPlan.FullTypeName}?[]> {documentPlan.ClassName.Pluralize()}Async({inputParams}) => this.{databasePlan.DbClassName}.ReadByIdsAsync<{documentPlan.FullTypeName}>( partitionKey: this.PartitionKey, ids: {toId}, type: {documentPlan.ConstDocType}); "); }
static string Read(DatabasePlan databasePlan, DocumentPlan documentPlan) => $@" /// <summary> /// Try to load a {documentPlan.ClassName} by id. /// Returns the {documentPlan.ClassName} or null if not found. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> protected virtual System.Threading.Tasks.Task<{documentPlan.FullTypeName}?> {documentPlan.ClassName}ByIdAsync(string id) => this.{databasePlan.DbClassName}.ReadByIdAsync<{documentPlan.FullTypeName}>( partitionKey: this.PartitionKey, id: id, type: {documentPlan.ConstDocType}); /// <summary> /// Try to load a {documentPlan.ClassName} using an id generated by the parameters. /// Returns the {documentPlan.ClassName} or null if not found. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<{documentPlan.FullTypeName}?> {documentPlan.ClassName}Async({documentPlan.GetIdPlan.AsInputParameters()}) => this.{databasePlan.DbClassName}.ReadByIdAsync<{documentPlan.FullTypeName}>( partitionKey: this.PartitionKey, id: Cosmogenesis.Core.DbDocHelper.GetValidId({documentPlan.GetIdPlan.FullMethodName}({documentPlan.GetIdPlan.AsInputParameterMapping()})), type: {documentPlan.ConstDocType}); ";
static string Create(PartitionPlan partitionPlan, DocumentPlan documentPlan) => $@" /// <summary> /// Try to create a {documentPlan.ClassName}. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<Cosmogenesis.Core.CreateResult<{documentPlan.FullTypeName}>> {documentPlan.ClassName}Async({documentPlan.PropertiesByName.Values.Where(x => !partitionPlan.GetPkPlan.ArgumentByPropertyName.ContainsKey(x.PropertyName)).AsInputParameters()}) => this.{partitionPlan.ClassName}.CreateAsync({documentPlan.ClassNameArgument}: new {documentPlan.FullTypeName} {{ {partitionPlan.AsSettersFromDocumentPlanAndPartitionClass(documentPlan)} }}); ";
static string ReadOrCreate(PartitionPlan partitionPlan, DocumentPlan documentPlan) => $@" /// <summary> /// Read a {documentPlan.ClassName} document, or create it if it does not yet exist. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<Cosmogenesis.Core.ReadOrCreateResult<{documentPlan.FullTypeName}>> {documentPlan.ClassName}Async({new[] { "bool tryCreateFirst", documentPlan.PropertiesByName.Values.Where(x => !partitionPlan.GetPkPlan.ArgumentByPropertyName.ContainsKey(x.PropertyName)).AsInputParameters() }.JoinNonEmpty()}) => this.{partitionPlan.ClassName}.ReadOrCreateAsync({documentPlan.ClassNameArgument}: new {documentPlan.FullTypeName} {{ {partitionPlan.AsSettersFromDocumentPlanAndPartitionClass(documentPlan)} }}, tryCreateFirst: tryCreateFirst); ";
static string BuildQuery(DocumentPlan documentPlan) => $@" /// <summary> /// Build a query filtered to {documentPlan.ClassName} documents. /// Additional Linq transformations can be appended. /// Use ExecuteQueryAsync to execute. /// <see cref=""https://github.com/Azure/azure-cosmos-dotnet-v3/blob/bb72ba5786d99d928b4774e16810f2655029e8a2/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs"" /> /// </summary> public virtual System.Linq.IQueryable<{documentPlan.FullTypeName}> {documentPlan.PluralName}() => this.BuildQueryByType<{documentPlan.FullTypeName}>(type: {documentPlan.ConstDocType}); ";
static string CreateOrReplace(PartitionPlan partitionPlan, DocumentPlan documentPlan) => !documentPlan.IsTransient && !documentPlan.IsMutable ? "" : $@" /// <summary> /// Create or replace (unconditionally overwrite) a {documentPlan.ClassName}. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<Cosmogenesis.Core.CreateOrReplaceResult<{documentPlan.FullTypeName}>> {documentPlan.ClassName}Async({documentPlan.PropertiesByName.Values.Where(x => !partitionPlan.GetPkPlan.ArgumentByPropertyName.ContainsKey(x.PropertyName)).AsInputParameters()}) => this.{partitionPlan.ClassName}.CreateOrReplaceAsync(new {documentPlan.FullTypeName} {{ {partitionPlan.AsSettersFromDocumentPlanAndPartitionClass(documentPlan)} }}); ";
static string Replace(DatabasePlan databasePlan, PartitionPlan partitionPlan, DocumentPlan documentPlan) => !documentPlan.IsMutable ? "" : $@" /// <summary> /// Queue a {documentPlan.ClassName} for replacement in the batch /// </summary> public virtual {databasePlan.Namespace}.{partitionPlan.BatchClassName} Replace({documentPlan.FullTypeName} {documentPlan.ClassNameArgument}) {{ this.ReplaceCore(item: {documentPlan.ClassNameArgument}, type: {documentPlan.ConstDocType}); return this; }} ";
static string DeleteIfTransient(DocumentPlan documentPlan) => !documentPlan.IsTransient ? "" : $@" /// <summary> /// Try to delete an existing {documentPlan.ClassName}. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<Cosmogenesis.Core.DbConflictType?> DeleteAsync({documentPlan.FullTypeName} {documentPlan.ClassNameArgument}) => this.DeleteItemAsync( item: {documentPlan.ClassNameArgument} ?? throw new System.ArgumentNullException(nameof({documentPlan.ClassNameArgument}))); ";
static string Delete(DatabasePlan databasePlan, PartitionPlan partitionPlan, DocumentPlan documentPlan) => !documentPlan.IsTransient ? "" : $@" /// <summary> /// Queue a {documentPlan.ClassName} for deletion in the batch /// </summary> public virtual {databasePlan.Namespace}.{partitionPlan.BatchClassName} Delete({documentPlan.FullTypeName} {documentPlan.ClassNameArgument}) {{ this.DeleteCore(item: {documentPlan.ClassNameArgument}); return this; }} ";
static string Read(DatabasePlan databasePlan, DocumentPlan documentPlan) => $@" /// <summary> /// Try to load a {documentPlan.ClassName} by pk & id. /// id should be transformed using DbDocHelper.GetValidId. /// Returns the {documentPlan.ClassName} or null if not found. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<{documentPlan.FullTypeName}?> {documentPlan.ClassName}ByIdAsync(string partitionKey, string id) => this.{databasePlan.DbClassName}.ReadByIdAsync<{documentPlan.FullTypeName}>( partitionKey: new Microsoft.Azure.Cosmos.PartitionKey(partitionKey), id: id, type: {documentPlan.ConstDocType}); ";
static string ReplaceIfMutable(DocumentPlan documentPlan) => !documentPlan.IsMutable ? "" : $@" /// <summary> /// Try to replace an existing {documentPlan.ClassName}. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual System.Threading.Tasks.Task<Cosmogenesis.Core.ReplaceResult<{documentPlan.FullTypeName}>> ReplaceAsync({documentPlan.FullTypeName} {documentPlan.ClassNameArgument}) => this.ReplaceItemAsync( item: {documentPlan.ClassNameArgument} ?? throw new System.ArgumentNullException(nameof({documentPlan.ClassNameArgument})), type: {documentPlan.ConstDocType}); ";
static string ReadOrCreate(PartitionPlan partitionPlan, DocumentPlan documentPlan) => $@" /// <summary> /// Read a {documentPlan.ClassName} document, or create it if it does not yet exist. /// .id must be set if there is no stable id generator defined /// .pk, .CreationDate and .Type are set automatically /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> internal protected virtual System.Threading.Tasks.Task<Cosmogenesis.Core.ReadOrCreateResult<{documentPlan.FullTypeName}>> ReadOrCreateAsync(bool tryCreateFirst, {documentPlan.FullTypeName} {documentPlan.ClassNameArgument}) {{ {DocumentModelWriter.CreateAndCheckPkAndId(partitionPlan, documentPlan, documentPlan.ClassNameArgument)} return this.ReadOrCreateItemAsync(item: {documentPlan.ClassNameArgument}, type: {documentPlan.ConstDocType}, tryCreateFirst: tryCreateFirst); }} ";
static string ReadById(DatabasePlan databasePlan, DocumentPlan documentPlan) => documentPlan.GetIdPlan.Arguments.Count == 0 ? "" : $@" /// <summary> /// Try to load {documentPlan.ClassName} documents by id. /// id should be transformed using Cosmogenesis.Core.DbDocHelper.GetValidId. /// Returns an array of {documentPlan.ClassName} documents (or null if not found) in the same order as the ids were provided. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> protected virtual System.Threading.Tasks.Task<{documentPlan.FullTypeName}?[]> {documentPlan.ClassName.Pluralize()}ByIdAsync(System.Collections.Generic.IEnumerable<string> ids) => this.{databasePlan.DbClassName}.ReadByIdsAsync<{documentPlan.FullTypeName}>( partitionKey: this.PartitionKey, ids: ids, type: {documentPlan.ConstDocType}); ";
static string Create(DatabasePlan databasePlan, PartitionPlan partitionPlan, DocumentPlan documentPlan) => $@" /// <summary> /// Queue a {documentPlan.ClassName} for creation in the batch /// </summary> protected virtual {databasePlan.Namespace}.{partitionPlan.BatchClassName} Create({documentPlan.FullTypeName} {documentPlan.ClassNameArgument}) {{ {DocumentModelWriter.CreateAndCheckPkAndId(partitionPlan, documentPlan, documentPlan.ClassNameArgument)} this.CreateCore(item: {documentPlan.ClassNameArgument}, type: {documentPlan.ConstDocType}); return this; }} /// <summary> /// Queue a {documentPlan.ClassName} for creation in the batch /// </summary> public virtual {databasePlan.Namespace}.{partitionPlan.BatchClassName} Create{documentPlan.ClassName}({documentPlan.PropertiesByName.Values.Where(x => !partitionPlan.GetPkPlan.ArgumentByPropertyName.ContainsKey(x.PropertyName)).AsInputParameters()}) => this.Create({documentPlan.ClassNameArgument}: new {documentPlan.FullTypeName} {{ {partitionPlan.AsSettersFromDocumentPlanAndPartitionClass(documentPlan)} }}); ";
static string CreateOrReplace(PartitionPlan partitionPlan, DocumentPlan documentPlan) => !documentPlan.IsMutable && !documentPlan.IsTransient ? "" : $@" /// <summary> /// Create or replace (unconditionally overwrite) a {documentPlan.ClassName}. /// .id must be set if there is no stable id generator defined /// .pk, .CreationDate and .Type are set automatically /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> internal protected virtual System.Threading.Tasks.Task<Cosmogenesis.Core.CreateOrReplaceResult<{documentPlan.FullTypeName}>> CreateOrReplaceAsync({documentPlan.FullTypeName} {documentPlan.ClassNameArgument}) {{ {DocumentModelWriter.CreateAndCheckPkAndId(partitionPlan, documentPlan, documentPlan.ClassNameArgument)} return this.CreateOrReplaceItemAsync(item: {documentPlan.ClassNameArgument}, type: {documentPlan.ConstDocType}); }} ";
public static void AddProperties(OutputModel outputModel, ClassModel classModel, DocumentPlan documentPlan) { outputModel.CancellationToken.ThrowIfCancellationRequested(); if (!outputModel.CanGenerate) { return; } foreach (var propertyModel in classModel.Properties) { var symbol = propertyModel.PropertySymbol; if (outputModel.CanIncludeProperty(symbol)) { var propertyPlan = new PropertyPlan { PropertyName = symbol.Name, ArgumentName = symbol.Name.ToArgumentName(), FullTypeName = symbol.Type.ToDisplayString(), UseDefault = propertyModel.UseDefaultAttribute is not null, IsInitOnly = propertyModel.IsInitOnly, PropertyModel = propertyModel }; outputModel.ValidateIdentifiers( symbol, propertyPlan.PropertyName, propertyPlan.ArgumentName); documentPlan.PropertiesByName[propertyPlan.PropertyName] = propertyPlan; if (documentPlan.PropertiesByArgumentName.ContainsKey(propertyPlan.ArgumentName)) { outputModel.Report(Diagnostics.Errors.PropertyArgumentCollision, symbol, propertyPlan.ArgumentName); } else { documentPlan.PropertiesByArgumentName[propertyPlan.ArgumentName] = propertyPlan; if (!propertyPlan.IsInitOnly && !documentPlan.IsMutable && SymbolEqualityComparer.Default.Equals(propertyPlan.PropertyModel.PropertySymbol.ContainingType, classModel.ClassSymbol)) { outputModel.Report(Diagnostics.Warnings.InitOnlyNotMutable, symbol, propertyPlan.PropertyName); } } } } } }
static string ReadOrThrow(DatabasePlan databasePlan, DocumentPlan documentPlan) => $@" /// <summary> /// Try to load a {documentPlan.ClassName} by id. /// id should be transformed using Cosmogenesis.Core.DbDocHelper.GetValidId. /// Returns the {documentPlan.ClassName} or throws DbConflictException if not found. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbConflictException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> protected virtual async System.Threading.Tasks.Task<{documentPlan.FullTypeName}> {documentPlan.ClassName}ByIdAsync(string id) {{ var result = await this.{databasePlan.DbClassName}.ReadByIdAsync<{documentPlan.FullTypeName}>( partitionKey: this.PartitionKey, id: id, type: {documentPlan.ConstDocType}).ConfigureAwait(false); if (result is null) {{ throw Cosmogenesis.Core.DbModelFactory.CreateDbConflictException(dbConflictType: Cosmogenesis.Core.DbConflictType.Missing); }} return result; }} /// <summary> /// Try to load a {documentPlan.ClassName} using an id generated by the parameters. /// Returns the {documentPlan.ClassName} or throws DbConflictException if not found. /// </summary> /// <exception cref=""Cosmogenesis.Core.DbOverloadedException"" /> /// <exception cref=""Cosmogenesis.Core.DbConflictException"" /> /// <exception cref=""Cosmogenesis.Core.DbUnknownStatusCodeException"" /> public virtual async System.Threading.Tasks.Task<{documentPlan.FullTypeName}> {documentPlan.ClassName}Async({documentPlan.GetIdPlan.AsInputParameters()}) {{ var result = await this.{databasePlan.DbClassName}.ReadByIdAsync<{documentPlan.FullTypeName}>( partitionKey: this.PartitionKey, id: Cosmogenesis.Core.DbDocHelper.GetValidId({documentPlan.GetIdPlan.FullMethodName}({documentPlan.GetIdPlan.AsInputParameterMapping()})), type: {documentPlan.ConstDocType}).ConfigureAwait(false); if (result is null) {{ throw Cosmogenesis.Core.DbModelFactory.CreateDbConflictException(dbConflictType: Cosmogenesis.Core.DbConflictType.Missing); }} return result; }} ";
static string Query(DatabasePlan databasePlan, PartitionPlan partitionPlan, DocumentPlan documentPlan) => $@" /// <summary> /// Build and execute a query filtered to {documentPlan.ClassName} documents. /// <see cref=""https://github.com/Azure/azure-cosmos-dotnet-v3/blob/bb72ba5786d99d928b4774e16810f2655029e8a2/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs"" /> /// </summary> public virtual System.Collections.Generic.IAsyncEnumerable<T> {documentPlan.ClassName.Pluralize()}<T>( System.Func<System.Linq.IQueryable<{documentPlan.FullTypeName}>, System.Linq.IQueryable<T>> createQuery, System.Threading.CancellationToken cancellationToken = default) => this.{databasePlan.DbClassName} .ExecuteQueryAsync( query: createQuery(this.{partitionPlan.QueryBuilderClassName}.{documentPlan.ClassName.Pluralize()}()), cancellationToken: cancellationToken); /// <summary> /// Execute a query filtered to {documentPlan.ClassName} documents. /// </summary> public virtual System.Collections.Generic.IAsyncEnumerable<{documentPlan.FullTypeName}> {documentPlan.ClassName.Pluralize()}( System.Threading.CancellationToken cancellationToken = default) => this.{databasePlan.DbClassName} .ExecuteQueryAsync( query: this.{partitionPlan.QueryBuilderClassName}.{documentPlan.ClassName.Pluralize()}(), cancellationToken: cancellationToken); ";
public static string CreateAndCheckPkAndId(PartitionPlan partitionPlan, DocumentPlan documentPlan, string paramTypeName) => $@" if ({paramTypeName} is null) {{ throw new System.ArgumentNullException(nameof({paramTypeName})); }} var calculatedPk = {partitionPlan.GetPkPlan.FullMethodName}({partitionPlan.GetPkPlan.DocumentToParametersMapping(paramTypeName)}); var calculatedId = Cosmogenesis.Core.DbDocHelper.GetValidId({documentPlan.GetIdPlan.FullMethodName}({documentPlan.GetIdPlan.DocumentToParametersMapping(paramTypeName)})); if ({paramTypeName}.id is null) {{ {paramTypeName}.id = calculatedId ?? throw new System.InvalidOperationException(""The generated document id cannot be null""); }} else if ({paramTypeName}.id != calculatedId) {{ throw new System.InvalidOperationException(""The document .id property does not match the calculated document id""); }} if ({paramTypeName}.pk is null) {{ {paramTypeName}.pk = calculatedPk ?? throw new System.InvalidOperationException(""The generated partition key cannot be null""); }} else if ({paramTypeName}.pk != calculatedPk) {{ throw new System.InvalidOperationException(""The document .pk property does not match the calculated document partition key""); }} ";
public static string ConstructorArg(DocumentPlan documentPlan) => @$ "
static string DeserializeType(DocumentPlan documentPlan) => $@" {documentPlan.ConstDocType} => System.Text.Json.JsonSerializer.Deserialize<{documentPlan.FullTypeName}>(utf8Json: data, options: options),";
static string Type(DocumentPlan documentPlan) => $@" public const string {documentPlan.ClassName} = ""{documentPlan.DocType}"";";
public static string AsSettersFromDocumentPlanAndPartitionClass(this PartitionPlan partitionPlan, DocumentPlan documentPlan) { var setters = documentPlan .PropertiesByName .Values .Where(x => !partitionPlan.GetPkPlan.ArgumentByPropertyName.ContainsKey(x.PropertyName)) .Select(x => $"{x.PropertyName} = {x.ArgumentName}"); var key = partitionPlan .GetPkPlan .Arguments .Select(x => $"{x.PropertyName} = this.{partitionPlan.ClassName}.PartitionKeyData.{x.PropertyName}"); return(setters.Concat(key).JoinNonEmpty()); }
static string CheckedCreateOrReplace(DocumentPlan documentPlan) => $@" {documentPlan.FullTypeName} x => this.CreateOrReplace({documentPlan.ClassNameArgument}: x),";
static string CheckedDelete(DocumentPlan documentPlan) => $@" {documentPlan.FullTypeName} x => this.Delete({documentPlan.ClassNameArgument}: x),";