简体   繁体   English

UseLazyLoadingProxies codefirst null 导航属性仅在数据库创建后

[英]UseLazyLoadingProxies codefirst null navigation property only after database creation

Problematic code (Present in Core.run() below)有问题的代码(出现在下面的 Core.run() 中)

user.UserWordRequests.Add(
    new UserWordRequest
    {
        Date = DateTime.Now,
        Word = word
    }
);

Decription of problem问题描述

  1. Database doesn't exists数据库不存在
  2. Exec first time console program, code above in user.UserWordRequests is null.执行第一次控制台程序,上面user.UserWordRequests中的代码是null。 Expected value is empty list .预期值为空列表
  3. Database now exists because step 2 has created it.数据库现在存在,因为步骤 2 已创建它。
  4. Exec console program again, now user.UserWordRequests is empty list (or filled with data, depends of data in db).再次执行控制台程序,现在user.UserWordRequests是空列表(或填充数据,取决于数据库中的数据)。

More data, here is user object in step 2:更多数据,这里是步骤 2 中的用户 object:

在此处输入图像描述

And here in step 4:在第 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控制台托管服务 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核心 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);
    }
    
}

Versions used使用的版本

net6.0 and: 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" />

Summary, leason learned总结,经验教训

Use create or createProxy instead new for avoid errors.使用createcreateProxy而不是new以避免错误。

Error explained错误解释

First time console app is executed, when database is created, code created users with new instead Create or CreateProxy :第一次执行控制台应用程序时,创建数据库时,代码使用new而不是CreateCreateProxy创建用户:

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.后来,在第一次控制台应用程序执行中,应用程序执行此代码并且UserWordRequests是 null 而不是空列表。

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.该解决方案使用CreateCreateProxy来创建用户。

var proxy = _db.Users.CreateProxy();
proxy.Name = user.Name;
_db.Users.Add(proxy);

Then, UserWordRequests is empty list, not null.然后, UserWordRequests是空列表,而不是 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.因为用户是在数据库中创建的,并且代码没有执行插入,所以它通过数据库查询来获取它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM