Note that there are some explanatory texts on larger screens.

plurals
  1. PORepository Pattern with Entity Framework 4.1 and Parent/Child Relationships
    text
    copied!<p>I still have some confusion with the Repository Pattern. The primary reason why I want to use this pattern is to avoid calling EF 4.1 specific data access operations from the domain. I'd rather call generic CRUD operations from a IRepository interface. This will make testing easier and if I ever have to change the data access framework in the future, I will be able to do so without refactoring a lot of code.</p> <p>Here is an example of my situation:</p> <p>I have 3 tables in the database: <code>Group</code>, <code>Person</code>, and <code>GroupPersonMap</code>. <code>GroupPersonMap</code> is a link table and just consists of the <code>Group</code> and <code>Person</code> primary keys. I created an EF model of the 3 tables with VS 2010 designer. EF was smart enough to assume <code>GroupPersonMap</code> is a link table so it doesn't show it in the designer. I want to use my existing domain objects instead of EF's generated classes so I turn off code generation for the model. </p> <p>My existing classes that matches the EF model are as follows:</p> <pre><code>public class Group { public int GroupId { get; set; } public string Name { get; set; } public virtual ICollection&lt;Person&gt; People { get; set; } } public class Person { public int PersonId {get; set; } public string FirstName { get; set; } public virtual ICollection&lt;Group&gt; Groups { get; set; } } </code></pre> <p>I have a generic repository interface like so:</p> <pre><code>public interface IRepository&lt;T&gt; where T: class { IQueryable&lt;T&gt; GetAll(); T Add(T entity); T Update(T entity); void Delete(T entity); void Save() } </code></pre> <p>and a generic EF repository:</p> <pre><code>public class EF4Repository&lt;T&gt; : IRepository&lt;T&gt; where T: class { public DbContext Context { get; private set; } private DbSet&lt;T&gt; _dbSet; public EF4Repository(string connectionString) { Context = new DbContext(connectionString); _dbSet = Context.Set&lt;T&gt;(); } public EF4Repository(DbContext context) { Context = context; _dbSet = Context.Set&lt;T&gt;(); } public IQueryable&lt;T&gt; GetAll() { // code } public T Insert(T entity) { // code } public T Update(T entity) { Context.Entry(entity).State = System.Data.EntityState.Modified; Context.SaveChanges(); } public void Delete(T entity) { // code } public void Save() { // code } } </code></pre> <p>Now suppose I just want to map an existing <code>Group</code> to an existing <code>Person</code>. I would have to do something like the following:</p> <pre><code> EFRepository&lt;Group&gt; groupRepository = new EFRepository&lt;Group&gt;("name=connString"); EFRepository&lt;Person&gt; personRepository = new EFRepository&lt;Person&gt;("name=connString"); var group = groupRepository.GetAll().Where(g =&gt; g.GroupId == 5).First(); var person = personRepository.GetAll().Where(p =&gt; p.PersonId == 2).First(); group.People.Add(person); groupRepository.Update(group); </code></pre> <p>But this doesn't work because EF thinks <code>Person</code> is new, and will try to re-<code>INSERT</code> the <code>Person</code> into the database which will cause a primary key constraint error. I must use <code>DbSet</code>'s <code>Attach</code> method to tell EF that the <code>Person</code> already exists in the database so just create a map between <code>Group</code> and <code>Person</code> in the <code>GroupPersonMap</code> table.</p> <p>So in order to attach <code>Person</code> to the context I must now add an <code>Attach</code> method to my IRepository:</p> <pre><code>public interface IRepository&lt;T&gt; where T: class { // existing methods T Attach(T entity); } </code></pre> <p>To fix the primary key constraint error:</p> <pre><code>EFRepository&lt;Group&gt; groupRepository = new EFRepository&lt;Group&gt;("name=connString"); EFRepository&lt;Person&gt; personRepository = new EFRepository&lt;Person&gt;(groupRepository.Context); var group = groupRepository.GetAll().Where(g =&gt; g.GroupId == 5).First(); var person = personRepository.GetAll().Where(p =&gt; p.PersonId == 2).First(); personRepository.Attach(person); group.People.Add(person); groupRepository.Update(group); </code></pre> <p>Fixed. Now I have to deal with another issue where <code>Group</code> is being UPDATE'd in the database every time I create a Group/Person map. This is because in my <code>EFRepository.Update()</code> method, the entity state is explicitly set to <code>Modified'. I must set the Group's state to</code>Unchanged<code>so the</code>Group` table doesn't get modified.</p> <p>To fix this I must add some sort of <code>Update</code> overload to my IRepository that does not update the root entity, or <code>Group</code>, in this case:</p> <pre><code>public interface IRepository&lt;T&gt; where T: class { // existing methods T Update(T entity, bool updateRootEntity); } </code></pre> <p>The EF4 implentation of the Update method would look something like this:</p> <pre><code>T Update(T entity, bool updateRootEntity) { if (updateRootEntity) Context.Entry(entity).State = System.Data.EntityState.Modified; else Context.Entry(entity).State = System.Data.EntityState.Unchanged; Context.SaveChanges(); } </code></pre> <p>My question is: Am I approaching this the right way? My Repository is starting to look EF centric as I start to work with EF and the repository pattern. Thanks for reading this long post</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload