2

I've already posted this in the Fluent NH group a long time ago, but didn't get any answers until today. SO, here's the problem: I've got a one-to-many relationship defined and the one side has the inverse flag set. the mapping code looks something like this:

public class MapeamentoReceita : ClassMap<Receita> {
    public MapeamentoReceita() {
        Table("Receitas");
        Not.LazyLoad();

        Id(rec => rec.Id, "IdReceita")
            .GeneratedBy
            .HiLo("TabelaHilo", "ProximoHi", "1000", "Tabela='receitas'")
            .Default(0);
        Version(rec => rec.Versao);

        //other props go here
        HasMany(rec => rec.Imagens)
            .Access.CamelCaseField((Prefix.Underscore))
            .AsBag()
            .Cascade.All()
            .KeyColumn("IdReceita")
            .Not.LazyLoad()
            .Inverse();
    }
}

Now, Imagem's mapping looks like this:

 public class MapeamentoImagem : ClassMap<Imagem> {
    public MapeamentoImagem() {
        Table("Imagens");
        Not.LazyLoad();
        Id(img => img.Id, "IdImagem")
            .GeneratedBy
            .HiLo("TabelaHiLo", "ProximoHi", "1000", "Tabela='imagens'")
            .Default(0);
        Map(img => img.Bytes)
            .CustomSqlType("image")
            .CustomType<Byte[]>()
            .LazyLoad()
            .Length(2000000000)
            .Not.Nullable()
            .Not.Update();

        References(img => img.Receita)
            .Column("IdReceita")
            .Cascade.None();
    }
}

And here's the code that tests the persistence of these classes:

 new PersistenceSpecification<Receita>(sess)
    .CheckList(rec => rec.Imagens, 
               _imagens, 
               (receita, imagem) => receita.AdicionaImagem(imagem))
    .VerifyTheMappings();

Even though Inverse is "on", PersistenceSpecification tries to insert Imagem before inserting Receita. Since IdReceita is foreign key configured not to accept null, I end up with an exception. I've tried writing "real world code" that uses receita and it works (I've turned on SQL and I can see that in this case, Receita is inserted before Imagem as it should be).

Since nobody answered this question on FH group, I was wondering if someone can please confirm that this PersistenceSpecification behavior is a bug.

thanks.

Luis Abreu
  • 4,008
  • 9
  • 34
  • 63
  • Can you post the code from your test that populates the `_imagens` field? And also the code for `Receita.AdicionalImagem(Imagem)`? Thanks. – Daniel Schilling Sep 29 '11 at 18:54

4 Answers4

3

Have you tried it this way?:

var receita = BuildMeAReceita();
var imagems = BuildSomeImagems();
foreach(var imagem in imagems){
    receita.AdicionaImagem(imagem);
}
new PersistenceSpecification<Receita>(sess)
.VerifyTheMappings(receita);
Mark Perry
  • 1,705
  • 10
  • 12
  • 1
    This is not testing anything, it only gets persistent and loaded by PersistenceSpecification, but no properties are checked because no CheckProperty or CheckList is called? – adriaanp Jul 31 '12 at 05:35
0

Try:

    References(img => img.Receita)
        .Column("IdReceita")
        .Not.Nullable();

My guess is that your real world code saves Recieta first so that the inserts are issued in the correct order. If you changed that code to save Imagem first you would get the same error because NHibernate would attempt to insert Imagem then update it with the foreign key.

Jamie Ide
  • 48,427
  • 16
  • 81
  • 117
  • Hello Jamie. Tried it and it doesn't work (end up with the non-null property references a null or transient value Receita). Does this mean that the PersistenceSpecification isn't really a good way to test the mappings??? – Luis Abreu Sep 27 '11 at 22:27
0

You could change your mapping of Imagem

References(img => img.Receita)
            .Column("IdReceita")
            .Cascade.SaveUpdate();

This will persist Receita before Imagem

What I've found is that PersistanceSpecification works well for fairly straight forward relationships but if you have complex object graphs you have to have cascading turned on all of your objects. Your scenario is fairly simple though so this small change should allow you to test this using PersistanceSpecification.

Edit:

Also make sure in your AdicionaImagem function you are setting the parent of the image. Here is an example:

public virtual void AdicionaImagem(Imagem newImagem)
{
    newImagem.Receita = this;
    imagems.Add(newImagem);
}
Cole W
  • 15,123
  • 6
  • 51
  • 85
  • Hello Cole. Done it, but it still inserts imagem before it inserts Receita. I believe that the problem lies in the way PersistenceSpecification runs the insertion (since I have SQL tracing on, it's easy to see that using it in my app ends up doing the correct thing, ie, Receita is inserted before each Imagem instance). – Luis Abreu Sep 30 '11 at 13:00
  • What version of Fluent NH are you using? If you change the mapping to the above and try to save an `Imagen` object (outside of PersistanceSpecification) does it create the `Receita`. `session.Save(testImagen);` – Cole W Sep 30 '11 at 13:34
  • Also in your `AdicionaImagem` function are you setting the parent reference in here? Please take a look at my edit for an example. – Cole W Sep 30 '11 at 13:41
  • Hello again Cole. Yes, when I open a transaction and save a Receita, it will first insert Receita and only then will it insert the Imagem instances. Oh and yes, I'm doing that: public void AdicionaImagem(Imagem imagem) { Contract.Requires(imagem != null); Contract.Requires(imagem.Receita == null); imagem.Receita = this; _imagens.Add(imagem); } – Luis Abreu Sep 30 '11 at 14:09
  • @LuisAbreu, The first part is not exactly what I asked. If you change your mapping to the mapping I have in my answer can you save an `Imagem` object first, **NOT** a `Receita` object. An example would be: `session.Save(testImagen);`. Does it save the Receita object first in this case? It should if cascading is turned on like in my mapping. – Cole W Sep 30 '11 at 14:19
  • Hello again Cole. well, I can do that, but that is not the behavior I'm after (Receita is the entity root of my aggregate). And as I said, using it in the real app does work...so, there's probably something wrong with PersitenceSpecification, though I still didn't managed to get the time to check it out... – Luis Abreu Oct 01 '11 at 20:29
  • Well by inserting an `Imagen` first this is essentially what `PersistanceSpecification` is doing. So if cascading is turned on it should insert your `Receita` object first when you try to do this. This is the point I was getting at. – Cole W Oct 03 '11 at 11:56
  • I understand what you're saying but that is not what I want. THe thing is that those mappings should perform the cascading from Receita to Imagem. I'm not sure if I've explained myself correctly before. The mappings work when used in production code (ie, sinve I've turned on sql logging, I can see that opening a session, starting a transaction and passing it a Receita will, in fact, save Receita and then insert Imagem). The problem only happens with PersistenceSpecification and I believe I'll need to dig deeper into what it's doing... – Luis Abreu Oct 13 '11 at 08:59
0

There are two possible issues in your code.

  1. Imagem.Bytes property is lazy-loaded but lazy loading for Imagem istself is disabled. It means no proxy is generated for Imagem instances and Bytes cannot be lazy-loaded: mission impossible (at least it appears to me right now, not sure). Simple load from database (session.Get<Imagem>(id);) leads to exceptions like this: Invalid Cast (check your mapping for property type mismatches); setter of Imagem. Solution is to enable lazy-loading on Imagem or disable lazy loading on Bytes property.
  2. PersistenceSpecification loads tested entity from database as another instance. It means that default equality comparer does not work well. You need to provide your own equality comparer to solve this issue (check this for more details).

Here is Receita and Imagem entities, implementation of IEqualityComparer and PersistentSpecification snippet. This code works well in NH3.1 and FNH 1.2. Please, let me know if your code differs somehow from these snippets.

Jakub Linhart
  • 4,062
  • 1
  • 26
  • 42
  • Hello Jakub. Imagem.Bytes being lazy loaded, the idea is simple: load only the image bytes when someone asks for them. I'm under the assumption that I've tested this in the past with entities which were not lazy loaded from the database and the results were the same (regarding Fluent NH testing). Regarding 2, I'm reusing Sharp base classes and I think they work pretty well. – Luis Abreu Oct 05 '11 at 15:44
  • Ok, that are only my observation and I cannot explain them. The provided code fragments just work - good old cargocult:). Bytes issue is really suspicious and it deserves further investigation. SSharp implementation of equality is controversial in my eyes due to GetTypeSpecificSignatureProperties and related stuff. Btw. your implementations of this abstract method could give some clue. Anyway please, let know if you find any answer. – Jakub Linhart Oct 05 '11 at 18:37
  • btw, the problem is not really in the equality checker...i can't even insert Imagem in the database during testing due to fk violations... – Luis Abreu Oct 13 '11 at 09:02