user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = word
}
);
user.UserWordRequests
is null. Expected value is empty list.user.UserWordRequests
is 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:
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; }
}
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();
}
}
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;
}
}
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);
}
}
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);
}
}
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" />
Use create or createProxy instead new
for avoid errors.
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.