Many-to-many with a unique constraint and a conditional update

303 views Asked by At

I have a design similar to this one (although a teeny-weeny bit more complicated):

MySQL layout design

This is a many-to-many relationship. A scan can own many located pages, but a located page might belong to many scans.

The trouble I have is I don't know how to add a unique constraint on URL, so that when a page from a scan is being saved and an entry with such a URL already exists, it should not create a new one. Instead, the new scan_locatedpage entry should link to the already existing locatedpage and (if different) update its checksum.

Only, I'm not sure how to do that.

Currently the code I have looks this way:

public LocatedPageMap()
{
    Id(x => x.Id);
    Map(x => x.Url)
        .Not.Nullable();
    Map(x => x.Checksum);
    HasManyToMany(x => x.ScansFoundWith)
        .Cascade.All()
        .Inverse()
        .Table("Scan_LocatedPage");
}

public ScanMap()
{
    Id(x => x.Id);
    Map(x => x.Date)
        .Not.Nullable();
    HasManyToMany(x => x.Located_Pages)
        .Cascade.SaveUpdate()
        .Table("Scan_LocatedPage");
}

Of course, this doesn't work. Every time a new Scan is being saved, a new LocatedPage gets added, despite the Url.

I have no idea how to achieve that effect and I'm not really sure where/how to look. I have read a bunch of many-to-many questions with "unique" in title, but haven't found anything useful. Surely 'cos I'm not sure what to look for exactly.

EDIT

Or maybe I should simply add the condition handler into my application logic, and not expect Fluent to take care of that for me?

1

There are 1 answers

2
Radim Köhler On BEST ANSWER

This kind of a problem belongs to upper layer than mapping. What you can/should do is to search for existing object LocatedPage with the provided url. If found - use it, if not - create new. We are doing that stuff on upper layers (Service, Business) during the incoming data binding.

There are few points I wanted you to be aware of:

1) Chapter 24. Best Practices says:

Good usecases for a real many-to-many associations are rare. Most of the time you need additional information stored in the "link table". In this case, it is much better to use two one-to-many associations to an intermediate link class. In fact, we think that most associations are one-to-many and many-to-one, you should be careful when using any other association style and ask yourself if it is really neccessary.

Please, think about it. We are not using many-to-many at all. Having the middle object as a full entity could bring lot of advantages (e.g. searching based on subqueries)

2) Cascade

Be careful when using the Cascade on many-to-many. This is not a setting for a middle/pairing table. It is setting for the second-end Entity. So Once your LocatedPageMap is deleted all the ScansFoundWith items (Scan) will be deleted as well. Usually ... cascade is not what you want to set on many-to-many. The pairing table is "cascaded always"