#entity-framework
#entity-framework
Вопрос:
У меня есть 3 экземпляра: Author, EventInstance и комментарии. Автор может написать комментарий. Пользователи (авторы) могут добавлять комментарии к EventInstance. Вот модель. Я продолжаю получать следующую ошибку.
public class Author
{
public Guid Id { get; set; }
//..
public List<EventInstance> Events { get; set; }
= new List<EventInstance>();
public ICollection<Comment> Comments { get; set; }
= new List<Comment>();
}
public class EventInstance
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; } //Author FK
//..
public Author Author { get; set; }
public ICollection<Comment> Comments { get; set; }
= new List<Comment>();
}
public class Comment
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; } //Author FK
public Guid EventId { get; set; }
public EventInstance Event { get; set; }
public Author Author { get; set; }
}
Я пробовал несколько способов исправить, но, похоже, ничего не работает. Даже простой запрос, такой как:
var list = await db.Events
.Include(e => e.Author)
.ToListAsync();
Спасибо за помощь.
Редактировать
Вот мой класс контекста
public class EventContext : DbContext
{
public DbSet<EventInstance> Events { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Author> Authors { get; set; }
public EventContext(DbContextOptions<EventContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<EventInstance>()
.HasOne(e => e.Author)
.WithMany(a => a.Events)
.HasForeignKey(e => e.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Comment>()
.HasOne(c => c.Event)
.WithMany(e => e.Comments)
.HasForeignKey(c => c.EventId)
.OnDelete(DeleteBehavior.NoAction);
builder.Entity<Comment>()
.HasOne(c => c.Author)
.WithMany(a => a.Comments)
.HasForeignKey(c => c.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
}
}
Я использую фабрику для добавления и запуска миграций
public class EventContextFactory : IDesignTimeDbContextFactory<EventContext>
{
public EventContext CreateDbContext(string[] args)
{
string connectionString =
Окружающая среда.GetEnvironmentVariable(«SqlConnectionString»);
var optionsBuilder = new DbContextOptionsBuilder<EventContext>();
optionsBuilder.UseSqlServer(connectionString);
return new EventContext(optionsBuilder.Options);
}
}
И это класс запуска
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string connectionString = Environment.GetEnvironmentVariable("SqlConnectionString");
builder.Services.AddDbContext<EventContext>(
options => options.UseSqlServer(connectionString));
builder.Services.AddScoped<IEventService, EventService>();
}
}
Я надеюсь, что все соответствующие фрагменты были вставлены сюда.
Комментарии:
1. Это не должно решить вашу проблему, но все же я бы сказал, что
[ForeignKey("EventInstance")]
это должно быть[ForeignKey("Event")]
2. Это проблема сериализации Json, а не EF (Core). Вы можете найти много связанных сообщений, выполнив поиск в Web / SO с сообщением об ошибке. Вот один из них, связанный с функциями Azure github.com/Azure/azure-functions-durable-extension/issues/685
Ответ №1:
Вам следует подумать о нормализации структуры, чтобы избежать двойной ссылки. Например, если у авторов есть коллекция экземпляров событий, а экземпляры событий имеют список комментариев, тогда авторы могут получать свои комментарии через экземпляры событий.
public class Author
{
public Guid Id { get; set; }
//..
public virtual ICollection<EventInstance> Events { get; set; }
= new List<EventInstance>();
}
public class EventInstance
{
public Guid Id { get; set; }
[ForeignKey("Author")]
public Guid AuthorId { get; set; }
//..
public Author Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public Guid Id { get; set; }
[ForeignKey("Event")]
public Guid EventId { get; set; }
public EventInstance Event { get; set; }
}
Затем, чтобы получить комментарии для конкретного автора:
var commentsForAuthor = context.Authors
.Where(x => x.Id == authorId)
.SelectMany(x => x.Events.SelectMany(e => e.Comments))
.ToList();
В качестве альтернативы, если комментарии можно рассматривать как объект верхнего уровня (/ w DbSet)
var commentsForAuthor = context.Comments
.Where(x => x.EventInstance.AuthorId == authorId)
.ToList();
Обычно вы используете проекцию для получения подробной информации о комментарии, связанном с ним событии и авторе. (Использование Select
) Это позволяет перемещаться по реляционной структуре для получения соответствующих сведений.
Вероятно, вы можете применить выражения сопоставления для устранения ошибки с помощью явного сопоставления, но я бы рекомендовал удалить денормализацию. Проблема с денормализацией ссылок, чтобы автор мог иметь коллекцию комментариев, заключается в том, что нет способа обеспечить соответствие ссылки на автора комментария автору EventInstance комментария. Т.е. Я могу иметь идентификатор автора 1 с идентификатором EventInstance 1 и создать комментарий сИДЕНТИФИКАТОР 101. Ничто не мешает мне изменить идентификатор автора этого комментария на 2. Теперь у меня есть два источника истины относительно того, кто является автором. Comment.Author
(ID: 2) и Comment.EventInstance.Author
. (ID: 1) Везде, где это возможно, удалите эти повторяющиеся ссылочные пути.
РЕДАКТИРОВАТЬ: обратная связь по сопоставлению
При сопоставлении ссылок и обратно вы захотите избежать двойного сопоставления в смысле сопоставления отношений из обеих сущностей. Сопоставьте отношения с одной стороны. Вы могли бы попробовать:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EventInstance>()
.HasOne(x => x.Author)
.WithMany(x => x.Events)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<EventInstance>()
.HasMany(x => x.Comments)
.WithOne(x => x.Event)
.HasForeignKey(x => x.EventId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Author>()
.Ignore(a => a.AuthorName)
.HasMany(x => x.Comments)
.WithOne(x => x.Author)
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
}
Редактировать # 2: если авторы могут создавать комментарии для событий другого автора, я бы предложил удалить связь комментариев со стороны автора. Комментарии могут быть получены как объект верхнего уровня или через События как объект верхнего уровня.
Например:
public class Author
{
public Guid Id { get; set; }
//..
public virtual ICollection<EventInstance> Events { get; set; }
= new List<EventInstance>();
}
public class EventInstance
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
//..
public Author Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public Guid Id { get; set; }
public Guid EventId { get; set; }
public virtual EventInstance Event { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
}
Затем в сопоставлении:
modelBuilder.Entity<Comment>()
.HasOne(x => x.Author)
.WithMany()
.HasForeignKey(x => x.AuthorId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Comment>()
.HasOne(x => x.Event)
.WithMany(x => x.Comments)
.HasForeignKey(x => x.EventId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Author>()
.Ignore(a => a.AuthorName);
Это переключает сопоставление с события на комментарий, но комментарий сохраняет двунаправленную ссылку на это событие, но однонаправленную ссылку на его автора. ( HasMany()
) Это должно удовлетворять отображению EF. На заметку с явным сопоставлением отношений с использованием OnModelCreating
or IEntityTypeConfiguration
нам не нужны [ForeignKey]
атрибуты, поэтому я их удалил.
Комментарии:
1. Автор может добавить комментарий к событию, которое он / она не создавал. В этом случае я должен создать
authors
иcomments
many to many
связь? Я боюсь, что это может не решить проблему.2. Если это так, то авторы могут создать комментарий для события, которое они не создавали, тогда я бы удалил «многие» отношения для автора из комментариев. Я обновлю свой ответ, чтобы описать этот вариант.
3. Все, что вы предлагаете, не сработало
Azure Function
. Мне было так любопытно, что я реализовал исходную модель в ASP.NET Ядро MVC, то есть автор может добавить событие. Автор события или любой другой автор может прокомментировать событие. Тот же код, который вызывал проблему в функции Azure, отлично работал в ASP.NET Ядро MVC.4. Работают ли другие отношения сущностей, как ожидалось, в функции Azure?
5. нет. Я удалил все свойства навигации из всех объектов и всей конфигурации. Я запускаю миграцию, а затем обновляю базу данных. Итак, все 3 таблицы не имеют FK. Я запускаю простейший запрос
var list = await db.Events.ToListAsync();
. У меня все еще одна и та же проблема, как включеннаяAzure
, так и включеннаяMSSQLLocalDB
.