Как работать со связями многие-ко-многим?
Я реализовал программу для работы с БД, использовал Entity Framework, т.е. создал классы модели и т.д.
При разработке ПО выяснилось что две таблицы из БД должны иметь связь многие ко многим. Когда база была создана (подход Codе First) в базе между двумя таблицами появилась ещё одна. Всё в принципе хорошо программа работает, но вот из-за того что появилась эта таблица я не могу работать программно с ней т.к. я не создавал класса(модель) этой таблицы в итоге когда у меня есть какой нибудь Id
первой таблице по которому мне нужно получить что нибудь из второй через LINQ я этого сделать не могу, приходится писать запрос "вручную". Как можно решить данную проблему, что бы можно было программно работать со связями многие ко многим через LINQ?
ServiceStationContext db = new ServiceStationContext();
public class WorkOrder1
{
public int Id { get; set; }
public string Accepter { get; set; }
public string Foreman { get; set; }
public string myDate { get; set; }
public ICollection<GuideWorkTypeStandardHour1> GuideWorkTypeStandardHour1s { get; set; }
public WorkOrder1()
{
GuideWorkTypeStandardHour1s = new List<GuideWorkTypeStandardHour1>();
}
}
public class GuideWorkTypeStandardHour1
{
public int Id { get; set; }
public string CodeWork { get; set; }
public ICollection<WorkOrder1> WorkOrder1s { get; set; }
public GuideWorkTypeStandardHour1()
{
WorkOrder1s = new List<WorkOrder1>();
}
}
Сохранение в БД:
private void button1_Click_1(object sender, EventArgs e)
{
WorkOrder1 wo = new WorkOrder1();
wo.myDate = maskedTextBoxData.Text;
wo.Accepter = textBoxAccepter.Text;
wo.Foreman = textBoxForeman.Text;
wo.BestPractice = textBox4Recommendation.Text;
db.WorkOrders.Add(wo);
db.SaveChanges();
}
Считывание из БД с учётом подсказки указанной ниже(т.е. св-ва теперь virtual
)
List<GuideWorkTypeStandardHour1> guideWorkTypeStandardHour1;
WorkOrder1 workOrder1 = db.WorkOrders.Find(ListWorkOrders.workorderselectedId);
var works = db.GuideWorkTypeStandardHour1s;
foreach(var w in works)
{
if(workOrder1.GuideWorkTypeStandardHour1s.Contains(w))
{
guideWorkTypeStandardHour1.Add(w);
}
}
Писать длинные имена классов мне лень - поэтому я поясню работу с MtM-связами на примере вот такой простой модели:
public class A {
public int Id { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B {
public int Id { get; set; }
public virtual ICollection<A> As { get; set; }
}
1.1 Создание связи между уже привязанными к контексту сущностями
public void Connect(A a, B b) {
if (a.Bs == null) // Проверка на случай отсутствия Lazy Loading
a.Bs = new List<B>();
a.Bs.Add(b);
// И не забыть SaveChanges()
}
1.2 Создание связи по Id
public void Connect(DbContext ctx, int ida, int idb) {
var a = new A { Id = ida };
var b = new B { Id = idb };
// Если сущности c указанными ключами уже загружены в контекст - тут будет ошибка
// Постарайтесь, чтобы так не случалось (лучший способ - каждый раз создавать новый контекст)
ctx.Entry(a).State = EntityState.Unchanged;
ctx.Entry(b).State = EntityState.Unchanged;
a.Bs = new List<B> { b }; // Если тут использовать массив - полезут ошибки при отслеживании связей в будущем. Но если контекст - временный, то можно и массив использовать.
ctx.SaveChanges();
// Очистка контекста - можно не делать, если контекст больше не будет использоваться
ctx.Entry(a).State = EntityState.Detached;
ctx.Entry(b).State = EntityState.Detached;
}
2.1 Сущности уже загружены в контекст
public void Disconnect(A a, B b) {
a.Bs.Remove(b); // Тут не может быть NPE если обе записи загружены в контекст.
// И не забыть SaveChanges()
}
2.2 Сущностей в контексте еще нет
public void Disconnect(DbContext ctx, int ida, int idb) {
var a = new A { Id = ida };
var b = new B { Id = idb };
a.Bs = new List<B> { b };
// Если сущности c указанными ключами уже загружены в контекст - тут будет ошибка
// Постарайтесь, чтобы так не случалось (лучший способ - каждый раз создавать новый контекст)
ctx.Entry(a).State = EntityState.Unchanged;
ctx.Entry(b).State = EntityState.Unchanged;
a.Bs.Remove(b);
ctx.SaveChanges();
// Очистка контекста - можно не делать, если контекст больше не будет использоваться
ctx.Entry(a).State = EntityState.Detached;
ctx.Entry(b).State = EntityState.Detached;
}
3.1 Lazy Loading включен
a.Bs // оно само загрузится
3.2 Ручная загрузка
ctx.Entry(a).Collection(_ => _.Bs).Load()
a.Bs // теперь загружено
3.3 Включение в запрос
var q = (from a in ctx.As
where a.Id = 5
select a).Include(a => a.Bs)
3.4 Получение связей по Id
var a = new A { Id = ida };
ctx.Entry(a).State = EntityState.Unchanged;
ctx.Entry(a).Collection(_ => _.Bs).Load();
//теперь a.Bs не пусто
Я привел примеры работы со связями. Но не рассматривайте их как готовые подпрограммы - число реальных ситуаций намного больше рассмотренных тут (к примеру, одна сущность может быть уже в контексте - а вторая задана своим Id).
Это именно примеры.
Отдельно замечу: почти любая операция над таблицей связей, кроме добавления новой связи, требует полной загрузки всех связанных записей. Если такое поведение нежелательно - надо создавать отдельную связную сущность, преобразовав MtM-отношение в два 1tM.
Примерно так:
public class A {
public int Id { get; set; }
public virtual ICollection<ABLink> ABLinks { get; set; }
}
public class B {
public int Id { get; set; }
public virtual ICollection<ABLink> ABLinks { get; set; }
}
public class ABLink {
[Key]
public int AId { get; set; }
[ForeignKey("AId")]
public virtual A A { get; set; }
[Key]
public int BId { get; set; }
[ForeignKey("BId")]
public virtual B B { get; set; }
}
В таком варианте запросы к БД несколько усложняются - зато можно удалять связи зная только Id концов, без операций загрузки данных из БД вообще.
Еще можно реализовать в сущности-связи интерфейс IEntityWithChangeTracker
, чтобы иметь доступ из нее до контекста БД, чтобы можно было удалять ее зная только ссылку на нее саму, после чего реализовать коллекцию-проекцию, преобразующую ICollection<ABLink>
в ICollection<B>
- но это уже высший пилотаж.
public class WorkOrder1
{
public int Id { get; set; }
public string Accepter { get; set; }
public string Foreman { get; set; }
public string myDate { get; set; }
public virtual ICollection<GuideWorkTypeStandardHour1> GuideWorkTypeStandardHour1s { get; set; }
public WorkOrder1()
{
GuideWorkTypeStandardHour1s = new List<GuideWorkTypeStandardHour1>();
}
}
public class GuideWorkTypeStandardHour1
{
public int Id { get; set; }
public string CodeWork { get; set; }
public virtual ICollection<WorkOrder1> WorkOrder1s { get; set; }
public GuideWorkTypeStandardHour1()
{
WorkOrder1s = new List<WorkOrder1>();
}
}
что бы получить связанные данные необходимо либо воспользоваться lazy loading
для этого необходимо навигационное свойство переделать следующим образом
public virtual ICollection<GuideWorkTypeStandardHour1> GuideWorkTypeStandardHour1s { get; set; }
тогда при обращении к навигационному свойству EF подгрузит необходимые данные (аналогичным образом во втором классе), либо необходимо это сделать следующим образом:
var order = db.WorkOrder
.Where(x=>x.Id==512)
.Include(x=>x.GuideWorkTypeStandardHour1s);
т.е. подгрузить явно
вот смотрите пример только с немного другими моделями:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Player> Players { get; set; }
public Team()
{
Players = new List<Player>();
}
}
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Position { get; set; }
public virtual ICollection<Team> Teams { get; set; }
public Player()
{
Teams = new List<Team>();
}
}
public class DefaultContext : DbContext
{
public DbSet<ConsoleApplication2.Program.Player> Players { get; set; }
public DbSet<ConsoleApplication2.Program.Team> Teams { get; set; }
}
static void Main(string[] args)
{
var db = new DefaultContext();
Player pl1 = new Player { Name = "Роналду", Age = 31, Position = "Нападающий" };
Player pl2 = new Player { Name = "Месси", Age = 28, Position = "Нападающий" };
Player pl3 = new Player { Name = "Хави", Age = 34, Position = "Полузащитник" };
Team t1 = new Team { Name = "Барселона" };
t1.Players.Add(pl2);
t1.Players.Add(pl3);
Team t2 = new Team { Name = "Реал Мадрид" };
t2.Players.Add(pl1);
List<Team> teams = new List<Team>(){ t1,t2 };
db.Teams.AddRange(teams);
db.SaveChanges();
var playerInTeam1 = db.Teams.First();
}
пример взят здесь
Из за какой то ошибки не могу опубликовать ответ от себя (баллы обнулились). Всем спасибо! Ответ на свой вопрос нашёл благодаря подсказке @Pavel Mayorov:
@Vladimir Так у вас, получается, не с MtM проблема, а с устаревшими данными в контексте!
Вот решение моей проблемы! код который требовался:
db.Entry(workOrder1).Collection(p => p.GuideWorkTypeStandardHour1s).Load();
Делаю клиент-серверную игру на 2х игроковСервер ждет пока 2 игрока пришлют информацию и затем рассылает друг друг
Как можно переопределить метод ToString() для коллекции?
Нужна помощь с преобразованием таблицы в сводную диаграмму, таблица формируется из datagridview в excel через epplus, на скринах пример таблицы и сводной...
Есть цикл, в котором генерируется NewTimeРешил проверить, какое число там получается, поставил MessageBox, и на выводе показывает что NewTime - "не число"