[.NET Entity Framework] Eigen base class gebruiken

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Hoi,

Ik ben vandaag begonnen om met wat het Entity Framework 4 te spelen (in combinatie met SQL Server Compact Edition 4 op een ASP.NET website), en hoewel alles prima werkt probeer ik nu een extra laagje abstractie in te bouwen.

In de websites die ik maak zorg ik er altijd voor dat alle tabellen in de database de volgende vier kolommen hebben:
  • Id (autonummer ofwel identity)
  • Deleted (bit/boolean die aangeeft of het record verwijderd is)
  • CreatedTime (datetime)
  • UpdatedTime (datetime)
Op deze manier kan ik gemakkelijk bijhouden (en later bekijken) wanneer records gemaakt danwel geupdate zijn, en door het Deleted bit te zetten hoef ik records nooit daadwerkelijk weg te gooien maar kan ik ze altijd terughalen (alle select queries geven alleen records terug waarbij Deleted false is uiteraard).

Als ik in het Entity Framework nu een tabel toevoeg, zeg Persons, dan wordt er een Person class gegenereerd met de properties van deze tabel (dus inclusief Id, Deleted, CreatedTime, UpdatedTime). Deze gegenereerde class inherit EntityObject.

Om een extra laagje abstractie in te bouwen zou ik graag hebben dat deze vier standaard property in een extra class 'EntityBase' oid (of gewoon Entity) komen, die van EntityObject overerft. In plaats van de relatie EntityObject <--- Person, wil ik dus graag EntityObject <-- Entity <-- Person, waarbij Person nu de Id, Deleted, etc properties overerft van Entity in plaats van ze zelf te definieren.

Hoe kan ik het entity framework nu vertellen dat ik graag een extra basis class gebruik tussen EntityObject en Person in?


Voor meer informatie (die niet strikt nodig is om mijn vraag te beantwoorden denk ik):

Ik heb nu generic EditPage en ViewPage classes gemaakt die overerven van System.Web.UI.Page. De EditPage pagina stelt een pagina voor waar een record (entity) gewijzigd ofwel aangemaakt wordt (stel maar even voor een pagina met een paar textboxjes om wat details op te geven en een OK/Cancel knop). De ViewPage pagina stelt een pagina voor met een grid die de records laat zien, en een knopje om een nieuw record toe te voegen, te wijzigen, of te verwijderen.

Deze classes zijn generic en hebben als type parameters onder andere het type entity dat ermee gewijzigd / bekeken wordt:
C#:
1
2
3
public class EditPage<TEntity, ...> : System.Web.UI.Page
    where TEntity : EntityObject
    where ...

Bijvoorbeeld, stel dat ik een ViewPage heb die personen laat zien (en de EditPage dus de details van een persoon, naam, telefoon, adres, etc), dan heeft deze als type parameter Person, waarbij Person de class is die ik van het Entity Framework model krijg (automatisch gegenereerd onder het edmx bestand).

Hoewel ik hier al vrij veel mee kan zoek ik toch nog een abstractie hoger: ik wil graag dat deze Person class overerft van mijn eigen 'Entity' class. In deze Entity class heb ik simpelweg de vier properties die ik hierboven genoemd heb (Id, Deleted, CreateTime, UpdatedTime) gedefinieerd.

Het voordeel hiervan is dat ik het merendeel van de New/Edit/Delete functies in de abstracte EditPage/ViewPage kan plaatsen zodat ik die code niet meer zelf hoef te schrijven. Het is toch steeds dezelfde code, enkel met een andere entity. Op het moment kan dit niet omdat de IDE niet "weet" dat elke entity die ik maak deze properties zal hebben (enkel dat het een EntityObject is). Wellicht dat ik Reflection zou kunnen gebruiken en er gewoon vanuit ga dat TEntity deze properties heeft, maar datis natuurlijk niet zo netjes.

Het probleem is nu dat ik de Person class van het entity framework krijg, en deze inherit EntityObject, dus moet mijn type parameter TEntity overerven van EntityObject, en die class heeft geen Id, Deleted, etc properties.

Hoe kan ik nu zorgen dat het Entity Framework als basis mijn Entity class gebruikt? Mijn Entity class zou dan uiteraard op zijn beurt van EntityObject kunnen overerven (anders werkt het natuurlijk niet meer).

Is dit uberhaupt mogelijk?

Bedankt!

[ Voor 20% gewijzigd door NickThissen op 10-01-2011 19:15 ]

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Jan_V
  • Registratie: Maart 2002
  • Laatst online: 21:13
Ooit heb ik een dergelijke vraag ook gesteld, weet niet meer of het hier was.
Een van de responses was dat je het OO-gedeelte niet in de database moet willen oplossen, maar dat je het meer op een OR manier moet implementeren.
Dus iets als:
ID
Name
EntityID

