Inserting new record into linking table with linq to entities with POCO
I have a Team table and a Player table in many to many relationship. There is a linking table called TeamOnPlayer. EF with POCO generates navigation propertie called Person for the Team entity and also generates a nav. prop. called Team for the People entity.
I'm trying to insert a new record into the TeamOnPlayer table, but EF and POCO hides it. I tried to do this:
public static void AddPersonToTeam(int TeamId, int PersonId)
{
using (var ef = new korfballReportEntities())
{
var team = GetTeam(TeamId);
var person = GetPerson(PersonId);
team.Person.Add(person);
person.Team.Add(team);
ef.SaveChanges();
}
}
The GetTeam(TeamId) and GetPerson(PersonId) gets the right team and person:
public static Team GetTeam(int id)
{
using (var ef = new korfballReportEntities())
{
var q = from l in ef.Team
where l.Id == id
select l;
return q.Single();
}
}
public static Person GetPerson(int id)
{
using (var ef = new korfballReportEntities())
{
var query = from p in ef.Person
where p.Id == id
select p;
return query.Single();
}
}
When it tries to call the team.Person.Add(person) it throws an exception:
"The ObjectContext instance has been disposed and can no longer be used for operations that require a connection." System.Exception {System.ObjectDisposedException}
Can anyone please show me the correct way?
Edit
Now I understand what the problem was, thanks to you. I was a bit confused about the using blocks you included. For example this:
using (var ef = new korfballReportEntities())
{
//switch lazy loading off, only in this single context
ef.Configuration.LazyLoadingEnabled = false;
var repository = new MyRepository(ef);
repository.AddPersonToTeam(int TeamId, int PersonId);
}
Where should I put it?
I've done something else. I simply did this, and it worked fine.
public static void AddPersonToTeam(int TeamId, int Perso开发者_运维技巧nId)
{
using (var ef = new korfballReportEntities())
{
var q = from t in ef.Team
where t.Id == TeamId
select t;
var team = q.Single();
var q2 = from p in ef.Person
where p.Id == PersonId
select p;
var person = q2.Single();
try
{
team.Person.Add(person);
person.Team.Add(team);
}
catch (Exception e)
{
}
ef.SaveChanges();
}
}
The only problem is, that i coludn't reuse my GetPerson(int id) and GetTeam(int id) method.
What do you think? Is it okay? Is this an ugly way?
My guess is that you are working with lazy loading - your navigation properties Team.Person
and Person.Team
are marked as virtual
in your entity classes. The result is that your methods GetTeam
and GetPerson
do not exactly return Team
and Person
objects but instances of dynamically created proxy classes which are derived from those entities. This dynamic proxy supports lazy loading which means that EF tries to load the navigation collections Team.Person
and Person.Team
when you access them for the first time. This happens in your AddPersonToTeam
method when you call Add
on these collections.
Now the problem is that the proxies are created within an context which you immediately dispose in your GetTeam
and GetPerson
methods (at the end of the using block). The proxies have stored a reference to this context internally and will use this context to load the navigation collections from the database.
Because these contexts are already disposed you get the exception.
You should redesign your code a bit: Don't create a new context in your repository methods GetTeam
and GetPerson
. You should instead use the same context for all operations: Retrieving the Team
, retrieving the Person
and adding the relationship. For example:
public static void AddPersonToTeam(int TeamId, int PersonId)
{
using (var ef = new korfballReportEntities())
{
var team = GetTeam(ef, TeamId);
var person = GetPerson(ef, PersonId);
team.Person.Add(person);
//person.Team.Add(team); <- not necessary, EF will handle this
ef.SaveChanges();
}
}
public static Team GetTeam(korfballReportEntities ef, int id)
{
var q = from l in ef.Team
where l.Id == id
select l;
return q.Single();
}
public static Person GetPerson(korfballReportEntities ef, int id)
{
var query = from p in ef.Person
where p.Id == id
select p;
return query.Single();
}
Another approach is to make your "Repository"/"Service" not static, inject the context into the constructor and then use this context throughout the repository. Then you don't need to pass in the context into every method. A rough sketch:
using (var ef = new korfballReportEntities())
{
var repository = new MyRepository(ef);
repository.AddPersonToTeam(int TeamId, int PersonId);
}
public class MyRepository
{
private readonly korfballReportEntities _ef;
public MyRepository(korfballReportEntities ef)
{
_ef = ef;
}
public void AddPersonToTeam(int TeamId, int PersonId)
{
var team = GetTeam(TeamId);
var person = GetPerson(PersonId);
team.Person.Add(person);
_ef.SaveChanges();
}
public Team GetTeam(int id)
{
var q = from l in _ef.Team
where l.Id == id
select l;
return q.Single();
}
public Person GetPerson(int id)
{
var query = from p in _ef.Person
where p.Id == id
select p;
return query.Single();
}
}
Edit
One little thing about performance tuning: In this specific case lazy loading is not necessary and more disturbing. It causes to load a (potentially long) collection team.Person
when you want to add only one additional Person
to the collection. You can switch off lazy loading for this particular operation (I refer to my second example):
using (var ef = new korfballReportEntities())
{
//switch lazy loading off, only in this single context
ef.Configuration.LazyLoadingEnabled = false;
var repository = new MyRepository(ef);
repository.AddPersonToTeam(int TeamId, int PersonId);
}
public void AddPersonToTeam(int TeamId, int PersonId)
{
var team = GetTeam(TeamId);
var person = GetPerson(PersonId);
// if lazy loading is off, the collecton is null, so we must instantiate one
if (team.Person == null)
team.Person = new List<Person>();
team.Person.Add(person);
_ef.SaveChanges();
}
精彩评论