Klasy dziedziczące po interfejsach - mapowanie FLuent NH

0

Hej,

mam pewien projekt, w którym część klas dziedziczy po interfejsach... przykładowo:

public interface IProfil 
{
  int Id {get; set;}
  DateTime DateCreated {get; set;}
}

public class ProfilBiznesowy: IProfil
{
  string Nazwa {get; set;}
  string JakisTekst {get; set;}
}

public class ProfilPrywatny: IProfil
{
  string Nazwa {get; set;}
}

Do tego zdarza się lista obiektów interfejsu:

public interface ILike
{
   int Id {get; set;}
   Photo Photo {get; set;}
}

public class Photo: IPhoto
{
   int Id {get; set;}
   IList<ILike> PhotoLikes {get; set;}
}

No i problem mam taki że nie wiem jak powinienem napisać mappingi tego :/ Możecie mi pomóc?

5

a ja tego nie chce mapować

Jeśli chcesz, żeby móc tam wrzucić wszystkie obiekty klas implementujących ten interfejs no to tak się nie da.

Problem wygląda tak, że NHibernate musi koniec końców stworzyć te tabelki w bazie. Jeśli w klasie A masz kolekcję obiektów klasy B, to w tabeli dla klasy B musi być coś takiego jak id klasy A, np mając klasy:

  public class Post
  {
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Content { get; set; }

    public virtual ISet<Comment> Comments { get; set; }

    public Post()
    {
      Comments = new HashSet<Comment>();
    }
  }

  public class Comment
  {
    public virtual int Id { get; set; }
    public virtual string Content { get; set; }
  }

będzie coś takiego ostatecznym produktem:
215bf27bc9.png

i przez to, że tam musi być jakaś tabelka z tym id innej encji leżymy przy takim podejściu.

Na szczęście jest kilka innych:

  1. Potrzebujemy tam wrzucać obiekty tylko jednej konkretnej klasy, dla której mamy mapowania:
    Kod modeli:
  public class Post
  {
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Content { get; set; }

    public virtual ISet<ISomethingWithContent> Comments { get; set; } // Kolekcja z interfejsem!

    public Post()
    {
      Comments = new HashSet<ISomethingWithContent>();
    }
  }

  public interface ISomethingWithContent
  {
    string Content { get; set; }
  }

  public class Comment : ISomethingWithContent
  {
    public virtual int Id { get; set; }
    public virtual string Content { get; set; }
  }

Kod mapowań.

  public class PostMappings : ClassMap<Post>
  {
    public PostMappings()
    {
      Id(x => x.Id).GeneratedBy.Native();

      Map(x => x.Title);
      Map(x => x.Content);

      HasMany<Comment>(x => x.Comments).Cascade.AllDeleteOrphan(); // Jawnie podajemy parametr generyczny na typ konkretnej klasy!!!
    }
  }

  public class CommentMappings : ClassMap<Comment>
  {
    public CommentMappings()
    {
      Id(x => x.Id).GeneratedBy.Native();

      Map(x => x.Content);
    }
  }

I generalnie rozwiązanie powyżej ssie, bo po cholerę nam w takim przypadku interfejs.

Jest też inna opcja, że definiujemy klasę zamiast interfejsu i ją mapujemy na normalną tabelę. Później definiujemy klasy pochodne i już używamy SubclassMap<> zamiast ClassMap<>, przykład:

public class Post
  {
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Content { get; set; }

    public virtual ISet<Content> Contents { get; set; }

    public Post()
    {
      Contents = new HashSet<Content>();
    }
  }

  public class Content
  {
    public virtual int Id { get; set; }
    public virtual string SomeContent { get; set; }
  }

  public class Comment : Content
  {
    public virtual bool IsApproved { get; set; }
  }

  public class NotComment : Content
  {
    public virtual DateTime DateTime { get; set; }
  }

i mapowania:

public class PostMappings : ClassMap<Post>
  {
    public PostMappings()
    {
      Id(x => x.Id).GeneratedBy.Native();

      Map(x => x.Title);
      Map(x => x.Content);

      HasMany(x => x.Contents).Cascade.AllDeleteOrphan();
    }
  }

  public class ContentMappings : ClassMap<Content>
  {
    public ContentMappings()
    {
      Id(x => x.Id).GeneratedBy.Native();

      Map(x => x.SomeContent);
    }
  }

  public class CommentMappings : SubclassMap<Comment>
  {
    public CommentMappings()
    {
      Map(x => x.IsApproved);
    }
  }

  public class NotCommentMappings : SubclassMap<NotComment>
  {
    public NotCommentMappings()
    {
      Map(x => x.DateTime);
    }
  }

I teraz diagram się prezentuje tak:
86fa550848.png
I NHibernate generalnie umie to obsługiwać.

Możesz tutaj poczytać więcej:
http://nhibernate.info/doc/nh/en/#inheritance
http://www.codeproject.com/Articles/232034/Inheritance-mapping-strategies-in-Fluent-Nhibernat