Dus een FK naar de Entity tabel. Op dat moment was het niet echt een antwoord waar ik op zat te wachten, maar heeft me wel aan het denken gezet. Op zich is dat, databasetechnisch, een mooiere oplossing.

Oplossing voor jouw vraag kan ook.
Je moet dan even op je edmx gaan staan en de tabel Person selecteren. In de properties heb je dan een optie 'Base Type'. Hier kun je een andere tabel kiezen, deze zal dan als base fungeren voor de tabel. Volgens mij is dat wat je wilt, mits je Entity natuurlijk ook een tabel is.

Battle.net - Jandev#2601 / XBOX: VriesDeJ


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Jan_V schreef op maandag 10 januari 2011 @ 19:46:
Ooit heb ik een dergelijke vraag ook gesteld, weet niet meer of het hier was.
Een van de responses was dat je het OO-gedeelte niet in de database moet willen oplossen, maar dat je het meer op een OR manier moet implementeren.
Dus iets als:
ID
Name
EntityID

Dus een FK naar de Entity tabel. Op dat moment was het niet echt een antwoord waar ik op zat te wachten, maar heeft me wel aan het denken gezet. Op zich is dat, databasetechnisch, een mooiere oplossing.
Kun je hier iets meer over uitwijden? Ik snap niet precies wat je bedoelt namelijk.
Jan_V schreef op maandag 10 januari 2011 @ 19:46:
Oplossing voor jouw vraag kan ook.
Je moet dan even op je edmx gaan staan en de tabel Person selecteren. In de properties heb je dan een optie 'Base Type'. Hier kun je een andere tabel kiezen, deze zal dan als base fungeren voor de tabel. Volgens mij is dat wat je wilt, mits je Entity natuurlijk ook een tabel is.
Dat had ik inderdaad al gevonden, maar er is daar een fundamenteel probleem mee. Je bedoelt dus dat ik een extra tabel 'BaseTable' ofzo maak, met mijn vier properties, en dat mijn 'Persons' table enkel nog verdere properties definieert, zoals Firstname en Lastname:
Afbeeldingslocatie: http://i53.tinypic.com/j5f1oh.jpg

Nu krijg ik in mijn code een class 'BaseTable' die overerft van EntityObject, en een class 'Person' die overerft van BaseTable. So far so good.
Afbeeldingslocatie: http://i56.tinypic.com/hrihkg.jpg

Nu zijn er echter twee problemen, een een beetje raar en misschien niet echt een probleem, maar een probleem kom ik volgens mij absoluut niet omheen...


Nummer 1: waar mijn 'ContextObject' voorheen een property 'Persons' had (die de ObjectSet<Person> teruggaf), is dit nu veranderd in 'BaseTables' die een ObjectSet<BaseTable> teruggeeft.
Dit geeft me problemen omdat ik in de rest van m'n abstractie laag overal Person als generic type gebruik. Dat zou ik nu dan moeten veranderen in BaseTable, en daar krijg ik dan problemen omdat ik steeds moet gaan casten naar Person (tenminste, ik neem aan dat hij alleen Persons teruggeeft, enkel als BaseTable referentie?). Nou ja... Ik snap er niet zoveel van hoe een tabel kan overerven van een andere tabel, dat zit niet helemaal lekker in m'n hoofd lol...


Nummer 2: ik heb nu wel de base class 'BaseTable' waarvan Person overerft, maar deze zit nu in het project waar ik mijn database model definieer. Dat kan natuurlijk niet! Mijn abstractie laag zit in een ander project, en m'n base table moet dus ook in dat project zitten.

Voorbeeld: mijn abstractie laag zit in een 'DatabasePageFramework' project. Elke web applicatie die hiervan gebruik wil maken zou gewoon een referentie naar dat project toevoegen. Maar als dat project een class nodig heeft uit het web applicatie project dan krijg je natuurlijk een circulaire referentie... Ik hoop dat je het probleem snapt, ik kan het niet goed uitleggen.

