拥有类型的延迟加载 不使用自有类型的选项按推荐顺序覆盖InternalDbSet方法提供一个using System; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Exten

如何解决拥有类型的延迟加载 不使用自有类型的选项按推荐顺序覆盖InternalDbSet方法提供一个using System; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Exten

我正在朝着使用实体框架核心的域驱动设计迈出第一步。我有一个User实体,在简化版本中,它只有IdProfilePhoto。但是,我想将个人资料照片存储在另一个表中,这就是为什么我创建了一个包含该个人资料照片并以这种方式配置的拥有类型的原因:

用户:

public class User
{
    private int id;
    public int Id => this.id;

    //private UserProfilePhoto userProfilePhoto;
    public virtual UserProfilePhoto UserProfilePhoto { get; set; }

    private User()
    {
    }

    public static User Create(byte[] profilePhoto)
    {
        var user = new User();
        user.UserProfilePhoto = new UserProfilePhoto(profilePhoto);

        return user;
    }

    public void SetProfilePhoto(byte[] profilePhoto)
    {
        this.UserProfilePhoto = new UserProfilePhoto(profilePhoto);
    }
}

UserProfilePhoto:

public class UserProfilePhoto
{
    public byte[] ProfilePhoto { get; private set; }

    public UserProfilePhoto(byte[] profilePhoto)
    {
        this.ProfilePhoto = profilePhoto;
    }
}

DbContext配置:

public class ModelContext : DbContext
{
    public ModelContext(DbContextOptions<ModelContext> options) : base(options)
    {
    }

    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        OnUserModelCreating(modelBuilder);
    }

    protected void OnUserModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .HasKey(u => u.Id);

        modelBuilder.Entity<User>()
            .Property(u => u.Id)
            .HasField("id");

        modelBuilder.Entity<User>()
            .OwnsOne(u => u.UserProfilePhoto,builder =>
            {
                builder.ToTable("UserProfilePhoto");

                builder.Property(u => u.ProfilePhoto)
                    .IsRequired();
            });
    }
}

我选择使用“拥有的”类型,因为我希望个人资料照片只能从用户实体访问。通过一对一映射,例如,我仍然可以使用UserProfilePhoto访问context.Set<UserProfilePhoto>()表,并且,据我了解的DDD聚合,这可能意味着跳过User业务逻辑。 因此,我进行了迁移,数据库模型就像我期望的那样:UserProfilePhoto表,其主键和外键为User.Id

很明显,在我的查询中,我不想每次都加载整个User实体,因此我未成功启用延迟加载。这是我在单元测试中尝试过的代码:

protected ModelContext GetModelContext(DbContextOptionsBuilder<ModelContext> builder)
{
    builder
        .UseLoggerFactory(loggerFactory)
        .UseLazyLoadingProxies()
        .EnableDetailedErrors();

    var ctx = new ModelContext(builder.Options);
    ctx.Database.EnsureCreated();

    return ctx;
}

[TestMethod]
public async Task TestMethod1()
{
    var builder = new DbContextOptionsBuilder<ModelContext>()
        .UseSqlServer(...);
    var ctx = this.GetModelContext(builder);
    var user = User.Create(new byte[] { });

    try
    {
        await ctx.Users.AddAsync(user);
        await ctx.SaveChangesAsync();

        var users = ctx.Users;

        foreach (var u in users)
        {
            Console.WriteLine(u.Id);
        }
    }
    finally
    {
        ctx.Users.Remove(user);
        await ctx.SaveChangesAsync();
        ctx.Database.EnsureDeleted();
    }
}

这是生成的SQL:

SELECT [u].[Id],[u0].[UserId],[u0].[ProfilePhoto]
FROM [Users] AS [u]
LEFT JOIN [UserProfilePhoto] AS [u0] ON [u].[Id] = [u0].[UserId]

我不确定它是否有效,但是注入ILazyLoader并不是我的解决方案,另一方面,这就像弄脏模型一样。

我的疑问是,拥有的类型不会通过实际的导航属性绑定到主体实体,因此不支持为它们创建代理。

我的方法有什么问题?是DDD吗?如果是这样,我该如何懒惰地加载拥有的实体?

我发现与此相关的issue on Github,尽管它不能回答我的问题。


修改

我的目标是阻止从EF api访问UserProfilePhoto表(请参阅注释)。如果我能够做到这一点,那么保护我的UserProfilePhoto类并将其封装在User类中将很容易,就像这样:

User

...
protected virtual UserProfilePhoto UserProfilePhoto { get; set; }

public void SetProfilePhoto(byte[] profilePhoto)
{
    this.UserProfilePhoto.SetProfilePhoto(profilePhoto);
}

public byte[] GetProfilePhoto()
{
    return this.UserProfilePhoto.ProfilePhoto;
}
...

我尝试了一对一映射的代码,即使在延迟加载的情况下也可以完美地工作。我该如何仅使用拥有类型?还有其他方法吗?

解决方法

