UseLazyLoadingProxies codefirst null navigation 属性 仅在数据库创建后
UseLazyLoadingProxies codefirst null navigation property only after database creation
有问题的代码(出现在下面的 Core.run() 中)
user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = word
}
);
问题描述
- 数据库不存在
- 执行第一次控制台程序,上面
user.UserWordRequests
中的代码为空。 预期值为空列表。
- 数据库现在存在,因为步骤 2 已创建它。
- 再次执行控制台程序,现在
user.UserWordRequests
是空列表(或填充了数据,取决于数据库中的数据)。
更多数据,这里是第 2 步中的用户对象:
在第 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();
}
}
控制台托管服务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;
}
}
数据库上下文
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);
}
}
核心class❗❗❗问题代码
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 和:
<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" />
总结,学习了
使用 or 代替 new
以避免错误。
错误解释
第一次执行控制台应用程序时,创建数据库时,代码使用 new
而不是 Create
或 CreateProxy
:
创建用户
var user = new User();
user.prop = "value";
...
db.Users.Add(user)
稍后,在第一次控制台应用程序执行中,应用程序执行此代码并且 UserWordRequests
为 null 而不是空列表。
var user = db.Users.Where(...).FirstOrDefault();
user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = ...
}
);
解决方案使用 Create
或 CreateProxy
来创建用户。
var proxy = _db.Users.CreateProxy();
proxy.Name = user.Name;
_db.Users.Add(proxy);
那么,UserWordRequests
是空列表,不是null。
但是...为什么第二次没有发生这种情况?因为用户是在数据库中创建的,代码没有执行插入,所以它通过数据库查询获得它们。
有问题的代码(出现在下面的 Core.run() 中)
user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = word
}
);
问题描述
- 数据库不存在
- 执行第一次控制台程序,上面
user.UserWordRequests
中的代码为空。 预期值为空列表。 - 数据库现在存在,因为步骤 2 已创建它。
- 再次执行控制台程序,现在
user.UserWordRequests
是空列表(或填充了数据,取决于数据库中的数据)。
更多数据,这里是第 2 步中的用户对象:
在第 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();
}
}
控制台托管服务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;
}
}
数据库上下文
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);
}
}
核心class❗❗❗问题代码
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 和:
<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" />
总结,学习了
使用 new
以避免错误。
错误解释
第一次执行控制台应用程序时,创建数据库时,代码使用 new
而不是 Create
或 CreateProxy
:
var user = new User();
user.prop = "value";
...
db.Users.Add(user)
稍后,在第一次控制台应用程序执行中,应用程序执行此代码并且 UserWordRequests
为 null 而不是空列表。
var user = db.Users.Where(...).FirstOrDefault();
user.UserWordRequests.Add(
new UserWordRequest
{
Date = DateTime.Now,
Word = ...
}
);
解决方案使用 Create
或 CreateProxy
来创建用户。
var proxy = _db.Users.CreateProxy();
proxy.Name = user.Name;
_db.Users.Add(proxy);
那么,UserWordRequests
是空列表,不是null。
但是...为什么第二次没有发生这种情况?因为用户是在数据库中创建的,代码没有执行插入,所以它通过数据库查询获得它们。