Finding an m:n related collection based on two entity types.

Posts   
 
    
Auxon
User
Posts: 6
Joined: 28-Nov-2008
# Posted on: 14-Apr-2009 22:24:32   

I'm stuck when trying to generically find a related collection that links two entities in an m:n relationship.

Given any Ti i and a particular Tk k which both implement IEntity2, I want to find the collection IEnumerable<Tj> jC, that relates i to k.

If there is no j in jC that relates i and k I want to create one.

I hope this explains the situation. This is basically what I want my code to be like, unless there is a better way; the section that is /**/ commented is the problem area.


IEnumerable<Tj> CreateManyToManyRelation<Ti, Tj, Tk>(Tk k, IEnumerable<Ti> toAdd)
            where Ti : IEntity2
            where Tj : IEntity2, new()
            where Tk : IEntity2 {

/*************************************************/         
// How do I get the right member entity collection from k or Tk?
            List<Tj> jC = ????? 
/************************************************/
            foreach (Ti i in toAdd) {               
                
                IEntityField2 iPk = i.PrimaryKeyFields[0];
                IEntityField2 kPk = i.PrimaryKeyFields[0];
                object iPkValue = i.Fields[iPk.Name].CurrentValue;
                object kPkValue = k.Fields[kPk.Name].CurrentValue;
                
                // How else can I get the foreign key field if jC is empty?
                string jiFk = new Tj().PrimaryKeyFields.Single(field => field.Name == iPk.Name).Name; 
                string jkFk = new Tj().PrimaryKeyFields.Single(field => field.Name == kPk.Name).Name;

                if(jC.Count == 0 || 
                    !jC.Any(j => (j.Fields[jiFk].CurrentValue == iPkValue
                        && j.Fields[jkFk].CurrentValue == kPkValue))) {
                    
                    Tj j = new Tj();
                    var iKey = j.PrimaryKeyFields.Single(field => field.Name == i.PrimaryKeyFields[0].Name);
                    iKey.CurrentValue = i.PrimaryKeyFields[0].CurrentValue;
                    var kKey = j.PrimaryKeyFields.Single(field => field.Name == k.PrimaryKeyFields[0].Name);
                    kKey.CurrentValue = k.PrimaryKeyFields[0].CurrentValue;

                    jC.Add(j);
                }           
            }
            return jC;
        }

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-Apr-2009 05:16:38   

Hi Richard,

There is a better way for sure. - Did you already fetch the entities and want to filter them in-memory? - Why Enumerable<...>? - If possible, could you please explain your scenario in terms of a Northwind case? For example: Order <-> (m:n) <-> Product, or something like that. Coz I lost a little bit in the intention of your code.

David Elizondo | LLBLGen Support Team
Auxon
User
Posts: 6
Joined: 28-Nov-2008
# Posted on: 15-Apr-2009 19:42:43   

daelmo wrote:

Hi Richard,

There is a better way for sure. - Did you already fetch the entities and want to filter them in-memory? - Why Enumerable<...>? - If possible, could you please explain your scenario in terms of a Northwind case? For example: Order <-> (m:n) <-> Product, or something like that. Coz I lost a little bit in the intention of your code.

I used IEnumerable here because that's what I want to do, but really right now, it's an:

public partial class EntityCollection<TEntity> : EntityCollectionBase2<TEntity>
   where TEntity : EntityBase2, IEntity2

It shouldn't make a difference really, though, unless EntityCollectionBase2 has something I can use to do what I want that IEnumerable can't (for this problem)? I'd rather go to IEnumerable so that I don't have to require an EntityCollection (sometimes the caller might not have an EntityCollection).

In terms of Northwind ... let's say I want to one or more Products to an existing Order. The Products and Orders have been fetched.

Tk = Order, Ti = Product, Tj = Order Details.

So, for each existing Order <-m:n-> Product, there is one Order Detail record. I want to add a new Order Detail record to link another Product to an Order, thus I need to create a new Order Detail record.

Now that's all very easy to do, however, I don't just have an Order <-m:n-> Product relationship to deal with. I have many Ti <-m:n-> Ti types of relationships. In a particular scenario, Tk will remain constant, but there will be a list of 1 or more Ti to "link" via a new Tj. If the Tj already exists (think, if the Order Detail record already exists), then I don't do anything. If it doesn't I have to make a new Tj.

