Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

LINQ και ιεραρχία attributes

Îåêßíçóå áðü ôï ìÝëïò DeClen. Τελευταία δημοσίευση από το μέλος KelMan στις 18-02-2008, 00:18. Υπάρχουν 7 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  14-02-2008, 17:14 40162

    LINQ και ιεραρχία attributes

    Τον τελευταίο καιρό ασχολούμαι με το LINQ και προσπαθώ να δω - μάθω κάποια πράγματα πέρα από τον designer.

    Ξεκινώντας λοιπόν ένα πολυ απλό test - projectάκι είπα να κάνω τα εξής....

    1) Δημιουργώ ένα base αντικείμενο με κάποια βασικά properties, πχ.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [Table]
    public class BaseObligations
    {
    [Column(Name = "ObligationID", DbType = "Int NOT NULL IDENTITY")]
    public int ObligationID
    {
    get;
    set;
    }
    }

    2) Χρησιμοποιώ την base class και φτιάχνω το αντικείμενο με το οποίο θέλω να δουλέψω

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     [Table]    
    public class Obligations : BaseObligations
    {
    [Column(Name = "ObligationDate", DbType = "SmallDateTime NOT NULL")]
    public System.DateTime ObligationDate
    {
    get;
    set;
    }
    }

    3) Στη συνέχεια χρησιμοποιούμε μια sproc για να "γεμίσουμε" το αντικείμενό μας

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
      public class ObligationsDataContext : DataContext
    {
    //Initialize data context
    public ObligationsDataContext()
    : base(System.Configuration.ConfigurationManager.ConnectionStrings["DBConnectionString"].ConnectionString)
    {
    }

    [Function(Name="SelectObligations")]
    public ISingleResult<Obligations> SelectObligations([Parameter(Name = "ObligationCode", DbType = "Int")]
    System.Nullable<int> obligationCode)
    {
    IExecuteResult result = this.ExecuteMethodCall(this,
    ((MethodInfo)(MethodInfo.GetCurrentMethod())), obligationCode);
    return ((ISingleResult<Obligations>)(result.ReturnValue));
    }
    }

    Σε αυτό λοιπόν το σημείο παρουσιάζεται πρόβλημα. Πιο συγκεκριμένα ένα πολύ ωραίο μήνυμα σφάλματος με πληροφορεί πως

    Data member 'Int32 ObligationID' is not part of the mapping for type 'Obligations'. Is the member above the root of an inheritance hierarchy?

    Αφού ψάχτηκα αρκετά διαγράφω την base class. Πλέον το αντικείμενό μου είναι:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     [Table]    
    public class Obligations
    {

    [Column(Name = "ObligationID", DbType = "Int NOT NULL IDENTITY")]
    public int ObligationID
    {
    get;
    set;
    }

    [Column(Name = "ObligationDate", DbType = "SmallDateTime NOT NULL")]
    public System.DateTime ObligationDate
    {
    get;
    set;
    }
    }

    Με αυτή την αλλαγή όλα δούλεψαν μια χαρά...

    Το θέμα είναι ότι δεν μπορώ να καταλάβω γιατί δεν δουλεύει η πρώτη προσέγγιση. Σε κάποιο blog διάβασα πως το LINQ to SQL υποστηρίζει μόνο “one table per inheritance hierarchy”.

    Δηλαδή ρε παιδιά αν κατάλαβα καλά δεν μπορούμε να ορίσουμε base classes με κοινά attributes (πχ Id, timestamp κτλ.) για τα αντικείμενά μας (...ή έχω καταλάβει κάτι λάθος);

  •  14-02-2008, 23:54 40170 σε απάντηση της 40162

    Απ: LINQ και ιεραρχία attributes

    DeClen:

    Δηλαδή ρε παιδιά αν κατάλαβα καλά δεν μπορούμε να ορίσουμε base classes με κοινά attributes (πχ Id, timestamp κτλ.) για τα αντικείμενά μας (...ή έχω καταλάβει κάτι λάθος);

    Νομίζω σωστά τα λες. Κι εγώ διάβασα ότι αυτό που προσπαθείς να κάνεις θα υποστηρίζεται στο LINQ to Entities. Το LINQ to SQL που έχουμε τώρα είναι για απλούστερα σενάρια.


    Vir prudens non contra ventum mingit
  •  15-02-2008, 10:35 40181 σε απάντηση της 40170

    Απ: LINQ και ιεραρχία attributes

    Πράγματι, έχεις δίκιο.

    Το έψαξα περισσότερο το θέμα και είδα ότι κάποια πιο advanced features όπως Attributes Inheritance, Entity Inheritance, Single Entity From Multiple Tables κτλ. θα υποστηρίζονται στο LINQ to Entities.

    Σε ευχαριστώ για το χρόνο σου.
  •  15-02-2008, 12:48 40184 σε απάντηση της 40181

    Απ: LINQ και ιεραρχία attributes

    Αυτό που περιγράφεις μπορεί να γίνει με Single-Table Inheritance. Θα πρέπει να φτιάξεις ένα πίνακα όπως ο παρακάτω, ο οποίος περιέχει όλα τα πεδία της base και των derived κλάσεων. To πεδίο ObligationType παίρνει την τιμή 1 αν το row αφορά την ObligationBase, 2 αν το row αφορά την Obligation.

    CREATE TABLE [dbo].[Obligations](
      [ObligationID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
      [ObligationType] [int] NOT NULL,
      [ObligationDate] [datetime] NULL
    ) ON [PRIMARY]
    GO
    INSERT INTO Obligations (ObligationType, ObligationDate) VALUES(1,null)
    INSERT INTO Obligations (ObligationType, ObligationDate) VALUES(2,getdate())
    GO
    /****** Object: StoredProcedure [dbo].[SelectObligation] Script Date: 02/15/2008 12:12:18 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    CREATE PROCEDURE [dbo].[SelectObligation]
      (  @ObligationCode int  )
    AS
      select * from obligations where ObligationID=@ObligationCode

    Μετά πρέπει να φτιάξεις το κατάλληλο DataContext. Ο πιο εύκολος τρόπος είναι να δημιουργήσεις μία κλάση LINQ to SQL και να τραβήξεις από το Data View τον πίνακα obligations επάνω στο designer. Έτσι θα δημιουργηθεί αυτόματα η κλάση Obligation. Μετονόμασε την σε ObligationBase και φτιάξε μία νέα κλάση Obligation. Σε αυτή μετέφερε το ObligationDate με Cut, Paste και πρόσθεσε ένα Inheritance association με Discriminator Property το ObligationType και Base Value το 1 και derived value το 2. O designer θα δημιουργήσει αυτόματα το κατάλληλο mapping file και τις κλάσεις. Ο κώδικας των κλάσεων είναι ο παρακάτω. Το σημαντικό είναι ότι για να δουλέψει το inheritance θα πρέπει να προσθέσεις στην ObligationBase τα attributes InheritanceMapping και στο property ObligationType το IsDiscriminator=true.

    [Table(Name = "dbo.Obligations")]
    [InheritanceMapping(Code = "1", Type = typeof(ObligationBase), IsDefault = true)]
    [InheritanceMapping(Code = "2", Type = typeof(Obligation))]
    public partial class ObligationBase
    {

    private int _ObligationID;

    private int _ObligationType;

    public ObligationBase()
    {
    }

    [Column(Storage = "_ObligationID", AutoSync = AutoSync.Always, DbType = "Int NOT NULL IDENTITY", IsDbGenerated = true)]
    public int ObligationID
    {
    get
    {
    return this._ObligationID;
    }
    set
    {
    if ((this._ObligationID != value))
    {
    this._ObligationID = value;
    }
    }
    }

    [Column(Storage = "_ObligationType", DbType = "Int NOT NULL", IsDiscriminator = true)]
    public int ObligationType
    {
    get
    {
    return this._ObligationType;
    }
    set
    {
    if ((this._ObligationType != value))
    {
    this._ObligationType = value;
    }
    }
    }
    }

    public partial class Obligation : ObligationBase
    {

    private System.DateTime _ObligationDate;

    public Obligation()
    {
    }

    [Column(Storage = "_ObligationDate", DbType = "DateTime NOT NULL")]
    public System.DateTime ObligationDate
    {
    get
    {
    return this._ObligationDate;
    }
    set
    {
    if ((this._ObligationDate != value))
    {
    this._ObligationDate = value;
    }
    }
    }
    }

    To Single-Table inheritance έχει αρκετούς περιορισμούς. Καταρχήν πρέπει να ορίσεις σε ένα πίνακα όλα τα πεδία, όλων των κλάσεων. Αυτό σημαίνει ότι δεν κάνει για μεγάλες ιεραρχίες. Επιπλέον, όλα τα πεδία των derived κλάσεων πρέπει να είναι nullable ή να έχουν default τιμές. Αυτό δημιουργεί προβλήματα στο σχεδιασμό της εφαρμογής καθώς δεν μπορείς πλέον να ελέγξεις αν υπάρχουν κάποια απαιτούμενα πεδία για τις derived κλάσεις μέσω του NOT NULL ούτε μπορείς να ορίσεις διαφορετικές default τιμές για κάθε κλάση.
    Το πλεονέκτημα του είναι ότι είναι γρήγορο, τόσο για αναζητήσεις ενός τύπου κλάσης, όσο και για πολυμορφικές αναζητήσεις (αναζητήσεις που θα επιστρέψουν διάφορες κλάσεις) καθώς δεν χρειάζεσαι joins.

    Σε περίπτωση που έχεις multi-table inheritance στη βάση μπορείς να τα χρησιμοποιήσεις φτιάχνοντας ένα view για κάθε κλάση το οποίο θα κάνει τα απαραίτητα joins. Στο dbml αυτά θα εμφανίζονται ως ξεχωριστές κλάσεις. Αν μπορεί να δουλέψει και inheritance εκεί δεν ξέρω, αλλά επειδή με καίει το ψάχνω σήμερα.

    Συνοπτικά, το LINQ to SQL υποστηρίζει inheritance, απλά είναι "ελαφρώς" περιορισμένο.

    Παρακάτω δίνω και dbml για το παραπάνω παράδειγμα:

    <?xml version="1.0" encoding="utf-8"?>
    <Database Name="MyDB" Class="ObligationsDataContext" xmlns="http://schemas.microsoft.com/linqtosql/dbml/2007">
    <Connection Mode="AppSettings" ConnectionString="Data Source=.\SQLEXPRESS;Initial Catalog=MyDB;Integrated Security=True" SettingsObjectName="LinqTest.Properties.Settings" SettingsPropertyName="MyDBConnectionString" Provider="System.Data.SqlClient" />
    <Table Name="dbo.Obligations" Member="ObligationBases">
    <Type Name="ObligationBase" Id="ID1" InheritanceCode="1" IsInheritanceDefault="true">
    <Column Name="ObligationID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsDbGenerated="true" CanBeNull="false" />
    <Column Name="ObligationType" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" IsDiscriminator="true" />
    <Type Name="Obligation" Id="ID2" InheritanceCode="2">
    <Column Name="ObligationDate" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
    </Type>
    </Type>
    </Table>
    <Function Name="dbo.SelectObligation" Method="SelectObligation">
    <Parameter Name="ObligationCode" Parameter="obligationCode" Type="System.Int32" DbType="Int" />
    <ElementType IdRef="ID1" />
    </Function>
    </Database>

     

     


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  16-02-2008, 13:16 40207 σε απάντηση της 40184

    Απ: LINQ και ιεραρχία attributes

    Είναι δυνατόν να υλοποιήσει κανείς και τα άλλα δύο μοντέλα (table per class και table per concrete class) inheritance με το LINQ to SQL αν και θέλει ολίγον κώδικα και η κάθε λύση έχει τους δικούς της περιορισμούς. Σαν παράδειγμα, έστω ότι υπάρχουν τρεις κλάσεις, Person (ID, Name), Employee (ID, Name, HireDate) και Manager  (ID, Name, HireDate, Department), όπου η κάθε κλάση κληρονομεί από την προηγούμενη.

    Table per Class

    Στο μοντέλο table per class υπάρχει ένας πίνακας για κάθε κλάση, ο οποίος κρατάει μόνο τα πεδία που ορίζονται σε αυτή την κλάση. Χρησιμοποιώντας το παραπάνω παράδειγμα, θα υπάρχει ένας πίνακας Persons (PersonID, Name) , ένας πίνακας Employees (PersonID, HireDate) και ένας πίνακας Manager (PersonID,Department). Για να πάρουμε τους Employees πρέπει να κάνουμε join τους πίνακες Persons και Employees. Για να πάρουμε τους Managers, θα πρέπει να κάνουμε join τους πίνακες Persons, Employees, Managers. Για να βρούμε όλα τα αντικείμενα, τόσο Person όσο και Employee όσο και Manager, κάνουμε select από τον πίνακα Persons.
    Το μοντέλο αυτό είναι το πιο συνηθισμένο γιατί επιτρέπει τη χρήση κανονικοποιημένων πινάκων και εύκολα πολυμορφικά queries ακόμα και με απλή SQL.

    Για να δουλέψει αυτό το μοντέλο με το LINQ to SQL χρειάζονται τα παρακάτω:

    • Ένα view για κάθε concrete class πέρα από την root, π.χ. vwEmployees και vwManagers το οποίο θα κάνει join όλους τους πίνακες που απαιτούνται για κάθε κλάση.
    • Insert, Update, Delete stored procedures για κάθε κλάση.
    • Οι κλάσεις Person, Employee, Manager θα πρέπει να περιέχουν όλα τα properties με τα κατάλληλα LINQ attributes.
    • Οι κλάσεις θα κάνουν κανονικά inherit η μία από την άλλη, αλλά θα πρέπει τα properties να ορίζονται ως virtual στις base κλάσεις και ως override στις derived

    Τα παραπάνω μπορούν να γίνουν και μέσω του LINQ to SQL designer, ορίζοντας το inheritance στο code-behind αρχείο και όχι στον ίδιο το designer. Υπάρχει όμως πρόβλημα με τα functions που προσθέτει ο designer για την υλοποίηση των INotifyPropertyChanging, INotifyPropertyChanged. Αυτά μπορούν να αφαιρεθούν από τις derived κλάσεις αλλά θα ξαναδημιουργηθούν όταν αλλάξει κάτι στο designer.

    Table per Concrete Class

    Στο μοντέλο table per class υπάρχει ένας πίνακας για κάθε κλάση, ο οποίος κρατάει όλα τα πεδία που ορίζονται σε αυτή την κλάση. Χρησιμοποιώντας το παραπάνω παράδειγμα, θα υπάρχει ένας πίνακας Persons (PersonID, Name) , ένας πίνακας Employees (PersonID, Name, HireDate) και ένας πίνακας Manager (PersonID,Name, HireDate, Department).  Για να βρούμε ένα Person, Employee, Manager απλά κάνουμε query τον αντίστοιχο πίνακα. Για να βρούμε όλα τα Person αντικείμενα θα πρέπει να φτιάξουμε ένα query το οποίο θα ψάχνει σε όλους τους πίνακες και θα κάνει union τα αποτελέσματα.

    Για να δουλέψει αυτό το μοντέλο με το LINQ to SQL χρειάζονται τα παρακάτω:

    • Οι κλάσεις Person, Employee, Manager θα πρέπει να περιέχουν όλα τα properties με τα κατάλληλα LINQ attributes.
    • Οι κλάσεις θα κάνουν κανονικά inherit η μία από την άλλη

    Τα παραπάνω μπορούν να γίνουν και μέσω του LINQ to SQL designer, ορίζοντας το inheritance στο code-behind αρχείο και όχι στον ίδιο το designer, προσέχοντας πάλι τα functions που προσθέτει ο designer.
    Το πρόβλημα εδώ είναι ότι για να κάνουμε αναζήτηση όλων των persons θα πρέπει να φτιάξουμε ένα union query . Αν θέλουμε να βρούμε τους Employees θα πρέπει να φτιάξουμε νέο union query.

    Συμπέρασμα

    Μπορεί κανείς να χρησιμοποιήσει και το μοντέλο Table-per-Class και το Table-per-Concrete-Class προσθέτοντας κώδικα. Δυστυχώς, το πιο χρήσιμο μοντέλο απαιτεί και τον περισσότερο κώδικα. Αυτό δημιουργεί το εξής δίλημα:

    • Χρησιμοποιούμε το μοντέλο που ταιριάζει στην εφαρμογή μας και προσθέτουμε τα απαραίτητα view, stored procedures ή
    • Προσαρμόζουμε την εφαρμογή και τη βάση στο μοντέλο για να γλυτώσουμε τον επιπλέον κώδικα

    Προσωπικά θα προτιμούσα να χρησιμοποιήσω το μοντέλο που χρειάζεται η βάση και η εφαρμογή μου και ας χρειάζεται επιπλέον κώδικα. Όταν βγει το LINQ to Entities θα μετατρέψω τον κώδικα ώστε να δουλεύει σύμφωνα με αυτό και θα αφαιρέσω τα επιπλέον views, κλπ. Αν προσαρμόσω την εφαρμογή στο μοντέλο, κινδυνεύω να καταλήξω με μία εφαμοργή η οποία θα είναι δύσκολη στη συντήρηση μελλοντικά.


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  17-02-2008, 18:56 40234 σε απάντηση της 40207

    Απ: LINQ και ιεραρχία attributes

    Πολύ κατατοπιστικά τα δύο posts σου Παναγιώτη, ιδιαίτερα για το πώς αποτυπώνονται οι ιεραρχίες στο σχεσιακό μοντέλο. Έχω προσέξει ότι καθώς πολλοί developers σχεδιάζουν την εφαρμογή ξεκινώντας από τη βάση, δεν έχουν μάθει να σκέφτονται για inheritance και ως εκ τούτου το μοντέλο τους είναι flat με αποτέλεσμα να είναι ακόμα πιο δύσκολο το πέρασμα στο object oriented μοντέλο. Από εκεί και πέρα, υπάρχει το μεγάλο θέμα του τί schema changes απαιτούνται για να μπορέσει να παίξει ο mapper πάνω στο σχεσιακό μοντέλο. Όσο λιγότερες αλλαγές απαιτούνται, όσα λιγότερα βοηθητικά πεδία, τόσο το καλύτερο. Σήμερα, οι βάσεις τίνουν να περιέχουν πολύ meta-πληροφορία. Διάφορα "ειδικά" πεδία όπως για replication ή για change management ή ακόμα-ακόμα και objects (όπως triggers) που τελικά δεν εξυπηρετούν το business modeling αλλά τη λειτουργία της εφαρμογής, κάνουν το σύστημα πιο περίπλοκο. Προσωπικά μου αρέσει η βάση να είναι όσο το δυνατόν πιο "καθαρή". Δεν θα επέλεγα κάποια λύση που δεν θα υποστήριζε εγγενώς το εργαλείο μου, όπερ σημαίνει σιχαίνομαι το edit σε auto-generated κώδικα. Αν είναι κάτι τέτοιο απαραίτητο, καλύτερα να χρησιμοποιηθεί άλλο εργαλείο αν και αυτό δεν είναι πάντοτε εφικτό καθώς αυτού του είδους τις αποφάσεις βαρύνουν και άλλοι παράγοντες...

     


    Vir prudens non contra ventum mingit
  •  17-02-2008, 21:51 40248 σε απάντηση της 40234

    Απ: LINQ και ιεραρχία attributes

    Το single-table inheritance είναι ίσως το πιο ... "βρώμικο" μοντέλο όσον αφορά τις αλλαγές που απαιτεί από τη βάση. Πρέπει να υπάρχει ένας μόνο πίνακας για όλες τις κλάσεις, γεμάτος από NULLABLE πεδία τα οποία δεν έχουν νόημα παρά μόνο για συγκεκριμένες κλάσεις (η κανονικοποίηση πάει περίπατο) αλλά απαιτεί και ένα πεδίο τύπου με "μαγικές" τιμές, όσον αφορά τη βάση. Η δημιουργία queries, reports, stored procedures σε τέτοιο πίνακα είναι αρκετά δύσκολη.

    Δεν είναι υποχρεωτικό πάντως να χρησιμοποιηθεί αυτό το μοντέλο, ούτε αναγκαστικά ο generated κώδικας που δημιουργεί ο OR Designer. Μπορεί άνετα να χρησιμοποιηθεί δικός μας κώδικας με τη δομή και το inheritance που θέλουμε χρησιμοποιώντας τα κατάλληλα mapping attributes ή mapping files. Το mapping file και οι αρχικές κλάσεις μπορούν να δημιουργηθούν μία φορά με το SqlMetal και μετά να τις αλλάξουμε όπως θέλουμε. Εναλλακτικά, μπορούμε να χρησιμοποιήσουμε άλλους code generators όπως το Codesmith και να φτιάξουμε τον κώδικα όπως τον θέλουμε.

    Τώρα, γιατί να το κάνουμε αυτό? Το Linq to SQL είναι ένα πολύ απλό ORM το οποίο προφανώς έχει ελλείψεις οι οποίες επηρεάζουν τις εφαρμογές. Από την άλλη, είναι απλό και είναι εδώ τώρα. Πολλά projects όπως το ASP.NET Dynamic Data το χρησιμοποιούν προσωρινά. Όλοι περιμένουν το Entity Framework αλλά δεν το έχουμε ακόμα. Αν ένα έργο πρέπει να βγει τον επόμενο 1 μήνα ή 2, δεν είναι εύκολο να χρησιμοποιήσει κανείς άλλη τεχνολογία. Αναγκαστικά θα πρέπει τα κενά να τα γεμίσουμε μόνοι μας.

    Ακόμα και όταν βγει το Entity Framework το Linq to SQL θα έχει τη θέση του ως η απλή και γρήγορη ORM λύση.


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  18-02-2008, 00:18 40250 σε απάντηση της 40248

    Απ: LINQ και ιεραρχία attributes

    Ως προς τις τρεις μεθόδους, ναι, κι εμένα δε μου αρέσει ιδιαίτερα αυτή η λύση, προτιμώ την Table Per Class λύση.

    Ως προς το Linq to SQL ως ORM νομίζω αποτελεί μια λύση που θα πρέπει να μπει υπό εξέταση μαζί με τις υπόλοιπες, αυτές που υπάρχουν εδώ και καιρό. Ας μην ξεχνάμε ότι πέρα από το Linq to SQL με "customιές" του σήμερα και το Linq to Εntities χωρίς customies του αύριο υπάρχει και το option της χρήσης κάποιου από τους mappers που ήδη έχουμε. Αν είναι απλό το πρόβλημα και μπορώ να χρησιμοποιήσω το Linq to SQL, οκ. Αν όχι, θα προτιμήσω να χρησιμοποιήσω πχ το LLBLGen Pro αντί να ψάχνω τρόπο να γεμίσω το κενό. Θεωρώ μεγάλο πλεονέκτημα ως προς την παραγωγικότητα να πατάω το κουμπάκι "Generate" και να μην με νοιάζει τίποτε άλλο...


    Vir prudens non contra ventum mingit
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems