NHibernate narrowing proxy warning

匿名 (未验证) 提交于 2019-12-03 08:46:08

问题:

We are building an ASP.NET MVC application utilizing NH for data access. Using NH Profiler I see a lot of warnings like "WARN: Narrowing proxy to Domain.CaseTask - this operation breaks ==". I get these very often when executing queries for classes which are mapped in a table per subclass, for example, using the NH Linq provider:

Query<ICaseTask>().Where(c => c.Assignee == Of || c.Operator == Of) 

where the class CaseTask inherits from Task, triggers the warning.

Information about the warning in the internet is scarce and mostly hints that this is something to be ignored... What does this warning warn about exactly? Should this be something I should seek to correct?

回答1:

This warning is about classes having properties or fields that are a subclass. IE:

public class Animal {     public int Id {get;set;} }  public class Cat : Animal {     public int Weight {get;set;} }  public class Person {     public Cat Pet {get;set;} } 

NHibernate gets upset when it loads the person entity because it doesn't want to cast for you because behavior becomes unpredictable. Unless you tell NHibernate how to deal with Equals (among other logic) it won't know how to do that comparison on its own.

The basic idea to correct this is to let NHibernate put the base class object into the graph and then you deal with the casting (note that this setup would use some slightly different mappings - ive done this to simplify the code but it can obviously be done by keeping the properties as full getters/setters):

public class Animal     {         public int Id {get;set;}     }  public class Cat : Animal {     public int Weight {get;set;} }  public class Person {     private Animal _pet;     public Cat Pet {         get{return _pet as Cat;}     } } 


回答2:

The reality is more complicated. When you load entity using either session.Load or you access a property that is lazy loaded NHibernate returns a proxy object. That proxy object will by hydrated (data will be loaded from DB) when you access any of its properties for the first time. To achieve this NHibernate generates proxy class that extends entity class and overrides all property getters and setters. This works perfectly when inheritance is not used since you will have no way to differentiate between proxy and entity class (proxy base class), e.g. simple test proxy is MyEntity will always work.

Now imagine that we have a Person entity:

class Person {   // lazy-loaded   public Animal Pet { get; set; } } 

And we also have Animal class hierarchy:

public abstract class Animal { ... } public class Cat { ... } public class Dog { ... } 

Now assume that Pet property is lazy loaded, when you ask NHibernate for person pet you will get a proxy object:

var pet = somePerson.Pet; // pet will be a proxy 

But since Pet is lazy loaded property NH will not know if it will be instance of a Cat or a Dog, so it will do its best and will create a proxy that extends Animal. The proxy will pass test for pet is Animal but will fail tests for either pet is Cat or pet is Dog.

Now assume that you will access some property of pet object, forcing NH to load data from DB. Now NH will know that your pet is e.g. a Cat but proxy is already generated and cannot be changed. This will force NHibernate to issue a warning that original proxy for pet that extends type Animal will be narrowed to type Cat. This means that from now on proxy object for animal with pet.Id that you create using session.Load<Animal>(pet.Id) will extend Cat from now. This also means that since Cat is now stored as a part of session, if we load a second person that shares cat with the first, NH will use already available Cat proxy instance to populate lazy-loaded property.

One of the consequences will be that object reference to pet will be different that reference obtained by session.Load<Animal>(pet.Id) (in object.ReferencesEqual sense).

// example - say parent and child share *the same* pet var pet = child.Pet; // NH will return proxy that extends Animal pet.DoStuff(); // NH loads data from DB  var parent = child.Parent; // lazy-loaded property var pet2 = parent.Pet; // NH will return proxy that extends Cat  Assert.NotSame(pet, pet2); 

Now when this may cause harm to you:

  1. When you put your entities into Sets or Dictionaryies in your code or if you use any other structure that requires Equals/GetHashCode pair to work. This can be easily fixed by providing custom Equals/GetHashCode implementation (see: http://www.onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html?page=1)

  2. When you try to cast your proxy object to target type e.g. (Cat)pet, but again there are know solutions (e.g. Getting proxies of the correct type in NHibernate)

So the moral is to avoid as much as possible inheritance in your domain model.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!