I another scenario, I will pass in a list of Ti that I no longer want associated with Tk. Think: I want to remove a product from an order.

Ok, so the reason is ... imagine if you were to open up a form for a certain kind of Order ... like an Order with an OrderType of Overnight or something ..., the Order is constant within that form.

Now you display two grids, master detail style. In the first you have a list of all the ProductTypes that are able to be delivered overnight - a ProductType grid. When the user picks a ProductType, the list of all the Products of that type are displayed in the second grid. So there's not just a Product ... there's a BigProduct, SmallProduct, AppleProduct, OrangeProduct, whatever.

A checkbox column is added to the second grid to allow the user to select which products. They may also view currently selected and unselect them as well.

The selected products are iterated through when the user saves, and if a relationship already exists, it's skipped. If not, it's created. Unselected products are also iterated through and existing relationships are removed.

So, I want to be able to use the same code on any scenario that's similar to the one described: Add or remove an m:n relationship, no matter what type of m or n they are, because we do this many places, and over and over again. The code is a giant swicth statement that requires us to check the type of entity and specify the exact collection.

Sorry for the wall of text.

Auxon
User
Posts: 6
Joined: 28-Nov-2008
# Posted on: 15-Apr-2009 20:17:20   

Well I came up with a shorter explanation, so you don't have to read the above, unless this is unclear.

I have some entity of type X, that is related to many other different entities of types A, B, ..., N. They are all in an m:n relationship with X, but all through separate entities of types Ax, Bx, ..., Nx.

So, given any entity x of type X, I want to find out what entity type (Ax, Bx, ... Nx,) relates it to another entity of type A, B, C, D ... etc....

If I have an instance a of A, I want to get what type Ax is, and get the x.MemberEntityCollection that contains Ax elements, and find the instance ax of type Ax that relates a to x, if it exists. If it doesn't I want to make the ax relation (or if it does, I may want to delete it).

Man, I hope that explains it. confused

The problem seems to be that I can't get a specific member entity collection from X, that has elements of a type Tx that relate it to A. Because I can't look inside an empty member collection to determine if an entity is related to A. I need to know which member collections relate to A, when the member collection is empty.

Auxon
User
Posts: 6
Joined: 28-Nov-2008
# Posted on: 15-Apr-2009 20:29:11   

Ok, I think I have it. Maybe not as general as I like, but I will post the code when I get everything cleaned up and readable.

Auxon
User
Posts: 6
Joined: 28-Nov-2008
# Posted on: 15-Apr-2009 22:31:27   

Auxon wrote:

Ok, I think I have it. Maybe not as general as I like, but I will post the code when I get everything cleaned up and readable.

Nope, haven't solved it. The thing is that there is nothing in the IEntity2.GetAllRelations() IRelations collection that has anything to connect to the n side of the m:n relationship, if I have the m side.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 16-Apr-2009 13:50:03   

M:N relations are not explicitly defined in the relations collection, as they're actually a combination of two other relations and these are necessary anyway.

So this information is not directly available in the generated code as it's normally not necessary to have that: m:n relations are readonly so only the code to work with them is there.

You want to obtain this info at runtime and then act on that. The easiest way to do that is to generate this information into the generated code: the meta-data contains this information and all you have to do is write a template (be it for a separate class or an include template for example which generates this info into an entity class simple_smile ), which is then in the format you'd like for your generic code (so your own code is easier to write as the info is already in a format that you can easily consume at runtime for example). I'm not sure if you're familiar with templates and writing them, so a good place to start is the SDK.

Another way, which might also be easy for you but will take some startup time, is to traverse all entities and obtain their relations. Every entity which has two m:1 relations is an intermediate entity for the m:n relation between the two '1' sides of these m;1 relations. Of course this is a little tricky as you don't know the collection names which you do know in the code generation approach (as you can generate that into your datastructure you build in the generated code simple_smile ).

Would this work for you?

Frans Bouma | Lead developer LLBLGen Pro
Auxon
User
Posts: 6
Joined: 28-Nov-2008
# Posted on: 16-Apr-2009 15:23:16   

Otis wrote:

... Would this work for you?

I am sure that I can get it to work via code generation templates. I will give it a try as soon as I can.

Thanks,

Richard