【问题标题】:Include fields from related tables包括相关表中的字段
【发布时间】:2018-10-28 04:51:04
【问题描述】:

我在我的应用程序中使用 EF 和 DotNet Core 2.1。该应用程序处理多个相关表和 FK 互连表中的数据。

我只需要对一张表的审计日志数据进行更改。但是,我的问题是,我需要审核日志的表有相当多的 FK,对于其中的每一个,我都想记录 FK 本身和相关表中的字段。

让我试着说明一下我的意思 - 假设这是我的模型:

public class Blog {
  public int Id { get; set; }
  public string Name { get; set; }
  public string Url { get; set; }

  [InverseProperty ("Blog")]
  public ICollection<Post> Posts { get; set; }

  public Blog() {
    Posts = new Collection<Post> ();
  }
}
...
[AuditInclude]
public class Post
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string Content { get; set; }

  [Required]
  public int BlogId { get; set; }
  public Blog Blog { get; set; }
}

如前所述,我希望审计日志只更改一个实体 - 假设它是 Post - 这是一个审计类:

public class Audit_Post : IAudit {
  public int Id { get; set; }

  public string Title { get; set; }
  public string Content { get; set; }

  public int BlogId { get; set; }
  public string Blog { get; set; }    // <- I need populating this from Blog.Name

  [StringLength (64)]
  public string AuditUsername { get; set; }
  public DateTime AuditDt { get; set; }
  public string AuditAction { get; set; }

  public Audit_Manufacturer () { }
}

这就是我在startup.cs -&gt; ConfigureService() 中设置审计日志的方式:

...
Audit.Core.Configuration.Setup ()
  .UseEntityFramework (ef => ef
    .AuditTypeExplicitMapper (m => m
      .Map<Post, Audit_Post> ((d, al) => {
        al.Blog = d.Blog?.Name;    // !! This doesn't work
      })
      .AuditEntityAction<IAudit> ((evt, entry, auditEntity) => {
        Object val;
        var gotVal = evt.CustomFields.TryGetValue ("AuditUsername", out val);
        string username = null;
        if (gotVal && val is string)
          username = val as string;
        else
          username = "<anonymous>";
        auditEntity.AuditDt = DateTime.UtcNow;
        auditEntity.AuditUsername = username;
        auditEntity.AuditAction = entry.Action;
      })
    )
  );

问题:是否有可能从依赖表(一对多)关系中获取和审核日志数据?

除了提到的问题之外,我还遇到了一个离题的问题,那就是 - 如果我忘记使用迁移来更新数据库以初始化 Audit_Posts 表并且我正在对 Posts 表进行操作,即使审计日志无法写入(UnitOfWork 保存异常),数据也会存储到后面。 AuditDbContext 是否有一个标志可以使其在与原始查询相同的事务中运行?

【问题讨论】:

    标签: ef-core-2.1 audit.net


    【解决方案1】:

    正如@thepirat000 指出的那样,保证所有相关项目都存在于DbContext 内存中就足够了。这意味着:

    • INSERT 在执行context.Posts.Add(item) 之前查询所有相关项,例如context.Blogs.Find(item.BlogId)
    • 更新 检索Post 时,请使用.Include(d =&gt; d.Blog) + 其他相关项目。
    • DELETE 检索Post 时,请使用.Include(d =&gt; d.Blog) + 其他相关项。

    另一个让我烦恼的重要事情是我的审计表的布局。问题是我在审计表中以不同的类型重用了相同的属性名称 - 在原始表中,属性Blog 是一个关系属性,而在审计表中它是一个字符串。这会导致从一种模型转换到另一种模型时出现错误。

    [AuditInclude]
    public class Post
    {
      ...
      [Required]
      public int BlogId { get; set; }
      public Blog Blog { get; set; }
      ...
    }
    

    只需将其重命名为:

    public class Audit_Post
    {
      ...
      public int BlogId { get; set; }
      public string BlogName { get; set; }
      ...
    }
    ...
    // and in startup.cs use ...
    ...
    .Map<Post, Audit_Post> ((d, al) => {
        al.BlogName = d.Blog?.Name;
      })
    ...
    

    关于第二个问题 - 在事务中运行审计。我决定暂时不使用它。我将通过测试了解所描述的案例。

    也许是对包的未来开发的建议——如果能简单地提到案例就好了——我的意思是,传递属性。

    【讨论】:

      【解决方案2】:

      如果您在检索Post 实体时Include Blog 属性,您的代码应该可以正常工作,例如:

      using (var context = new BlogsContext())
      {
          var post = context.Posts
              .Include(p => p.Blog)  // Important, otherwise post.Blog will be NULL
              .First();
      
          post.Content += " adding this";
          context.SaveChanges();
      }
      

      如果由于某种原因无法包含 Blog 实体,您可以对映射进行查询,但您需要在 Map 方法上使用较低级别的重载,如下所示:

      .Map<Post, PostAudit>((ev, entry, postAudit) =>
      {
          var entryEf = entry.GetEntry();
      
          var post = entryEf.Entity as Post;
          var dbContext = entryEf.Context as BlogsContext;
      
          // Get the blog related
          var blog = dbContext.Blogs.FirstOrDefault(b => b.Id == post.BlogId);
      
          postAudit.Blog = blog?.Name;
      })
      

      关于另一个问题,目前没有内置机制可以在审核保存失败时回滚数据库更改,但也许您可以尝试使用AuditDbContext overrides

      【讨论】:

      • 感谢您的快速回复。我已经尝试过使用Include(),但没有成功。我认为这是因为它不是selectupdatedelete。这是我在PostRepository -&gt; Add(Post item):context.Posts.Include(d =&gt; d.Blog).Add(item) 中尝试的,但这导致了无效操作,因为IIncludableQueryable&lt;Post, Blog&gt; 不包含Add() 的定义。我正在对此进行进一步调查。您建议的其他解决方案可能会起作用 - 我现在就尝试一下。我只是想知道这是否会导致 +1 查询的成本更高。
      • 使用Map&lt;&gt;((ev, entry, postAudit) =&gt; { ... }) 代码我只能在dbContext.Blogs 上使用异步方法,但映射不是异步的,因此我不能等待结果 - 我仍在调查为什么
      猜你喜欢
      • 2011-03-04
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多