2013-01-20 21 views
8

Wybrałem ServiceStack OrmLite dla mojego projektu, który jest czystą aplikacją zorientowaną na dane. Jestem skłonny pozwolić użytkownikowi końcowemu stworzyć własne typy obiektów zdefiniowane w formacie XML, które będą używane do generowania klas w czasie wykonywania przy użyciu CodeDOM.Dodaj właściwość do klasy POCO w czasie wykonywania

Będę również definiował niektóre obiekty "systemowe" wymagane przez aplikację (tj. User), ale nie mogę przewidzieć wszystkich właściwości, których użyje końcowy użytkownik, dlatego szukam sposobu na umożliwienie rozszerzenia klas, które utworzę w czas projektowania. Próbka poniżej użytkownik

public class User 
{ 
    public Guid Uid { get; set; } 
    public String Username { get; set; } 
    public String Password { get; set; } 
} 

Koniec chce mieć Email oraz Address. Powinien móc dodać 2 właściwości do klasy wyższej i całej klasy będą (które nadal mogą być wykorzystywane przez OrmLite, ponieważ pozwala na nadpisanie:

public class User 
{ 
    public Guid Uid { get; set; } 
    public String Username { get; set; } 
    public String Password { get; set; } 
    public String Email{ get; set; } 
    public String Address { get; set; } 
} 

wiem, że może istnieć ryzyko robi tak aby zawiesić system (jeśli klasa jest już utworzona), więc szukam najlepszego sposobu na uniknięcie tego problemu i naśladuję potrzebuję.

Odpowiedz

4

Wygląda na to, że są dwie części tego, co tu robisz. Musisz tworzyć typy dynamicznie, aby obsługiwać dodatkowe właściwości. Musisz również upewnić się, że nigdy nie skończysz z duplikatami typów w AppDomain, tj. Dwiema różnymi definicjami User.

typu Runtime generacji

Różnorodne propozycje już podane uchwyt jak tworzyć typy. W jednym projekcie mieliśmy coś podobnego. Stworzyliśmy klasę bazową, która miała podstawowe właściwości i słownik do przechowywania właściwości "rozszerzenia". Następnie użyliśmy Reflection.Emit, aby utworzyć typ wyprowadzony o pożądanych właściwościach. Każda definicja właściwości jest po prostu odczytywana ze słownika lub zapisywana w słowniku w klasie bazowej. Ponieważ Reflection.Emit pociąga za sobą pisanie niskiego poziomu kodu IL, początkowo wydaje się skomplikowany. Napisaliśmy próbki klas pochodnych w innej bibliotece klas i skompilowaliśmy je.Są to przykłady tego, co powinniśmy osiągnąć w czasie wykonywania. Następnie użyliśmy programu ildasm.exe, aby zobaczyć, jaki kod wygenerował kompilator. To sprawiło, że bardzo łatwo było ustalić, w jaki sposób moglibyśmy wygenerować ten sam kod w czasie wykonywania.

Unikanie kolizji namespace

Twój Drugim wyzwaniem jest, aby uniknąć zduplikowane nazwy typu. Do nazwy każdego wygenerowanego typu dołączyliśmy GUID (z usuniętymi nieprawidłowymi znakami), aby upewnić się, że tak się nie stało. Łatwa naprawa, chociaż nie wiem, czy uda ci się uciec dzięki ORM.

Jeśli jest to kod serwera, należy również wziąć pod uwagę fakt, że zespoły nie są nigdy rozładowywane w .NET. Jeśli więc wielokrotnie tworzysz nowe typy w czasie wykonywania, twój proces będzie nadal rosnąć. To samo stanie się w kodzie klienta, ale może to być mniejszy problem, jeśli nie oczekujesz, że proces będzie działał przez dłuższy czas.

Powiedziałem, że zespoły nie są rozładowywane; można jednak rozładować cały plik AppDomain. Jeśli więc jest to kod serwera, możesz uruchomić całą operację we własnej domenie aplikacji, a następnie ją odłożyć, aby upewnić się, że dynamicznie tworzone typy są rozładowywane.

+0

To jest bardzo zbliżone do rozwiązania, które wymyśliłem. Stworzyłem klasy "Property" i "DataObjectType" o relacji N <->. Następnie użyłem generatora kodu CodeDOM do zbudowania nowego 'ExtensionAssembly', który utworzy nowe klasy przy użyciu właściwości rozszerzeń z powyższych tabel. Do każdego z oryginalnych typów użyłem klucza ExtensionKey jako guid, który jest używany przez odpowiednie nowe typy jako identyfikator. –

+0

Ucieczka od konfliktu nazw jest tak łatwa jak dodanie przedrostka lub przyrostka do nowo utworzonych typów, np. "Użytkownik" i "Rozszerzenie użytkownika" –

0

Dlaczego nie używać pary wartości klucza dla wszystkich jej właściwości lub najmniej dynamiczne?

http://msdn.microsoft.com/en-us/library/system.collections.hashtable.aspx

Możesz to zrobić tak, jak opisujesz za pomocą Reflection, ale to zajmie wydajność, w ten sposób pozwoli również usunąć właściwości.

+0

ponieważ OrmLite potrzebuje ich jako właściwości, o ile wiem. Htable będzie serializowany do tekstu –

+0

Może to pomóc .. http://msdn.microsoft.com/pl-pl/library/ms404245.aspx Można utworzyć klasę podstawową, a następnie użyć jej do utworzenia klasy dynamicznej w środowisko wykonawcze. Lub zimne użycie Reflection.Emit –

0

Projekt, nad którym obecnie pracuję, ma podobny wymóg. Mamy system już w produkcji i mieliśmy pola do dodawania żądań klienta.

Rozwiązaliśmy ten problem, dodając właściwość CustomFields do naszego modelu.

public class Model: IHasId<Guid> 
{ 
    [PrimaryKey] 
    [Index(Unique = true)] 
    public Guid Id { get; set; } 

    // Other Fields... 

    /// <summary> 
    /// A store of extra fields not required by the data model. 
    /// </summary> 
    public Dictionary<string, object> CustomFields { get; set; } 
} 

Używamy tego przez kilka tygodni bez żadnych problemów.

Dodatkową korzyścią, jaką znaleźliśmy, było to, że każdy rząd mógł mieć własne pola niestandardowe, abyśmy mogli obsłużyć je na podstawie każdego rekordu, zamiast wymagać ich dla każdego rekordu.

+0

Co się stanie, jeśli masz za dużo pól i wartość, że wartość blobbed przekroczy domyślny limit 8000 znaków? –

+0

Prawidłowy punkt, faktycznie ograniczamy bardziej złożone dane do oddzielnej tabeli, do której odwołujemy się z wartości id na liście CustomField. Nie oczekujemy, że nasi klienci będą wymagać dużej liczby pól niestandardowych, ale masz rację, że nie ma limitu na polu, które należy wziąć pod uwagę. –

Powiązane problemy