Включить поля из связанных таблиц - PullRequest
0 голосов
/ 27 октября 2018

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

Ответы [ 2 ]

0 голосов
/ 27 октября 2018

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

  • INSERT Непосредственно перед выполнением 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;
  })
...

По второму вопросу - проведение аудита внутри транзакций. Я решил не использовать это сейчас. Я опишу описанный случай тестами.

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

0 голосов
/ 27 октября 2018

Ваш код должен работать нормально, если вы 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();
}

Если вы не можете включить сущность Блог по какой-либо причинеВы можете выполнить запрос для сопоставления, но вам нужно будет использовать перегрузку более низкого уровня для метода 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...