简体   繁体   中英

In a many to many relationship ensure that one entity is only tied to another entity

This is a bit of a convoluted question, but I can't quite get the logic right in my head.

I have three tables TableA , TableB , and TableABBridge . I need to retrieve all the values from TableABBridge in which records from TableB are only tied to a single record from TableA .

Essentially, I have a tool that allows users to edit a specific property on values in TableA . However, TableB also has this same property and it needs to be kept synchronized with the property from entities in TableA . So if I update the value in TableA , all of the values in TableB linked to the specific entity need to be updated, but ONLY if the entities in TableB are linked to ONLY ONE entity from TableA .

So for example

TableA

|   ID   |   Prop   |
|-------------------|
|       1|         1|
|       2|         3|
|       3|         3|
|-------------------|

TableB

|   ID   |   Prop   |
|-------------------|
|       1|         1|
|       2|         1|
|       3|         3|
|       4|         1|
|-------------------|

TableABBridge

|   AId   |   BId   |   
|-------------------|
|        1|        1|
|        1|        2|
|        1|        4|
|        2|        3|
|        3|        3|
|-------------------|

In the above example, I would be able to edit the property in TableA for ID 1 only as the values for TableB for ID 1, 2, and 4 are ONLY tied to ID 1 from TableA . However, I will not be able to edit the property for for ID 2 or 3 because they share a common entity from TableB .

So essentially, using Linq, I need to figure out how to get a list of TableA IDs that I am allowed to edit by querying TableABBridge .

The SQL to accomplish this is:

SELECT MAX(AId), BId 
FROM TableABBridge
GROUP BY BId
HAVING Count(AId) = 1

and the reverse (also needed)

SELECT a.Id
FROM    TableABBridge a
JOIN 
    (SELECT BId 
    FROM TableABBridge
    GROUP BY BId
    HAVING Count(AId) > 1) b ON b.BId = a.BId 

I believe one possible solution would be:

var bridges = context.TableABBridge.Select(n => new
{
    n.AId,
    n.BId
}).Distinct()
.GroupBy(n => n.BId, n => n.AId).Where(n => n.Count() > 1)
.Select(n=>n.Key);

var uneditable = context.TableABBridge.Where(n => bridges.Contains(n.BId)).Select(n => n.AId);

var editable = context.TableABBridge.Select(n => new
{
    n.AId,
    n.BId
}).Distinct()
.GroupBy(n => n.BId, n => n.AId).Where(n => n.Count() == 1)
.Select(n => n.FirstOrDefault().Value);

A LINQ-ish way to do it is this:

var diffs = from b in context.Bs
            where b.ABs.Count() == 1
            let a = b.ABs.FistOrDefault().A
            where a.Prop != b.Prop
            select new { b, ValueA = a.Prop };

foreach(var diff in diffs)
{
    diff.b.Prop = diff.ValueA;
}

context.Bs where b.ABs.Count() == 1 gives you all Bs having 1 junction record and hence one A . Then a structure is created in which the B and the value of A are stored side by side, so it's easy to do the update in a loop.

I said "LINQ-ish", because sometimes the natural way to express things in LINQ is different than what's more obvious in SQL ( b.ABs.Count() vs. GROUP BY ). It will generate a subquery for the counts, maybe making it less efficient than grouping. But it should also be kept in mind that LINQ group by isn't always translated into SQL GROUP BY .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM