views:

139

answers:

2

One type of entity in my model (let's call it E1) needs to be able to treat its relationship with another entity type (E2) as a stack. Reciprocally, that other entity needs to be able to see all related entities of the first type where the E2 is at the top of the stack, and separately every case where the E2 is within a stack for an E1.

That doesn't sound all that clear to me, so let me try and demonstrate:

E1 entities: foo { stack: quux, plugh, glurp }, bar { stack: plugh, glurp }, baz { stack: quux, plugh }

E2 entities: quux { top: null; in: foo, baz }, plugh { top: baz; in: foo, bar, baz }, glurp { top: bar; in: foo, bar }

Right now, I have a database table which has columns for the keys to both E1 and E2, as well as an int for storing the E2's position in the stack. Entity Framework treats this table as its own entity, rather than part of the relationship between E1 and E2, which complicates queries and just leads to some plain ugly code.

I know I'm doing it wrong, but is it possible to do this right? And if so, how?

+1  A: 

So if we call your relationship table R, each E1 "contains" multiple Rs (it's stack, ordered by R.position) and each E2 contains multiple Rs in two ways: those with R.position == R.e1.top and those where this does not hold).

  • Is that correct?
  • How are you keeping track of the top? Do you update all the Rs for an E1 when the stack changes (e.g. e1.top is constant) or do you store it in each E1?
  • Would it make sense to have the Rs chained (giving them a "next pointer" rather than a "position")
  • Do you need to random-access the stacks?
  • Do the E2s really need to know about the cases where they aren't at the top?
MarkusQ
Right now each E2 contains multiple Rs in only one way, and determining top is done via a LINQ query. I'd rather R become totally invisible, by E1s having a single stack object for its end of the relationship and E2s having two lists -- one for top and one for generally in-stack.
Chris Charabaruk
Random access is only needed so far as knowing that an E2 is somewhere in an E1's stack, so that if the E2 cares to change what's further up the stack, it can do so. (E2s have E1s delegated to them, and can delegate those E1s to other E2s, including changing who they delegated the E1s to before.)
Chris Charabaruk
Chaining (which I'm understanding as giving each R a nullable column for pointing to another R) may be good too, but I fear might further increase the complexity of the problem. OTOH it's harder to screw up and means I don't have a split key any more. (R's PK is (E1.key,position) atm.)
Chris Charabaruk
+1  A: 

Because this is quite an exotic situation, there is no build in support for that. But you can make it look nice.

For now you have something like that assuming your join table is called E1E2.

public partial class E1
{
   public Guid Id { get; set; }
   public IQueryable<E1E2> Stack { get; }
}

public partial class E2
{
   public Guid Id { get; set; }
   public IQueryable<E1E2> In { get; }
}

public partial class E1E2
{
   public E1 E1 { get; set; }
   public E2 E2 { get; set; }
   public Int32 Position { get; set; }
}

As hoc I cannot come up with an better solution to map this to a database. To make the usage as smart as posible just add some properties and methods to the entities. This is easy because the entites a generated as partial classes.

Extend the class E1 with something as follows.

public partial class E1
{
   public IQueryable<E2> NiceStack
   {
      get { return this.Stack.Select(s => s.E2).OrderBy(s => s.Position); }
   }

   public void Push(E2 e2)
   {
      this.Stack.Add(
         new E1E2
         {
            E2 = e2,
            Position = this.Stack.Max(s => s.Position) + 1
         });
   }

   public E2 Pop()
   {
      return this.Stack.
         Where(s => s.Position == this.Stack.Max(s => s.Position).
         Select(s => s.E2).
         Single();
   }
}

Extend the class E2 with something as follows.

public partial class E2
{
   public IQueryable<E1> NiceIn
   {
      get { return this.In.Select(i => i.E1); }
   }

   public IQueryable<E1> NiceTop
   {
      get
      {
         return this.In.
            Where(i => i.Position == i.E1.Stack.Max(s => s.Position)).
            Select(i => i.E1);
      }
   }
}

End there you are. Now it should be possible to write quite nice code around this entities. Probably there are some bugs in the code but the idea should be clear. I left out the code to ensure that related properties are loaded when accessed. You could further make the original properties privat and hide them frome the outside. Maybe you should not include the NiceStack property because this allows random access. Or maybe you want to add more extension - maybe make NiceTop writable pushing an E2 instance onto the stack of an E1 instance inserted into NiceTop of the E2 instance. But the idea remains the same.

The call to Single() will not work with the normal Entity Framework; instead use ToList().Single() to switch to LINQ to Object or use First() but first does not preserve the semantic of exactly one.

Daniel Brückner