Echt voorbeeld:
Mijn web applicatie is EntityFrameworkWebTest. Deze heeft een referentie naar DatabasePageFramework. Het entity framework database model zit in EntityFrameworkWebTest (uiteraard, want elke web applicatie gaat z'n eigen database en dus model gebruiken). De BaseTable class zit dus ook in EntityFrameworkWebTest, maar ik heb die in DatabasePageFramework nodig. Dat gaat dus niet werken, want EntityFrameworkWebTest heeft een referentie naar DatabasePageFramework, en niet andersom.

Ik zit dus een beetje vast...

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Jan_V
  • Registratie: Maart 2002
  • Laatst online: 21:13
NickThissen schreef op maandag 10 januari 2011 @ 21:34:
[...]

Kun je hier iets meer over uitwijden? Ik snap niet precies wat je bedoelt namelijk.


[...]
Kan niet meer vinden waar ik dat toen heb gelezen, maar komt hier op neer.
In je programmeertaal wil je het liefst OO ontwikkelen, dus heb je waarschijnlijk een SubClass welke overerft van een BaseClass (Person en Entity bij jou).
Dit werkt allemaal prima en kun je (normaal) ook vrij eenvoudig ontwikkelen.

Nu kun je eenzelfde principe in een database ook toepassen.
Je hebt dan een tabel Base en een tabel Sub (respectievelijk Entity en Person):
Base
IDInt, PK
Deletedbit
Createddatetime
Updateddatetime


En
Sub
IDInt, FK
Firstnamenvarchar(50)
Lastnamenvarchar(100)
Birthdatedatetime

Doormiddel van de foreign key zijn deze twee gekoppeld en kun je ook nog andere type items/tabellen toevoegen die ook de Base via de ID met een FK hebben gelinkt, bijvoorbeeld Sub2, Sub3, etc. (Auto, Huis, Fiets).
Wanneer je dit in je EF implementeert zul je inderdaad een dergelijk diagram krijgen als je zelf hebt ondervonden. Dit is ook de situatie die je nu hebt en beschrijft. Je hebt nu een soort inheritance in je database gecreeerd.

Wat ik een keer als respons heb gekregen is dat je dit beter via een relatie kunt koppelen en niet op deze manier. Of het beter is weet ik niet, maar er zit op zich wat in natuurlijk.
Je maakt nog steeds 2 tabellen, Base en Sub, maar nu als volgt:
Base
IDInt, PK
Deletedbit
Createddatetime
Updateddatetime


En
Sub
IDInt, PK
Firstnamenvarchar(50)
Lastnamenvarchar(100)
Birthdatedatetime
BaseIDint, FK

De Sub is nu een opzichzelfstaand object. Wel heeft hij een koppeling (FK) naar de Base, zo kun je dus de generieke properties verkrijgen.

Als ik het zo terug lees, leg ik het misschien niet helemaal goed uit,maar hoop dat je het verschil ziet. In het eerste voorbeeld (zoals je in je reply hebt), doe je aan een soort overerving. In het tweede voorbeeld heeft je object gewoon een property wat een complex type is.
In C# zou het ongeveer als volgt uit zien:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Sub
{
public int ID{ get; set;}
public int FirstName{ get; set;}
public int LastName{ get; set;}
public DateTime Birthday { get; set;}
public Base Entity{ get; set;}
}

public class Base
{
public int ID{ get; set;}
//Overige properties en dergelijke
}

En dus niet meer
C#:
1
2
3
public class Sub : Base
{
}


Uit de overige context van je post begrijp ik dat je 2 projecten hebt, 1 framework met je modellen en dergelijke en 1 web project waar je de rest in stopt.
Op zich prima, maar dan kun je inderdaad een circulaire reference krijgen, wat volgens mij niet kan.

Wellicht zou je er verstandig aan doen om de hele data access in een apart project te stoppen.
Zo kun je dan het hele database gebeuren in dat project afhandelen en de methoden in dat project laat je dan gewoon de objecten van je Framework retourneren, dus bijvoorbeeld een List<Person> of een Person. Dan kun je in je web project (presentatie) gewoon je Person klassen gebruiken. Je zou dan alleen de Person moeten vullen in de Data Access project.
Aangezien het lijkt alsof je nu alles in 2 projecten hebt gestopt, denk ik dat je er vestandig aan doet om eens in te lezen over het n-layer/n-tier opzetten.
Nagenoeg alle projecten die ik maak hebben de volgende projecten:
  • Presentatie
  • Service
  • Business
  • DataAccess
  • Model
  • Framework
  • Test
Of dit goed is laat ik even in het midden. Je hebt dan alleen een veel betere scheiding met wat je aan het doen bent.

Voor je huidige probleem met de overerving:
Dat je ContextObject met de overerving trouwens ineens een BaseObject terug geeft zou kunnen, heb er weinig ervaring mee en is al lang geleden dat ik er mee aan de slag ben geweest. Je zou gewoon in de return van je data access methode dit gewoon kunnen omvormen naar een List<Person> of Person dacht ik zo. Dan kun je gewoon die klassen gebruiken in de rest van je applicatie.

Note: Ik zeg niet dat wat ik geschreven heb goed is, of het ei van Columbus, maar misschien helpt het je een beetje in de juiste richting.

Battle.net - Jandev#2601 / XBOX: VriesDeJ


Acties:
  • 0 Henk 'm!

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
Het is natuurlijk wel een performance killer als je op deze manier je object model gaat modelleren in je database. Dit betekent op zijn best een join voor elke query waar dat al niet nodig is, en op zn slechts (als je O/R mapper niet goed optimized) een n+1 select probleem.

Is de TS niet gewoon een beetje aan het overdrijven in zijn abstractie. Je data layer wordt gegenerate uit de DB, je plakt daar iets generieks tegenaan wat in die data layer zorgt voor het opslaan van die created/update dates en die delete flag, en je bent klaar.

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Grijze Vos schreef op donderdag 13 januari 2011 @ 01:14:
Is de TS niet gewoon een beetje aan het overdrijven in zijn abstractie. Je data layer wordt gegenerate uit de DB, je plakt daar iets generieks tegenaan wat in die data layer zorgt voor het opslaan van die created/update dates en die delete flag, en je bent klaar.
Ja maar dat is nou net het probleem. Hoe "plak ik er iets generieks tegenaan" als ik enkel weet dat elke entity van EntityObject overerft? Dan kan ik dus niet zeker weten dat elke entity een CreatedTime, UpdatedTime en Deleted property heeft, dus kan ik die ook niet gaan gebruiken tenzij ik Reflection ofzo ga gebruiken maar dat is ook weer zoiets...
Als ik de entities nou van mijn eigen Entity class kan laten overerven (die van EntityObject overerft, als tussenlaag zeg maar), dan kan ik dat wel weten want dan kan ik die properties in Entity zetten.

[ Voor 12% gewijzigd door NickThissen op 13-01-2011 11:03 ]

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Laat ze een interface implementeren? (Of begrijp ik je probleem nu verkeerd?)

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • urk_forever
  • Registratie: Juni 2001
  • Laatst online: 11-09 18:27
Ik ben niet heel erg bekend met Entity Framework, maar kan/moet je niet je baseclass als abstract declareren en die laten erven van EntityObject en je andere classes van je baseclass?

Hail to the king baby!


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
RobIII schreef op donderdag 13 januari 2011 @ 11:29:
Laat ze een interface implementeren? (Of begrijp ik je probleem nu verkeerd?)
Als ik wist hoe, graag! Nou ja, liever gewoon een abstract class, maar als ik de automatisch gegenereerde classes een interface kan laten implementeren moet ik ze toch ook een abstracte class kunnen laten overerven? :?
urk_forever schreef op donderdag 13 januari 2011 @ 11:47:
Ik ben niet heel erg bekend met Entity Framework, maar kan/moet je niet je baseclass als abstract declareren en die laten erven van EntityObject en je andere classes van je baseclass?
Ja, dat is precies wat ik wil, maar hoe?? De classes die rijen in m'n database voorstellen worden door het EF automatisch aangemaakt aan de hand van het model. In die files kan ik niet gaan rommelen want elke keer dat ik iets verander in het model (tabel toevoeg etc) wordt die file opnieuw gegenereerd en zijn mijn eigen veranderingen weg. De classes zijn wel partial dus ik kan wel ergens anders 'een ander deel' van de classes maken, om daar eigen code in te vullen, maar ik kan ze op die manier natuurlijk niet van een andere base class laten overerven (aangezien de automatisch gegenereerde partial classes al van EntityObject overerven).

Het probleem is dus simpel: het EF maakt een class aan die van EntityObject overerft. Ik wil dat die class van mijn eigen abstracte class overerft. Dus in plaats van dit:
C#:
1
2
3
// auto generated
public partial class Person : EntityObject
{ ... }

wil ik dit
C#:
1
2
3
4
5
6
public partial class Person : Entity
{ ... }

// in ander project:
public abstract class Entity : EntityObject
{ ... }


Maar ik kom er maar niet achter hoe ik dat kan dan, als het uberhaupt al mogelijk is.


Ik ben op het moment overgestapt naar NHibernate (eindelijk aan de praat gekregen met een oude versie 1.2) maar die mappings definieren is ook niet ideaal, dat werkt toch beter in die model editor van het entity framework. Maar in NHibernate hoeven de 'tabel classes' (zoals Person in m'n voorbeeld) niet perse van een of andere type over te erven (ik moet ze gewoon zelf maken), dus kan ik ze zelf van Entity laten overerven, en daarmee heb ik nou een handige abstractie laag toegevoegd waardoor inserts, updates, deletes, load, etc allemaal automatisch gaan.

[ Voor 6% gewijzigd door NickThissen op 13-01-2011 12:19 ]

Mijn iRacing profiel

Pagina: 1