当所有者被加载时(从Owned Entity Types: Querying owned types),EF Core会自动加载拥有的类型

在查询所有者时,默认情况下将包括拥有的类型。即使拥有的类型存储在单独的表中,也不必使用Include方法。

因此,使用拥有的类型不能满足仅按需加载的要求。

(您可以修改//frameOutputNode = graph.CreateFrameOutputNode(); //mediaInput.AddOutgoingConnection(frameOutputNode); //frameOutputNode.OutgoingGain = 4; 等,但这在很大程度上不受支持,不太可能在所有情况下都有效,并且可能随时中断。)

不使用自有类型的选项(按推荐顺序)

  • 覆盖Metadata.PrincipalToDependent.SetIsEagerLoaded(false)DbContext.Set<>()等,如果调用不当则抛出
  • 实施传统的自定义工作单元和存储库模式,该模式可让您完全控制所公开的API(交易具有控制灵活性)
  • 尽早在查询管道中添加一个表达式访问者(注册DbContext.Find()并从IQueryTranslationPreprocessorFactory派生),如果在查询中的任何地方都使用了RelationalQueryTranslationPreprocessorFactory,则会抛出该表达式访问者
  • 提供自己的DbSet<UserProfilePhoto>(和IDbSetSource)实现(内部),如果调用不当则抛出

覆盖InternalDbSet方法

通常,仅覆盖DbContextDbContext.Set<>()等应该是最简单的解决方案。您可以使用自定义属性修饰您不想直接查询的类型,然后只需检查DbContext.Find()等是否尚未使用该自定义属性修饰即可。

为了便于维护,可以将所有重写的方法移至基类,该类也可以执行运行时检查以确保所有有问题的方法都被重写(当然,这些检查也可以通过单元测试来完成) )。

以下是演示此方法的示例:

TEntity

