Problematic code (Present in Core.run() below)
user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = word
}
);
Decription of problem
- Database doesn't exists
- Exec first time console program, code above in
user.UserWordRequestsis null. Expected value is empty list. - Database now exists because step 2 has created it.
- Exec console program again, now
user.UserWordRequestsis empty list (or filled with data, depends of data in db).
More data, here is user object in step 2:
And here in step 4:
Usefull code
Database models
public class User
{
public User()
{
}
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<UserWordRequest> UserWordRequests { get; set; }
}
public class Word
{
public Word()
{
}
[Key]
public int Id { get; set; }
[Required]
public string Value { get; set; }
public virtual ICollection<UserWordRequest> UserWordRequests { get; set; }
}
public class UserWordRequest
{
public UserWordRequest()
{
}
[Key]
public int Id { get; set; }
[Required]
public int UserId { get; set; }
public virtual User User { get; set; }
[Required]
public int WordId { get; set; }
public virtual Word Word { get; set; }
[Required]
public DateTime Date { get; set; }
}
Main program
class Program
{
private static async Task Main(string[] args)
{
var host =
Host.CreateDefaultBuilder(args)
.ConfigureLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders(); // Disable console messages
})
.ConfigureServices((hostContext, services) =>
{
// Auto mapping config !!!
var config = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json")
.Build();
var settings = config/*.GetSection("GeneralSection")*/.Get<Config>();
// AddHostedService
services
.AddHostedService<ConsoleHostedService>()
.AddDbContext<DataBaseContext>
(
options =>
options
.UseLazyLoadingProxies(true)
.UseSqlite(
settings.General.DataBaseConnection
)
,
ServiceLifetime.Singleton,
ServiceLifetime.Singleton
)
//.AddEntityFrameworkProxies()
.AddSingleton((Config) => { return settings; })
.AddSingleton<Logger>()
.AddSingleton<Util.File>()
.AddSingleton<Core>()
.AddSingleton<DataBaseContext>()
;
}).Build();
host.Services.GetService<DataBaseContext>().Database.Migrate();
// Then run application
host.Run();
}
}
Console hosted service class
public class ConsoleHostedService : IHostedService
{
private readonly IHostApplicationLifetime _appLifetime;
private readonly Core _Core;
private readonly Logger _Logger;
private readonly DataBaseContext _db;
public ConsoleHostedService(
IHostApplicationLifetime appLifetime,
Core Core,
Logger Logger,
DataBaseContext DataBaseContext)
{
_appLifetime = appLifetime;
_Core = Core;
_Logger = Logger;
_db = DataBaseContext;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_appLifetime.ApplicationStarted.Register(() =>
{
Task.Run(async () =>
{
try
{
// Start program
//_db.Database.Migrate();
_Core.run();
}
catch (Exception e)
{
_Logger.Log(Logger.LogType.Error, "", e);
}
finally
{
// Stop the application once the work is done
_appLifetime.StopApplication();
}
});
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
DatabaseContext
public class DataBaseContext : DbContext
{
public DataBaseContext(DbContextOptions<DataBaseContext> options) : base(options)
{
}
public virtual DbSet<Word> Words { get; set; }
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Log> Logs { get; set; }
public virtual DbSet<UserWordRequest> UserWordRequests { get; set; }
}
/// <summary>
/// ❗❗❗ We need this class only for doing add-migration, but this is not used by anyone
/// </summary>
public class DataBaseContextFactory : IDesignTimeDbContextFactory<DataBaseContext>
{
public DataBaseContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<DataBaseContext>();
optionsBuilder.UseSqlite("Data Source=this_name_is_not_used.db");
return new DataBaseContext(optionsBuilder.Options);
}
}
Core class ❗❗❗ PROBLEMATIC CODE
public Core(DataBaseContext DataBaseContext, Logger Logger, File File, Config Config)
{
_db = DataBaseContext;
_Logger = Logger;
_File = File;
_Config = Config;
_Random = new Random();
_Logger.Log(Logger.LogType.Info, "Core");
}
public void run()
{
try
{
... some code
var word = _db.Words.FirsOrDefault();
var user = _db.Users.FirsOrDefault();
// ‼‼‼ PROBLEMATIC CODE
user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = word
}
);
_db.SaveChanges();
... some code
}
catch(Exception e)
{
_Logger.Log(Logger.LogType.Error, "", e);
}
}
Versions used
net6.0 and:
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
CodePudding user response:
Summary, leason learned
Use create or createProxy instead new for avoid errors.
Error explained
First time console app is executed, when database is created, code created users with new instead Create or CreateProxy:
var user = new User();
user.prop = "value";
...
db.Users.Add(user)
Later, inside first time console app execution, application executes this code and UserWordRequests is null instead empty list.
var user = db.Users.Where(...).FirstOrDefault();
user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = ...
}
);
The solution comes using Create or CreateProxy for user creations.
var proxy = _db.Users.CreateProxy();
proxy.Name = user.Name;
_db.Users.Add(proxy);
Then, UserWordRequests is empty list, not null.
But... why second time this does not happen? Because users are created in database and the code did not execute the inserts, it obtained them with a database query.


