Включить поля из связанных таблиц

Я использую 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 -> 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 флаг, который заставит его работать в той же транзакции, что и исходный запрос?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
82
2

Ответы 2

Ваш код должен работать нормально, если вы Include свойство Blog при извлечении объекта Post, например:

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отменяет

Спасибо за быстрый ответ. Я уже пробовал использовать Include(), но безуспешно. Я думал, это связано с тем, что это не select, update или delete. Это то, что я пробовал в моем PostRepository -> Add(Post item): context.Posts.Include(d => d.Blog).Add(item), но это привело к недопустимой операции, поскольку IIncludableQueryable<Post, Blog> не содержит определения для Add(). Я продолжаю расследование по этому поводу. Другое решение, которое вы предложили, вероятно, сработает - я собираюсь попробовать его прямо сейчас. Мне просто интересно, будет ли это намного дороже, так как это приведет к запросу +1.

damird 27.10.2018 20:28

С кодом Map<>((ev, entry, postAudit) => { ... }) я могу использовать только асинхронные методы на dbContext.Blogs, но отображение не асинхронное, поэтому я не могу дождаться результата - я все еще пытаюсь понять, почему <<< Я чувствую себя новичком :)

damird 27.10.2018 21:33

Как отметил @ thepirat000, этого достаточно, чтобы гарантировать, что все связанные элементы присутствуют в памяти DbContext. Это означает:

  • ВСТАВЛЯТЬ Непосредственно перед выполнением context.Posts.Add(item) выполните запрос ко всем связанным элементам, таким как context.Blogs.Find(item.BlogId).
  • ОБНОВИТЬ При извлечении Post делайте это с .Include(d => d.Blog) + другими связанными элементами.
  • УДАЛЯТЬ При извлечении Post делайте это с .Include(d => 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-му вопросу - ведение аудита внутри транзакций. Решил пока не использовать. Рассмотрю описанный случай тестами.

Может быть, предложение для будущего развития пакета - было бы хорошо, если бы упомянутые случаи были легко рассмотрены - я имею в виду, транзитивные свойства.

Другие вопросы по теме