I have a DbContext set up to use Cosmos DB as follows:
public class AuthenticationCosmosDataContext : DbContext, IUnitOfWork
{
private readonly CosmosOptions cosmosOptions;
public DbSet<Settings> Settings { get; set; }
public AuthenticationCosmosDataContext(DbContextOptions<AuthenticationCosmosDataContext> options, IOptions<CosmosOptions> cosmosOptions) : base(options)
{
this.cosmosOptions = cosmosOptions.Value;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultContainer(cosmosOptions.DefaultContainer);
modelBuilder.Entity<Settings>().ToContainer(cosmosOptions.DefaultContainer);
modelBuilder.ApplyConfiguration(new SettingsConfiguration());
}
Which is set up from Startup.cs like this:
services.AddDbContext<AuthenticationCosmosDataContext>((serviceProvider, options) =>
{
var cosmosOptions = serviceProvider.GetRequiredService<IOptions<CosmosOptions>>().Value;
options.UseCosmos(cosmosOptions.AccountEndpoint, cosmosOptions.AccountKey, cosmosOptions.DatabaseName);
Now, the SettingsConfiguration doesn't have much in it:
builder.HasKey(x => x.Id).HasName("id");
builder.HasPartitionKey(x => x.Id);
And the Settings look like this:
public class Settings
{
public string Id { get; set; }
public int SomeId { get; private set; }
public string Type { get; private set; }
public int AnotherId { get; private set; }
I've tried querying data from the database, buy everything returns either null or 0 results:
await cosmosContext.Settings.FirstOrDefaultAsync().ConfigureAwait(false); // returns null
await cosmosContext.Settings.CountAsync().ConfigureAwait(false); // returns 0
The connection seems to be fine as there are no exceptions and I did have to make some adjustments in my model because there was some mismatch with the documents.
What could be going wrong?
UPDATE: It appears the HasName() method in the configuration is not working as expected. If I rename the field from "Id" to "id" it works as expected. Looking for a way to properly set the configuration
You should set the key using builder.Property(p => p.Id).ToJsonProperty("id");.
Not sure if it helps, but dont you have to overwrite the OnConfiguring Method?:
public class OptionsContext : DbContext
{
#region Configuration
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseCosmos(
"AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
databaseName: "OptionsDB",
options =>
{
options.ConnectionMode(ConnectionMode.Gateway);
options.WebProxy(new WebProxy());
options.LimitToEndpoint();
options.Region(Regions.AustraliaCentral);
options.GatewayModeMaxConnectionLimit(32);
options.MaxRequestsPerTcpConnection(8);
options.MaxTcpConnectionsPerEndpoint(16);
options.IdleTcpConnectionTimeout(TimeSpan.FromMinutes(1));
options.OpenTcpConnectionTimeout(TimeSpan.FromMinutes(1));
options.RequestTimeout(TimeSpan.FromMinutes(1));
});
#endregion
}
Also: Can you create elements in the Db from code?
Related
We have an application in which we have separated read model and write model.
write model is a DDD approach with rich domain models and read model is just a class library in which we have scaffolded the database. its an anemic domain model just for read purposes.
this is write model DbContext:
public class AppraisalDbContext : DbContext
{
public AppraisalDbContext(DbContextOptions<AppraisalDbContext> options) : base(options)
{
}
public DbSet<Appraisal> Appraisals { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppraisalMapping).Assembly);
}
}
this is Appraisal in write model(domain model):
public class Appraisal : BaseEntity, IAggregateRoot
{
protected Appraisal()
{
}
public Appraisal(string title)
{
SetTitle(title);
}
.
.
.
}
and this is read model DbContext:
public partial class Appraisal360DbContext : DbContext
{
public Appraisal360DbContext()
{
}
public Appraisal360DbContext(DbContextOptions<Appraisal360DbContext> options)
: base(options)
{
}
public virtual DbSet<Appraisal> Appraisals { get; set; } = null!;
}
and this is read model Appraisal:
public partial class Appraisal
{
public string Title { get; set; } = null!;
}
As you can see we have 2 different DbContext with different models.
Now I have created a custom class which is derived from WebApplicationFactory:
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
private static readonly InMemoryDatabaseRoot InMemoryDatabaseRoot = new InMemoryDatabaseRoot();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(configurationBuilder =>
{
var integrationConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
configurationBuilder.AddConfiguration(integrationConfig);
});
builder.ConfigureServices((builder, services) =>
{
services
.Remove<DbContextOptions<Appraisal360DbContext>>()
.AddDbContext<Appraisal360DbContext>((sp, options) =>
options.UseInMemoryDatabase("AppraisalDb",InMemoryDatabaseRoot));
services
.Remove<DbContextOptions<AppraisalDbContext>>()
.AddDbContext<AppraisalDbContext>((sp, options) =>
options.UseInMemoryDatabase("AppraisalDb", InMemoryDatabaseRoot));
});
builder.UseEnvironment("Test");
}
}
We expect both DbContexts to refer to one in-memory database. but when we create an Appraisal using AppraisalDbContext(write model) the data is not persisted in Appraisal360DbContext(read model) and vice versa.
I tried to use Sqlite but since I have a column with BIN2_COLLATE, Sqlite doesn't support this option. I have also configured Always Encrypted and that is another reason why I can't use Sqlite in-memory database.
I appriciate any help. any suggestion. any recommendation.
Thank you for your time.
I want to accomplish clean architecture with EF Core.
I trid to separate primary key from base class, but it failed with below exception.
System.InvalidOperationException: 'A key cannot be configured on 'ManagerEntity' because it is a derived type. The key must be configured on the root type 'Manager'. If you did not intend for 'Manager' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.'
Core
public class Manager
{
public Manager(Guid identifier, string email)
{
Identifier = identifier;
Email = email;
}
public Guid Identifier { get; }
public string Email { get; }
public void FixPrinter(Printer printer)
{
printer.IsOutOfControl = true;
}
}
public class Printer
{
public Printer(Guid token)
{
Token = token;
Manager = null;
IsOutOfControl = false;
}
public Guid Token { get; }
public Manager? Manager { get; set; }
public bool IsOutOfControl { get; set; }
}
Infrastructure
public class ApplicationContext
: DbContext
{
// ...
public DbSet<ManagerEntity> ManagerSet { get; set; }
public DbSet<PrinterEntity> PrinterSet { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new ManagerEntityConfiguration(Database));
modelBuilder.ApplyConfiguration(new PrinterEntityConfiguration(Database));
}
}
Configure Manager
public sealed class ManagerEntity
: Manager
{
public ManagerEntity(string email)
: base(Guid.Empty, email)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class ManagerEntityConfiguration
: IEntityTypeConfiguration<ManagerEntity>
{
private readonly DatabaseFacade _database;
public ManagerEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<ManagerEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
// The exception occurs here.
builder
.HasKey(e => e.Id);
// ...
}
}
Configure Printer
public sealed class PrinterEntity
: Printer
{
public PrinterEntity()
: base(Guid.Empty)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class PrinterEntityConfiguration
: IEntityTypeConfiguration<PrinterEntity>
{
private readonly DatabaseFacade _database;
public PrinterEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<PrinterEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
builder
.HasKey(e => e.Id);
// ...
}
}
Web API
app.MapPost("/printer", async (ApplicationContext context) =>
{
PrinterEntity printer = new()
{
Manager = new ManagerEntity("master#google.com"),
};
await context.PrinterSet.AddAsync(printer);
await context.SaveChangesAsync();
return printer;
});
Should I architect it by using interface, not inheritance?
At Core,
public interface IPrinter
{
public Manager? Manager { get; set; }
}
At Infrastructure,
public sealed class PrinterEntity : IPrinter
{
// ...
}
Github source code
Thanks, #roji!
https://github.com/dotnet/efcore/issues/27421#issuecomment-1034762908
This is likely happening because you're mapping both Manager and ManagerEntity in your model, which means you're configuring inheritance mapping (i.e. EF thinks you intend to store both Manager and ManagerEntity instances in the database). With inheritance mapping, the key must be specified at the root.
However, it seems like you only want the class separation on the .NET side, without needing any actual hierarchy, so make sure you are not mapping the base class (Manager in the above). See the minimal code sample below.
await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
public class BlogContext : DbContext
{
// Uncomment the below to make the exception appear
// public DbSet<Manager> Managers { get; set; }
public DbSet<ManagerEntity> ManagerEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(#"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ManagerEntity>().HasKey(b => b.Id);
}
}
public class Manager
{
public string Name { get; set; }
}
public class ManagerEntity : Manager
{
public int Id { get; set; }
}
However... if the goal is simply not to expose an Id property on Manager, there are simpler ways to do that rather than introducing a .NET hierarchy. You can have a private _id field instead, which would be used by EF Core but not otherwise exposed in your application, keeping your data model clean (see docs). Alternatively, you can have an Id shadow property, removing the field/property from your CLR type altogether.
I'm trying to create a migration in my web api for one-to-many db. Services are configured this way and the connection string is successfully received from launchsettings.json
var connectionStr = Environment.GetEnvironmentVariable("ApplicationDbContext");
services.AddDbContext<InsolationResultContext>(options =>
{
options.UseNpgsql(connectionStr, builder =>
{
builder.CommandTimeout(300);
builder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), null);
});
});
This are models
public class DocumentInsolationResult
{
public string Id { get; set; }
public string UserId { get; set; }
public IEnumerable<InsolationResult> Elements { get; set; }
}
public class InsolationResult
{
public string UniqueId { get; set; }
public string Insolation { get; set; }
}
And DbContext
public class InsolationResultContext : DbContext
{
public DbSet<DocumentInsolationResult> DocumentInsolationResults { get; set; }
public DbSet<InsolationResult> InsolationResults { get; set; }
public InsolationResultContext(DbContextOptions<InsolationResultContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DocumentInsolationResult>()
.HasMany(p => p.Elements)
.WithOne();
}
}
the connection string looks like this:
"environmentVariables": {
"ApplicationDbContext": "Host=192.168.1.***;Port=****;Database=***.******;Username=*****;Password=****",
"ASPNETCORE_ENVIRONMENT": "Development"
},
When trying to "Add-Migartion Init i always get "Value cannot be null. (Paramtere 'connectionString'). What am i doing wrong? Coulnd't actually find the answer on the internet
upd: i'me receiving the connection string from launchsetting, it's ok as i'm using the same way of getting connectionString on some other projects
upd2 hardcoding the connection string worked for me
If you use IConfiguration instead of Environment.GetEnvironmentVariable then it becomes a lot easier:
var connString = config.GetValue<string>("ApplicationDbContext");
It should be
"ConnectionStrings": {
"ApplicationDbContext": "Host=192.168.1.***;Port=****;Database=***.******;Username=*****;Password=****""
}
in appsettings.Development.json file.
Thus it is giving error "Value cannot be null. (Paramtere 'connectionString')."
And in your startup.cs file
services.AddDbContext<InsolationResultContext >(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbContext")
});
Well the problem was really with the connection string. I couldn't create a migration when using connection string from appsettings or launchsettings. After hardcoding the connection string in stratup.cs i was able to create migration. And now after the db is initialized i'm using connection string from launchsettings. This might be some EF core tricky things.
Error:
Cannot create a DbSet for 'OpenIddictEntityFrameworkCoreApplication' because this type is not included in the model for the context.
I found this solution.
However, this solution does not work for .NET 5.0?
Missing reference to OpenIddictApplication" , OpenIddictAuthorization , OpenIddictScope, OpenIddictToken?
OpenIddict.AspNetCore" Version="3.0.0-beta6.20527.75
OpenIddict.EntityFrameworkCore" Version="3.0.0-beta6.20527.75
public class ApiHubContext : DbContext
{
public ApiHubContext(DbContextOptions options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.UseOpenIddict<ApplicationClient, ApplicationAuthorization, ApplicationScope, ApplicationToken, long>();
}
}
public class ApplicationClient : OpenIddictApplication<long, ApplicationAuthorization, ApplicationToken>
{
public bool IsActive { get; set; }
public string Remarks { get; set; }
}
public class ApplicationAuthorization : OpenIddictAuthorization<long, ApplicationClient, ApplicationToken> { }
public class ApplicationScope : OpenIddictScope<long> { }
public class ApplicationToken : OpenIddictToken<long, ApplicationClient, ApplicationAuthorization> { }
These entities have been renamed in 3.0. You can find the complete list here: https://github.com/openiddict/openiddict-core/tree/dev/src/OpenIddict.EntityFrameworkCore.Models
In EF Core 2.0, we have the ability to derive from IEntityTypeConfiguration for cleaner Fluent API mappings (source).
How can I extend this pattern to utilize a base entity? In the example below, how can I have a BaseEntityConfiguration to reduce duplication in LanguageConfiguration and MaintainerConfiguration, modifying properties that are in the BaseEntity only in the BaseEntityConfiguration? What would such a BaseEntityConfiguration look like; and how would it be used, if at all, in OnModelCreating()? See the TODOs in-code near the end of the example.
Example:
public abstract class BaseEntity
{
public long Id { get; set; }
public DateTime CreatedDateUtc { get; set; }
public DateTime? ModifiedDateUtc { get; set; }
}
public class Language : BaseEntity
{
public string Iso6392 { get; set; }
public string LocalName { get; set; }
public string Name { get; set; }
}
public class Maintainer : BaseEntity
{
public string Email { get; set; }
public string Name { get; set; }
}
public class FilterListsDbContext : DbContext
{
public FilterListsDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Language> Languages { get; set; }
public DbSet<Maintainer> Maintainers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//TODO: Possibly add something like BaseEntityConfiguration?
modelBuilder.ApplyConfiguration(new LanguageConfiguration());
modelBuilder.ApplyConfiguration(new MaintainerConfiguration());
}
}
public class LanguageConfiguration : IEntityTypeConfiguration<Language>
{
public void Configure(EntityTypeBuilder<Language> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
public class MaintainerConfiguration : IEntityTypeConfiguration<Maintainer>
{
public void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
Something like this could work (untested)?
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
}
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
base.Configure(entityTypeBuilder);
}
}
There is another way to solve the problem, and that is to use Template Method Design Pattern. Like this:
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
ConfigureOtherProperties(builder);
}
public abstract void ConfigureOtherProperties(EntityTypeBuilder<TEntity> builder);
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void ConfigureOtherProperties(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
With this way you don't need to write any single line in child configuration.
Another approach if you dont want to repeat the column Definitions for all of your Models that inherit from the same base Entity like this:
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Order>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
modelBuilder.Entity<Adress>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
// …
}
is to find all the Entites that inhert from the base Entity, loop over them and call the generic Method as shown below, in which the redundant Logic is placed:
protected override void OnModelCreating(ModelBuilder modelBuilder){
foreach (Type type in GetEntityTypes(typeof(BaseEntity))){
var method = SetGlobalQueryMethod.MakeGenericMethod(type);
method.Invoke(this, new object[] { modelBuilder });
}
}
static readonly MethodInfo SetGlobalQueryMethod = typeof(/*your*/Context)
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Single(t => t.IsGenericMethod && t.Name == "SetGlobalQuery");
public void SetGlobalQuery<T>(ModelBuilder builder) where T : BaseEntity{
builder.Entity<T>().Property(o => o.CreatedDateTime).HasDefaultValueSql("CURRENT_TIMESTAMP");
// Additional Statements
}
For the "GetEntityTypes" Method you need the Nuget Package „Microsoft.Extensions.DependencyModel“
private static IList<Type> _entityTypeCache;
private static IList<Type> GetEntityTypes(Type type)
{
if (_entityTypeCache != null && _entityTypeCache.First().BaseType == type)
{
return _entityTypeCache.ToList();
}
_entityTypeCache = (from a in GetReferencingAssemblies()
from t in a.DefinedTypes
where t.BaseType == type
select t.AsType()).ToList();
return _entityTypeCache;
}
private static IEnumerable<Assembly> GetReferencingAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
try
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
catch (FileNotFoundException)
{ }
}
return assemblies;
}
Its a bit hacky in my opinion, but works fine for me!
The source with more details:
https://www.codingame.com/playgrounds/5514/multi-tenant-asp-net-core-4---applying-tenant-rules-to-all-enitites
I'm late to the party, but this is what I did in the OnModelCreating method to achieve similar results.
Basically, I have (4) properties that inherit from a BaseEntity. Two of those are dates why two are strings.
For the dates, I wanted the default to be SQL's GETUTCDATE and the string to be "SystemGenerated." Using a static helper that allows me to retrieve the property name from BaseEntity in a strongly-typed manner, I grab the (4) property names. Then, I iterate over all of the iterate over all of the ModelBuilder entities after my primary mappings are set-up. This allows modelBuilder.Model.GetEntityTypes to return the entities that the modelBuidler is aware of. Then it's a matter of looking at the ClrType.BaseType to see if the type inherits from my BaseEntity and setting the defaults on the PropertyBuilder.
I tested this directly and through EF Migrations which confirmed that the proper SQL was generated.
var createdAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedAtUtc);
var lastModifiedAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedAtUtc);
var createdBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedBy);
var lastModifiedBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedBy);
foreach (var t in modelBuilder.Model.GetEntityTypes())
{
if (t.ClrType.BaseType == typeof(BaseEntity))
{
modelBuilder.Entity(t.ClrType).Property(createdAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(lastModifiedAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(createdBy).HasDefaultValueSql("SystemGenerated");
modelBuilder.Entity(t.ClrType).Property(lastModifiedBy).HasDefaultValueSql("SystemGenerated");
}
}
Here is the the static helper for getting property names for a given type..
public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
if (expression.Body is MemberExpression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
else
{
var op = ((UnaryExpression)expression.Body).Operand;
return ((MemberExpression)op).Member.Name;
}
}