提供一个using System; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace IssueConsoleTemplate { [AttributeUsage(AttributeTargets.Class)] public sealed class DontRootQueryMeAttribute : Attribute { } public class User { public int Id { get; private set; } public virtual UserProfilePhoto UserProfilePhoto { get; set; } public static User Create(byte[] profilePhoto) { var user = new User { UserProfilePhoto = new UserProfilePhoto(profilePhoto) }; return user; } } [DontRootQueryMeAttribute] public class UserProfilePhoto { public int Id { get; set; } public byte[] ProfilePhoto { get; private set; } public UserProfilePhoto(byte[] profilePhoto) { ProfilePhoto = profilePhoto; } } public abstract class ModelContextBase : DbContext { private static readonly string[] OverriddenMethodNames = { nameof(DbContext.Set),nameof(DbContext.Query),nameof(DbContext.Find),nameof(DbContext.FindAsync),}; static ModelContextBase() { var type = typeof(ModelContextBase); var overriddenMethods = type .GetRuntimeMethods() .Where( m => m.IsPublic && !m.IsStatic && OverriddenMethodNames.Contains(m.Name) && m.GetRuntimeBaseDefinition() != null) .Select(m => m.GetRuntimeBaseDefinition()) .ToArray(); var missingOverrides = type.BaseType .GetRuntimeMethods() .Where( m => m.IsPublic && !m.IsStatic && OverriddenMethodNames.Contains(m.Name) && !overriddenMethods.Contains(m)) .ToArray(); if (missingOverrides.Length > 0) { throw new InvalidOperationException( $"The '{nameof(ModelContextBase)}' class is missing overrides for {string.Join(",",missingOverrides.Select(m => m.Name))}."); } } private void EnsureRootQueryAllowed<TEntity>() => EnsureRootQueryAllowed(typeof(TEntity)); private void EnsureRootQueryAllowed(Type type) { var rootQueriesAllowed = type.GetCustomAttribute(typeof(DontRootQueryMeAttribute)) == null; if (!rootQueriesAllowed) throw new InvalidOperationException($"Directly querying for '{type.Name}' is prohibited."); } public override DbSet<TEntity> Set<TEntity>() { EnsureRootQueryAllowed<TEntity>(); return base.Set<TEntity>(); } public override DbQuery<TQuery> Query<TQuery>() { EnsureRootQueryAllowed<TQuery>(); return base.Query<TQuery>(); } public override object Find(Type entityType,params object[] keyValues) { EnsureRootQueryAllowed(entityType); return base.Find(entityType,keyValues); } public override ValueTask<object> FindAsync(Type entityType,params object[] keyValues) { EnsureRootQueryAllowed(entityType); return base.FindAsync(entityType,object[] keyValues,CancellationToken cancellationToken) { EnsureRootQueryAllowed(entityType); return base.FindAsync(entityType,keyValues,cancellationToken); } public override TEntity Find<TEntity>(params object[] keyValues) { EnsureRootQueryAllowed<TEntity>(); return base.Find<TEntity>(keyValues); } public override ValueTask<TEntity> FindAsync<TEntity>(params object[] keyValues) { EnsureRootQueryAllowed<TEntity>(); return base.FindAsync<TEntity>(keyValues); } public override ValueTask<TEntity> FindAsync<TEntity>(object[] keyValues,CancellationToken cancellationToken) { EnsureRootQueryAllowed<TEntity>(); return base.FindAsync<TEntity>(keyValues,cancellationToken); } // Add other overrides as needed... } public class ModelContext : ModelContextBase { public DbSet<User> Users { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlServer( @"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63887500_01") .UseLoggerFactory(LoggerFactory.Create(b => b .AddConsole() .AddFilter(level => level >= LogLevel.Information))) .EnableSensitiveDataLogging() .EnableDetailedErrors(); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); OnUserModelCreating(modelBuilder); } protected void OnUserModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>( entity => { entity.HasOne(e => e.UserProfilePhoto) .WithOne() .HasForeignKey<UserProfilePhoto>(e => e.Id); }); } } internal static class Program { private static void Main() { var accessingSetThrows = false; using (var ctx = new ModelContext()) { ctx.Database.EnsureDeleted(); ctx.Database.EnsureCreated(); var user = User.Create(new byte[] { }); ctx.Users.Add(user); ctx.SaveChanges(); // Make sure,that UserProfilePhoto cannot be queried directly. try { ctx.Set<UserProfilePhoto>() .ToList(); } catch (InvalidOperationException) { accessingSetThrows = true; } Debug.Assert(accessingSetThrows); } // No eager loading by default with owned type here. using (var ctx = new ModelContext()) { var users = ctx.Users.ToList(); Debug.Assert(users.Count == 1); Debug.Assert(users[0].UserProfilePhoto == null); } // Explicitly load profile photo. using (var ctx = new ModelContext()) { var users = ctx.Users.ToList(); ctx.Entry(users[0]).Reference(u => u.UserProfilePhoto).Load(); Debug.Assert(users.Count == 1); Debug.Assert(users[0].UserProfilePhoto != null); } } } } 实现

表达式访问者可用于解决问题,方法是使用IQueryTranslationPreprocessorFactory实现在查询中搜索特定表达式,只有在调用新的IQueryTranslationPreprocessorFactory扩展方法并抛出该异常时才添加该表达式,如果丢失,并且正在查询非根实体。实际上,这应该足以确保团队中没有人偶然查询非根对象。

(您还可以将内部类实例作为常量参数添加到方法调用表达式中,然后在表达式访问者中对其进行求值,以确保调用者确实对{{1 }}方法。但这只是锦上添花,在实践中是不必要的,因为开发人员无论如何都可以使用反射来绕过任何访问限制。因此,我不会为实现这一问题而烦恼。

这里是实现(使用自定义界面而不是自定义属性来标记不应直接查询的实体):

InternalQuery()
,

字节真的应该是域的一部分吗?您实际上在用户配置文件上下文中的那些字节上运行任何业务逻辑吗?真的有一种用例,您想从User AR内部访问字节吗?

如果不是这样,那么将字节存储与照片的元数据解耦,并引入具有ProfilePhoto属性的storageUrl/storageId VO来定位字节是更有意义的。

请不要忘记您的域模型应该为命令而不是查询和表示层设计。

当然,现在在将字节和AR的数据存储在数据库中时,您不容易拥有ACID属性,但通常很容易通过清理过程来解决。

如果您不需要User中的个人资料照片的元数据来执行业务规则,那么您也可以考虑将ProfilePhoto设为自己的AR。

最后,我认为尝试防止ORM滥用是不必要的。应该将ORM视为低级API,永远不要直接将其用于更改AR状态。我认为可以安全地假设开发人员将有足够的严格性来遵守该规则,就像他们应该尊重整个系统的体系结构一样。如果他们不这样做,那么您会有更大的问题。如果可以轻松地向成员添加私有修饰符,那么可以肯定,但这似乎需要付出很多努力,因此我将采取务实的方式...

,

我找到了一个临时解决方案:

modelBuilder.Entity<User>()
    .OwnsOne(u => u.UserProfilePhoto,builder =>
    {
        builder.Metadata.IsOwnership = false;
        builder.Metadata.IsRequired = false;
        builder.Metadata.PrincipalToDependent.SetIsEagerLoaded(false);

        builder.ToTable("UserProfilePhoto");

        builder.Property(u => u.ProfilePhoto)
            .IsRequired();
    });

我不喜欢它,我想EF允许您以其他更清晰的方式进行配置。我不接受这个答案,希望其他人可以指出正确的方向。


编辑:代理以这种方式工作,但是当删除User时,与UserProfilePhoto的关联被切断:

实体“ User”和“ UserProfilePhoto”之间的关联 键值'{UserId:1}'已被切断,但关系为 标记为“必需”或隐式必需,因为 外键不可为空。如果附属/子实体应该是 在切断必要的关系后将其删除,然后设置 关系以使用级联删除。

我什至尝试通过元数据指定DeleteBehaviour.Cascade选项,但这可能会破坏内部约束。

此外,现在可以通过DbContext.Set<UserProfilephoto>()来访问它,这不是我想要的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-