Mam nadzieję, że o to Ci mniej-więcej chodzi. :P

Ofc. Jeśli już będziesz skłonny napisać mapowanie dla tego interfejsu, to on jak najbardziej pasuje (nie musi być klasa, bo z tego co widzę w tych linkach wyżej o tym nie piszą).

Przykład:

public class Post
  {
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Content { get; set; }

    public virtual ISet<IContent> Contents { get; set; }

    public Post()
    {
      Contents = new HashSet<IContent>();
    }
  }

  public interface IContent
  {
    int Id { get; set; }
    string SomeContent { get; set; }
  }

  public class Comment : IContent
  {
    public virtual int Id { get; set; }
    public virtual string SomeContent { get; set; }
    public virtual bool IsApproved { get; set; }
  }

  public class NotComment : IContent
  {
    public virtual int Id { get; set; }
    public virtual string SomeContent { get; set; }
    public virtual DateTime DateTime { get; set; }
  }


  public class PostMappings : ClassMap<Post>
  {
    public PostMappings()
    {
      Id(x => x.Id).GeneratedBy.Native();

      Map(x => x.Title);
      Map(x => x.Content);

      HasMany(x => x.Contents).Cascade.AllDeleteOrphan();
    }
  }

  public class ContentMappings : ClassMap<IContent>
  {
    public ContentMappings()
    {
      UseUnionSubclassForInheritanceMapping();

      Id(x => x.Id).GeneratedBy.Increment();

      Map(x => x.SomeContent);
    }
  }

  public class CommentMappings : SubclassMap<Comment>
  {
    public CommentMappings()
    {
      Map(x => x.IsApproved);
    }
  }

  public class NotCommentMappings : SubclassMap<NotComment>
  {
    public NotCommentMappings()
    {
      Map(x => x.DateTime);
    }
  }

59362e84f8.png

0

Super! :)
Ale teraz wyższa szkoła jazdy...:


    public interface IProfile : IBaseClass, ICanLike, ICanComment
    {
        AccountStatus AccountStatus { get; set; }

        IList<IAlbum> Albums { get; set; }
        IList<IPost<IProfile>> Posts { get; set; }
        IList<ILike<IProfile>> Likes { get; set; }
        //is watched
    }
public class UserProfile : IProfile
    {
        public virtual UserDetails UserDetails { get; set; }
        public virtual Guid Id { get; set; }
        public virtual IProfile AddedBy { get; set; }
        public virtual DateTime AddedDate { get; set; }
        public virtual IProfile ModifiedBy { get; set; }
        public virtual DateTime ModifiedDate { get; set; }
        public virtual AccountStatus AccountStatus { get; set; }
        public virtual IList<IAlbum> Albums { get; set; }
        public virtual IList<IPost<IProfile>> Posts { get; set; }
        public virtual IList<ILike<IProfile>> Likes { get; set; }
    }
  public interface ILike<T> : IBaseClass
    {
        LikeCategory LikeCategory { get; }
        T Recipient { get; set; }
    }
 public interface IPost<T> : IBaseClass, ICanLike, ICanComment
    {
        T Recipient { get; set; }
        string Content { get; set; }
        IList<LikePost> Likes { get; set; }
        IList<IComment<IPost<IProfile>, LikeComment>> Comments { get; set; } 
    }

Jak coś takiego zmapować? Mógłbyś mi pomóc?

1

Tu masz przykład w XMLu jak można zamapować N specjalizacji typu generycznego: http://ayende.com/blog/2951/nhibernate-and-generic-entities
Jeśli natomiast chcesz mapować otwarty typ generyczny, to się nie da po prostu.

Resztę się mapuje tak samo jak w poprzednich przykładach.

Anyway.. Czemu masz tak skomplikowane coś co leci do bazy? Może zamiast wymyślać jak zapisać dziwne rzeczy do bazy, zastanowić się czy nie potrzebujemy przeprojektować model czy dodać nową warstwę abstrakcji?

4

Zdarza mi się używać NHibernate od jakichś 6 lat i jeszcze nigdy nie mapowałem interfejsów... Bo i po co? Mapuje się rzeczy, które da się zapisać do bazy, czyli takie, które zawierają dane. A interfejsy to tylko zachowania... Nie mają danych, więc skąd biedny mapper ma wiedzieć co zmapować?

Nie ogarniam tego kompletnie, wygląda mi to na jakiś overenginerring powstały pod wpływem ideologii "każda klasa musi mieć interfejs, bo tak jest bardziej obiektowo". Może masz jeszcze pary interfejs-klasa zarejestrowane w kontenerze IoC?

0

Panowie, dziękuję serdecznie za bardzo sensowne odpowiedzi - jak zwykle mogłem na Was liczyć! :)
Przemyślałem całą sprawę i "schodzę" z tej abstrakcji w dół do normalnych dziedziczeń :)
Dzięki jeszcze raz!

1 użytkowników online, w tym zalogowanych: 0, gości: 1