Obtendo proxies do tipo correto em NHibernate
-
03-07-2019 - |
Pergunta
Eu tenho um problema com não inicializado proxies em nhibernate
O modelo de domínio
Vamos dizer que tenho duas hierarquias de classe paralelas: animal, cão, gato e AnimalOwner, dogowner, catowner onde o cão e gato ambos herdam Animal e dogowner e catowner ambos herdam AnimalOwner. AnimalOwner tem uma referência do tipo animal chamado OwnedAnimal.
Aqui estão as classes no exemplo:
public abstract class Animal
{
// some properties
}
public class Dog : Animal
{
// some more properties
}
public class Cat : Animal
{
// some more properties
}
public class AnimalOwner
{
public virtual Animal OwnedAnimal {get;set;}
// more properties...
}
public class DogOwner : AnimalOwner
{
// even more properties
}
public class CatOwner : AnimalOwner
{
// even more properties
}
As classes têm mapeamento nhibernate adequada, todas as propriedades são persistentes e tudo o que pode ser preguiçoso carregado é preguiçoso carregado.
A lógica de negócios do aplicativo só permitem que você defina um cão em um dogowner e um gato em um catowner.
O Problema
Eu tenho um código como este:
public void ProcessDogOwner(DogOwner owner)
{
Dog dog = (Dog)owner.OwnedAnimal;
....
}
Este método pode ser chamado por muitos métodos de diffrent, na maioria dos casos, o cão já está na memória e tudo está ok, mas raramente o cão não é já na memória - neste caso, eu recebo um nhibernate "procuração não inicializado", mas o elenco lança uma exceção porque genrates nhibernate um proxy para animal e não para cão.
Eu entendo que isso é como funciona o nhibernate, mas eu preciso saber o tipo sem carregar o objeto -. Ou, mais corretamente Eu preciso o proxy não inicializada para ser um proxy de gato ou cachorro e não um proxy de animal
Restrições
- Eu não posso mudar o modelo de domínio, o modelo é entregue a mim por um outro departamento, eu tentei levá-los a mudar o modelo e falhou.
- O modelo actual é muito mais complicado, em seguida, o exemplo e as classes têm muitas referências entre eles, usando o carregamento ansioso ou adicionar se junta às consultas está fora de questão por razões de desempenho.
- Eu tenho total controle do código-fonte, o mapeamento hbm e o esquema de banco de dados e eu posso mudá-los de qualquer maneira que eu quero (contanto que eu não mudar as relações entre as classes do modelo).
- Eu tenho muitos métodos como a do exemplo e eu não quero modificar todos eles.
Obrigado,
Nir
Solução
É mais fácil para desligar o carregamento lento para a classe animal. Você diz que é principalmente na memória de qualquer maneira.
<class name="Animal" lazy="false">
<!-- ... -->
</class>
Como uma variante disso, você também pode usar no-proxy
, consulte este post :
<property name="OwnedAnimal" lazy="no-proxy"/>
Tanto quanto eu posso ver, ele só funciona quando o AnimalOwner
é realmente um proxy.
ou
Você pode usar os genéricos sobre o proprietário animal para fazer a referência uma classe concreta.
class AnimalOwner<TAnimal>
{
virtual TAnimal OwnedAnimal {get;set;}
}
class CatOwner : AnimalOwner<Cat>
{
}
class DogOwner : AnimalOwner<Dog>
{
}
ou
Você pode mapear o DogOwners
e CatOwners
em mesas separadas, e definir o tipo concreto de animais no mapeamento.
<class name="CatOwner">
<!-- ... -->
<property name="OwnedAninal" class="Cat"/>
</class>
<class name="DogOwner">
<!-- ... -->
<property name="OwnedAninal" class="Dog"/>
</class>
ou
Você confusão um pouco por aí com NHibernate, como proposto na este blogue . NH é realmente capaz de retornar o objeto real por trás do proxy. Aqui um pouco implementação mais simples, como proposto lá:
public static T CastEntity<T>(this object entity) where T: class
{
var proxy = entity as INHibernateProxy;
if (proxy != null)
{
return proxy.HibernateLazyInitializer.GetImplementation() as T;
}
else
{
return entity as T;
}
}
que pode ser usado como este:
Dog dog = dogOwner.OwnedAnimal.CastEntit<Dog>();
Outras dicas
Eu acho que recentemente teve um problema semelhante, solução AFAIR era dar 'Animal' a auto - "método / propriedade":
public Animal Self { get { return this; } }
Este poderia então ser escalado para "animal" correta. O que acontece é que o seu objeto original tem uma referência ao objeto proxy nhibernate (quando é preguiçosamente carregado), que atua como animal para todos os métodos expostos via classe Animal (ele passa todas as chamadas para o objeto carregado). No entanto, não pode ser escalado como qualquer um de seus outros animais porque não é nada disso, só emula a classe Animal. No entanto, a classe que é encapsulado por AnimalProxy pode ser escalado como animais subclasse porque é um exemplo real da classe correta, você só precisa chegar a ele de referência this
.
Você pode tentar colocar este método em sua entidade de base:
public virtual T As<T>() where T : Entity {
return this as T;
}
Você pode querer tentar isso para ver o tipo de proxy (assumindo NH 2.0 +):
((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass
Mas este tipo de vazamento ou "Tipo de espreitar" é muito má prática de qualquer maneira ...
Se temos vindo a trabalhar com o mesmo problema, a questão é que o proxy gerado é o proxy do animal e não de cão.
A solução que usamos foi para recarregar o objeto:
Dog dog = this.CurrentSession.Load<Dog>(owner.OwnedAnimal.AnimalID);
Isso remonta à sua sessão e recarrega o objeto com o tipo correto.
Espero que isso ajude
Se você usar Fluent NHibernate, você pode usar uma substituição de mapeamento automático para desligar o carregamento lento por apenas que a propriedade:
public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
{
public void Override( AutoMapping<DogOwner> mapping )
{
mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
}
}