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

 

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

Access DataContext μέσα από το object

Îåêßíçóå áðü ôï ìÝëïò Χρήστος Γεωργακόπουλος. Τελευταία δημοσίευση από το μέλος Χρήστος Γεωργακόπουλος στις 11-12-2008, 18:10. Υπάρχουν 10 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  09-12-2008, 14:43 46808

    Access DataContext μέσα από το object

    Hello, έχω ένα objectάκι που μου έχει φτιάξει το linq, πχ Customer. Θέλω να του βάλω ένα method Delete, έτσι ώστε να καλώ το MyCustomer.Delete και να μαρκάρεται για delete μέσα στο context.
    Αυτό για να γίνει εξωτερικά, θα έπρεπε να δώσω MyContext.DeleteOnSubmit(MyCustomer). Και ερωτώ: Μέσα απο το Customer, πως μπορώ να πάρω το context που το managάρει;
    Αυτή τη στιγμή περνάω το context σαν παράμετρο (Public Sub Delete(context as DbContext)) αλλά μου κάνει σε παράλογο να μην έχει το Customer reference στο Context που το χειρίζεται και να αναγκάζομαι να του το ξαναδώσω.

    Έχει κανείς κάποια ιδέα;

    Χρήστος Γεωργακόπουλος
    Δημοσίευση στην κατηγορία:
  •  10-12-2008, 08:27 46817 σε απάντηση της 46808

    Απ: Access DataContext μέσα από το object

    Ιδέα δεν νομίζω ότι υπάρχει, μιας και ο σκοπός είναι αυτός: το object να μην έχει context, εκτός αν εσύ του το καθορίσεις. Μπορείς να κάνεις extensions στο object σου για να σε βοηθήσουν να τα χειρίζεσαι πιο εύκολα, όπως την delete που έχεις φτιάξει ήδη, αλλά η αλήθεια είναι ότι χρειάζεσαι ένα layer επάνω από αυτό που είσαι - data layer - για να μπορέσεις να υλοποιήσεις κάτι τέτοιο.

     

    George J.

     


    George J. Capnias: Χειροπρακτικός Υπολογιστών, Ύψιστος Γκουράρχης της Κουμπουτερολογίας
    w: capnias.org, t: @gcapnias, l: gr.linkedin.com/in/gcapnias
    dotNETZone.gr News
  •  10-12-2008, 12:45 46822 σε απάντηση της 46808

    Απ: Access DataContext μέσα από το object

    Όσον αφορά το Linq ακόμη ψάχνομαι. Ίσως, όμως, αυτά τα δύο άρθρα σου φανούν χρήσιμα (1, 2).


    Ακόμα κι ένας άνθρωπος μπορεί ν' αλλάξει τον κόσμο. Μη θέλεις να κυβερνήσεις. Απλά δείξε το μονοπάτι κι ο κόσμος θ' ακολουθήσει!!
  •  10-12-2008, 21:18 46835 σε απάντηση της 46822

    Απ: Access DataContext μέσα από το object

    Markos:

    Όσον αφορά το Linq ακόμη ψάχνομαι. Ίσως, όμως, αυτά τα δύο άρθρα σου φανούν χρήσιμα (1, 2).



    Tnx. Το άρθρο του Strahl το είχα δεί, δεν έχω καταλάβει εντελώς τι κάνει (δεν το έχω δει τόσο προσεκτικά) αλλά από τον κώδικά του δεν νομίζω ότι μου δίνει κάποια λύση. Το δεύτερο στο CodeProject είναι ψευτολύση, γιατί τα methods που προσθέτει ανοίγουν δικό τους instance του context για να κάνουν τη δουλιά, και δεν χρησιμοποιούν το υπάρχον (+ το χειρότερο, φωνάζουν μόνα τους submitChanges), είναι νομίζω τελείως εκτός best practices.

    Χρήστος Γεωργακόπουλος
  •  10-12-2008, 23:26 46836 σε απάντηση της 46817

    Απ: Access DataContext μέσα από το object

    Ο Γιώργος έχει δίκιο, τα objects είναι... εντελώς objects, απλά κάνουν implement ένα interface. Δεν έχουν την παραμικρή ιδέα για το τι γίνεται γύρω τους. Αυτοί που έχουν μια σχετική δύναμη είναι τα tables σου δίνει το context. Από εκεί γίνονται όλες οι δουλιές που έχουν να κάνουν με το context. Αλλά δεν διευκολύνουν κάτι, για να έχεις πρόσβαση στα tables πρέπει να ξέρεις το context...

    Θα ψάξω να βρω κάποια διαφορετική υλοποίηση, το πραγματικό μου πρόβλημα πάντως είναι το εξής:  Έχω ένα Order που έχει πολλά OrderItems. Θέλω να κάνω raise ένα event στα OrderItems, έτσι ώστε όταν αλλάζει το quantity τους, να πιάνει το event το Order και να ξαναυπολογίζει το μεταφορικό κόστος (που βεβαίως είναι πεδίο πάνω στο Order). Το πρόβλημα είναι ότι όταν καταλάβει το Order ότι άλλαξε το quantity από ένα OrderItem, δεν μπορεί να βρει το context που βρίσκεται για να κάνει τις δουλιές που χρειάζεται.

    Το επιπλέον layer που ανέφερε ο Γιώργος πάντως δεν με ενθουσιάζει από άποψη όγκου δουλιάς, θα δω τι μπορώ να κολήσω πάνω στα objects μου για να κάνω τη δουλιά που θέλω...


    Χρήστος Γεωργακόπουλος
  •  11-12-2008, 00:00 46837 σε απάντηση της 46836

    Απ: Access DataContext μέσα από το object

    Sorry για το τρίτο απανωτό, αλλά έχω χωθεί στο reflector και ψάχνω....

    Λοιπόν, τα objects μπορεί να είναι... objects, αλλά τα EntityRefs που έχουν πάνω τους (οι δεσμοί με άλλα αντικείμενα) δεν είναι τόσο χαζά. Το EntityRef κρύβει μέσα του ένα member με το όνομα Source το οποίο είναι IEnumerable. Δεν το έψαξα πιο πολύ, αλλά υποθέτω στο runtime ως source του EntityRef, ορίζεται το Table του Context. Γι' αυτό όταν έχω ένα αντικείμενο με πεθαμένο context και προσπαθήσω να φορτώσω ένα referenced object, χτυπάει και ξέρει ότι το context του έχει πεθάνει. Η αν το πάρουμε ανάποδα, το EntityRef έχει απόλυτη γνώση του context στο οποίο ανήκει το αντικείμενό μου, για να ξέρει πως να φορτώσει referenced objects.

    Άρα... ζηλεύω. Τα EntityRefs έχουν μέσα την πληροφορία που θέλω (το context εν τέλει) αλλά το κρατάνε private. Πρέπει να βρω κάποιο τρόπο να το βγάλω έξω... Θέλω κι εγώ!!!

    Χρήστος Γεωργακόπουλος
  •  11-12-2008, 01:29 46838 σε απάντηση της 46808

    Απ: Access DataContext μέσα από το object

    Ο Γιώργος έχει δίκιο. Τα αντικείμενα υπάρχουν ανεξάρτητα από το όποιο DataContext χρησιμοποιείται για να τα διαβάσεις ή να τα αποθηκεύσει, έτσι δεν υπάρχει τρόπος το αντικείμενο να ξέρει από ποιό DataContext δημιουργήθηκε ή σε ποιό είναι attach κάθε στιγμή - πολύ απλά, μπορεί να μην είναι attached πουθενά. Μία καλή αναλογία είναι να σκεφτείς το DataContext ως ένα Connection και τα αντικείμενα ως τα Datasets, Datatables που μπορεί να περάσουν από αυτή. Ένα DataTable δεν ξέρει ποτέ ποιό connection το δημιούργησε ή ποιό θα το αποθηκεύσει στο μέλλον.

    Αυτό είναι ένα Καλό Πράγμα. Έτσι δεν είσαι υποχρεωμένος να κρατάς το ίδιο DataContext συνέχεια στη μνήμη και να κάνεις track αλλαγές οι οποίες μπορεί ποτέ να μην καταλήξουν στη βάση. Μπορείς άνετα να δημιουργείς ένα DataContext αντικείμενο μόνο όταν θέλεις να διαβάσεις από τη βάση ή να αποθηκεύσεις τις αλλαγές σου σε αυτή.

    Θα μου πεις τώρα, και ήδη το είπες, "Δηλαδή θα πρέπει να πλημμυρίσει ο κώδικας μου με DataContexts και DeleteOnSubmit ? Εδώ είπαμε να ξεχωρίσουμε αντικείμενα από το Data Layer και εσύ μου λες, να τα ξαναμπλέξουμε ξανά ?" . Προφανώς και δεν είναι καθόλου καλή ιδέα να τα μπλέξουμε ξανά. Ευτυχώς το πρόβλημα (και η λύση του) είναι κλασσικό για τα ORMs και έχει αντιμετωπιστεί εδώ και καιρό.

    Η λύση λέγεται Repository Pattern και ουσιαστικά είναι μία κλάση η οποία στην απλούστερη περίπτωση κρύβει από εσένα το DataContext και τις λεπτομέρειες του και σου δίνει ένα καθαρό interface με εντολές GetByID, Delete, Update. To Repository από πίσω αναλαμβάνει να χειριστεί το ή τα DataContext που χρειάζονται. Οι πιο προχωρημένες υλοποιήσεις διαχειρίζονται τα DataContexts και τα Transactions έτσι ώστε να υλοποιήσουν long running transactions, caching, concurrency και session management.

    Κατά κανόνα μία κλάση Repository υλοποιεί κάτι σαν το παρακάτω interface:

    interface IRepository<T>
    {
    public T GetByID(int id);
    public void Save(T entity);
    public void Delete(T entity);
    }

    Μία απλή υλοποίηση μπορεί να είναι η παρακάτω:

    class CustomerRepository:IRepository<Customer>
    {
    #region IRepository<Customer> Members

    public Customer GetByID(int id)
    {
    using (MyDataContext ctx = new MyDataContext())
    {
    return ctx.Customers.SingleOrDefault(c => c.CustomerID == id);
    }
    }

    public void Save(Customer entity)
    {
    using (MyDataContext ctx = new MyDataContext())
    {
    if (entity.CustomerID > 0)
    ctx.Customers.Attach(entity);
    else
    ctx.Customers.InsertOnSubmit(entity);
    ctx.SubmitChanges();
    }
    }

    public void Delete(Customer entity)
    {
    using (MyDataContext ctx = new MyDataContext())
    {

    ctx.Customers.DeleteOnSubmit(entity);
    ctx.SubmitChanges();
    }
    }

    #endregion
    }

    Έτσι μπορείς να γράψεις π.χ. τον παρακάτω κώδικα:
    CustomerRepository repository = new CustomerRepository();
    Customer customer=repository.GetByID(35);

    Η κλάση CustomerRepository δεν είναι και ότι καλύτερο. Είμαι σίγουρος ότι κανείς δεν έχει όρεξη να γράφει ένα Repository για κάθε κλάση. Για να φτιαχτεί μία γενική κλάση repository χρειάζονται δύο πράγματα: να έχουμε πρόσβαση στην ctx.Customers χωρίς να την προσδιορίσουμε και κάπως, να απαλλαγούμε από την αναφορά στο CustomerID.

    Η πρώτη αλλαγή είναι σχετικά εύκολη, καθώς η Customers είναι χονδρικά μία συντόμευση για την GetTable<Customer>. Η δεύτερη αλλαγή γίνεται επίσης εύκολα αν θυμηθούμε ότι μπορούμε να περάσουμε το c => c.CustomerID == id ως παράμετρ. Μπορούμε λοιπόν αντί για την GetByID να φτιάξουμε μία γενική Get:

    public T Get(Func<T,bool> criteria)
    {
    using (MyDataContext ctx = new MyDataContext())
    {
    return ctx.GetTable<T>().SingleOrDefault(criteria);
    }
    }

    Και η κλήση γίνεται:
    Repository<Customer> repository = new Repository<Customer>();
    Customer customer = repository.Get(c => c.CustomerID == 35);

    Μας μένει όμως το MyDataContext. Μπορούμε να το ξεφορτωθούμε και αυτό αν αλλάξουμε τον ορισμό της Repository σε    
    class Repository<T, C>
    where T:class
    where C:DataContext

    Έτσι η Get γίνεται:
    public T Get(Func<T,bool> criteria)
    {
    using (MyDataContext ctx = new MyDataContext())
    {
    Table<T> table = ctx.GetTable<T>();
    return table.SingleOrDefault(criteria);
    }
    }

    Η Save έχει ακόμα ένα προβληματάκι, καθώς ελέγχει το CustomerID για να δει αν ένα αντικείμενο είναι νέο και θέλει Insert, ή προϋπήρχε και θέλει Attach. Θα μπορούσα να ψάξω και να βρω ποιά πεδία είναι τα Primary keys του πίνακα αλλά δεν μου αρέσει. Αντί γι αυτό, θα περάσω και αυτό το κριτήριο ως παράμετρο. Έτσι η Repository γίνεται:

    class Repository<T, C>
    where T:class
    where C:DataContext, new()
    {
    protected Func<T, bool> IsNew;
    public Repository(Func<T, bool> isNewCriteria)
    {
    IsNew = isNewCriteria;
    }

    #region IRepository<Customer> Members

    public T Get(Func<T,bool> criteria)
    {
    using (C ctx = new C())
    {
    Table<T> table = ctx.GetTable<T>();
    return table.SingleOrDefault(criteria);
    }
    }

    public void Save(T entity)
    {
    using (C ctx = new C())
    {
    Table<T> table = ctx.GetTable<T>();
    if (IsNew(entity))
    table.Attach(entity);
    else
    table.InsertOnSubmit(entity);
    ctx.SubmitChanges();
    }
    }

    public void Delete(T entity)
    {
    using (C ctx = new C())
    {
    Table<T> table = ctx.GetTable<T>();
    table.DeleteOnSubmit(entity);
    ctx.SubmitChanges();
    }
    }

    #endregion
    }

    Το κριτήριο για το αν ένα αντικείμενο είναι νέο το ονομάζω IsNew και το περνάω στον constructor της Repository

    Τώρα, η κλήση της Get γίνεται:
         Repository<Customer,MyDataContext> repository = new Repository<Customer,MyDataContext>(c=> c.CustomerID>0);
       Customer customer = repository.Get(c => c.CustomerID == 35);

    Αλλά πάλι, αυτός ο constructor δεν μου αρέσει. Γι αυτό θα φτιάξω μία νέα CustomerRepository ως εξής:
        class CustomerRepository : Repository<Customer,MyDataContext>
        {
            public CustomerRepository()
            {           
                IsNew = (c => c.CustomerID > 0);
            }
        }

    και θα βγάλω τον Constructor από την Repository<>. Τώρα η κλήση μου γίνεται :

                CustomerRepository repository = new CustomerRepository();
                Customer customer = repository.Get(c => c.CustomerID == 35);

    Θα μπορούσα να προσθέσω κι άλλα πράγματα. Θα μπορούσα να κάνω το ίδιο κόλπο με την IsNew και να περνάω και το κριτήριο της Get ως παράμετρο στον constructor. Θα μπορούσα να προσθέσω και κάποιες Where, First κλπ, για να κάνω αναζητήσης όπως έχω συνηθίσει με το LINQ. Αντί γι αυτό όμως θέλω να περάσω στα πιο προχωρημένα θέματα.

    Αυτή τη στιγμή χρησιμοποιώ ντε και καλά ένα μόνο context τη φορά. Αυτό με βολεύει αν θέλω οι αλλαγές μου να περνάνε απευθείας στη βάση, μπορεί όμως να θέλω να υλοποιήσω διαφορετική λογική. Για παράδειγμα, μπορεί να θέλω να μαζέψω όλες τις αλλαγές που θέλω να κάνω σε ένα σύνολο αντικειμένων και να τις αποθηκεύσω όλες μαζί στο τέλος. Για να το κάνω αυτό, πρέπει το DataContext μου να παραμένει στη μνήμη, και μάλιστα να είναι διαθέσιμο για χρήση μεταξύ πολλών Repositories. Χρειάζομαι ένα τρόπο να λέω στην Repository που να βρει και πως να χρησιμοποιήσει το DataContext.

    Για να το πετύχω αυτό, θα φτιάξω διάφορες κλάσεις ContextPolicy. Άλλη θα μου επιστρέφει ένα διαφορετικό context κάθε φορά και άλλη θα μου επιστρέφει το ίδιο κάθε φορά. Επειδή όμως δεν θέλω να χάσω και την ευκολία του using, τα Factories δεν θα επιστρέφουν ένα DataContext αλλά ένα ContextWrapper:
    class ContextWrapper<C> : IDisposable where C : DataContext
    {
    public C Context { get; protected set; }

    public ContextWrapper(C context)
    {
    Context = context;
    }
    #region IDisposable Members

    public void Dispose()
    {
    Context.Dispose();
    }

    #endregion
    }

    Τα policies θα υλοποιούν το IContextPolicy interface
    interface IContextPolicy<C> where C : DataContext
    {
    ContextWrapper<C> Get();
    }

    Η κλάση ContextWrapper είναι η παρακάτω:
        public class ContextWrapper<C> : IDisposable where C : DataContext
        {
            public C Context { get; protected set; }
           
            public ContextWrapper(C context)
            {
                Context = context;
            }
            public virtual void Dispose()
            {
            }
        }

    H Dispose είναι επίτηδες κενή, γιατί θα χρησιμοποιήσω μία άλλη κλάση, την DisposingContextWrapper για τις περιπτώσεις που θέλω να κλείνω το context κάθε φορά
        class DisposingContextWrapper<C> : ContextWrapper<C> where C : DataContext
        {
            public DisposingContextWrapper(C context)
                : base(context)
            {}

            #region IDisposable Members

            public override void Dispose()
            {
                Context.Dispose();
            }

            #endregion
        }

    Και τώρα, μπορώ να φτιάξω μία ContextPolicy η οποία χρησιμοποιεί πάντα το ίδιο context:
     public class SingleContextPolicy<C> : IContextPolicy<C>
        where C : DataContext, new()
        {
            C context;
            #region IContextPolicy<C> Members

            public ContextWrapper<C> Get()
            {
                if (context==null)
                    context = new C();
                return new ContextWrapper<C>(context);
            }

            #endregion
        }
    Ή μία ContextFactory η οποία πάντα δημιουργεί ένα νέο context:
    public class MultipleContextPolicy<C> : IContextPolicy<C>
        where C : DataContext, new()
        {

            #region IContextPolicy<C> Members

            public ContextWrapper<C> Get()
            {
                C context = new C();
                return new DisposingContextWrapper<C>(context);
            }

            #endregion
        }

    Οι αλλαγές που θα κάνω στην Repository είναι ελάχιστες
    class Repository<T, C>
            where T:class
            where C:DataContext, new()
        {
            protected IContextPolicy<C> _contextPolicy;
            protected Func<T, bool> IsNew;

            #region IRepository<Customer> Members

            public T Get(Func<T,bool> criteria)
            {
                using (ContextWrapper<C> ctx = _contextPolicy.Get())
                {
                    Table<T> table = ctx.Context.GetTable<T>();
                    return table.SingleOrDefault(criteria);
                }
            }

            public void Save(T entity)
            {
                using (ContextWrapper<C> ctx = _contextPolicy.Get())
                {
                    Table<T> table = ctx.Context.GetTable<T>();
                    if (IsNew(entity))
                        table.Attach(entity);
                    else
                        table.InsertOnSubmit(entity);
                   
                    ctx.Context.SubmitChanges();
                }
            }

            public void Delete(T entity)
            {
                using (ContextWrapper<C> ctx = _contextPolicy.Get())
                {
                    Table<T> table = ctx.Context.GetTable<T>();
                    table.DeleteOnSubmit(entity);
                    ctx.Context.SubmitChanges();
                }
            }

            #endregion
        }
    Απλά, αντί για να φτιάχνω τα context με το χέρι, καλώ την Get του Context Factory.

    Μπορώ τώρα να ορίσω ότι η CustomerRepository θα χρησιμοποιεί πάντα ένα context:
    class CustomerRepository : Repository<Customer,MyDataContext>
        {
            public CustomerRepository()
            {           
                IsNew = (c => c.CustomerID > 0);
                _contextPolicy = new SingleContextPolicy<MyDataContext>();
            }
        }

    Επόμενο βήμα τώρα. Αφού μπορώ να έχω ένα κοινό context, γιατί να μην μπορώ να μαζέψω όλες τις αλλαγές μέχρι τη στιγμή που θέλω εγώ? Και χωρίς φυσικά να κάνω hard-code αυτή τη συμπεριφορά? Η απάντηση είναι άλλο ένα σετ από Policies, τα Submission policies. Θα προσθέσω επίσης μία SaveAll στη Repository η οποία θα καλεί απευθείας την SubmitChanges. Τα Submission policies είναι τα εξής
    interface ISubmissionPolicy<C> where C : DataContext
        {
            void Submit(C context);
        }

        public class AutoSubmitPolicy<C>:ISubmissionPolicy<C> where C : DataContext
        {

            #region ISubmissionPolicy<C> Members

            public void Submit(C context)
            {
                context.SubmitChanges();
            }

            #endregion
        }

        public class DeferSubmitPolicy<C> : ISubmissionPolicy<C> where C : DataContext
        {

            #region ISubmissionPolicy<C> Members

            public void Submit(C context)
            {}

            #endregion
        }

    Και η repository γίνεται

       class Repository<T, C>
    where T:class
    where C:DataContext, new()
    {
    protected IContextPolicy<C> _contextPolicy;
    protected ISubmissionPolicy<C> _submissionPolicy;
    protected Func<T, bool> IsNew;

    #region IRepository<Customer> Members

    public T Get(Func<T,bool> criteria)
    {
    using (ContextWrapper<C> ctx = _contextPolicy.Get())
    {
    Table<T> table = ctx.Context.GetTable<T>();
    return table.SingleOrDefault(criteria);
    }
    }

    public void Save(T entity)
    {
    using (ContextWrapper<C> ctx = _contextPolicy.Get())
    {
    Table<T> table = ctx.Context.GetTable<T>();
    if (IsNew(entity))
    table.Attach(entity);
    else
    table.InsertOnSubmit(entity);

    _submissionPolicy.Submit(ctx.Context);
    }
    }

    public void SaveAll()
    {
    using (ContextWrapper<C> ctx = _contextPolicy.Get())
    {
    ctx.Context.SubmitChanges();
    }
    }

    public void Delete(T entity)
    {
    using (ContextWrapper<C> ctx = _contextPolicy.Get())
    {
    Table<T> table = ctx.Context.GetTable<T>();
    table.DeleteOnSubmit(entity);
    _submissionPolicy.Submit(ctx.Context);
    }
    }

    #endregion
    }

    Και μπορώ πλέον να ορίσω ένα CustomerRepository ως εξής:
        class CustomerRepository : Repository<Customer,MyDataContext>
        {
            public CustomerRepository()           
            {           
                IsNew = (c => c.CustomerID > 0);
                _contextPolicy = new SingleContextPolicy<MyDataContext>();
                _submissionPolicy = new DeferSubmitPolicy<MyDataContext>();
            }
        }

    Αν μάλιστα προσθέσω και ένα constructor στην Repository ο οποίος θα δέχεται τα policies ως παραμέτρους, μπορώ να ορίσω Repositories με διαφορετική συμπεριφορά σε διαφορετικά σημεία του κώδικα, για την ίδια πάντα κλάση.

    Με αντίστοιχο τρόπο μπορώ να συνεχίσω για να προσθέσω concurrency, transactions, caching και ότι άλλο μπορώ να φανταστώ. Μάλιστα, μπορώ να πάω άλλο ένα βήμα παραπέρα και να χρησιμοποιήσω κάποιο dependency inversion μηχανισμό όπως το Unity, το Spring.Net ή κάποιο δικής μου κατασκευής για να ορίζω ποιά policies θα χρησιμοποιούνται μέσω configuration.

     

    ΥΓ. Για να μην αναρωτιέται που πήγα και κατέβασα αυτές τις ιδέες, απλά αντέγραψα από ... το NHibernate. Η ιδέα των policies προέρχεται από τη C++ και τα templates και εμφανίζεται και στην C# με διάφορες παραλλαγές όπως providers, factories κλπ. Μία αναζήτηση για LINQ to SQL Repository θα επιστρέψει αρκετές υλοποιήσεις.


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  11-12-2008, 01:42 46839 σε απάντηση της 46836

    Απ: Access DataContext μέσα από το object

    Χρήστος Γεωργακόπουλος:

    Θα ψάξω να βρω κάποια διαφορετική υλοποίηση, το πραγματικό μου πρόβλημα πάντως είναι το εξής:  Έχω ένα Order που έχει πολλά OrderItems. Θέλω να κάνω raise ένα event στα OrderItems, έτσι ώστε όταν αλλάζει το quantity τους, να πιάνει το event το Order και να ξαναυπολογίζει το μεταφορικό κόστος (που βεβαίως είναι πεδίο πάνω στο Order). Το πρόβλημα είναι ότι όταν καταλάβει το Order ότι άλλαξε το quantity από ένα OrderItem, δεν μπορεί να βρει το context που βρίσκεται για να κάνει τις δουλιές που χρειάζεται.

    Γιατί να μην κάνεις το πιο απλό, να καλείς μία CalculateCost επάνω στο Order για να υπολογίσει το κόστος πριν αποθηκεύσεις τις αλλαγές? Αν τα OrderItems είναι ήδη στη μνήμη το κόστος θα είναι μικρό. Αλλά και αν δεν θέλεις να το κάνεις αυτό κάθε φορά, οι αλλαγές στα OrderItems γίνονται από δικό σου κώδικα, οπότε μπορείς να καλέσεις την CalculateCost στο τέλος του. Επιπλέον, εκτός και αν έχεις αλλάξει με το χέρι τα Associations στο LINQ to SQL, η κλάση OrderItem πρέπει να περιέχει και ένα property Order το οποίο παραπέμπει στο parent order. Μπορείς όταν τροποποιείς τα OrderItems να τα βάλεις είτε να καλέσουν την CalculateCost ή να θέσουν ένα flag το οποίο θα ελέγξεις αργότερα για να δεις αν χρειάζεται να κάνεις CalculateCost πριν σώσεις.

    Υπάρχουν πολύ ευκολότεροι τρόποι να κάνεις αυτό που θέλεις από το να παλεύεις με Reflection να βρεις ποιό είναι το parent Order (που τον έχεις ήδη) και μετά και μετά και μετά. Η χρήση ενός επιπλέον layer θα διευκολύνει πολύ τα πράγματα και θα μειώσει τον όγκο δουλειάς - εξάλλου ήδη έχεις ξοδέψει περισσότερο χρόνο ψάχνοντας απ' όσο θα χρειαζόταν για να φτιάξεις το επιπλέον layer.
    Όταν έχεις μία συγκεκριμένη μέθοδο SaveCustomer έχεις και ένα συγκεκριμένο σημείο να κάνεις validations, recalculations και ότι άλλο θέλεις. Διαφορετικά θα πρέπει να σπείρεις μία απλή διαδικασία σε πολλές διαφορετικές κλάσεις. Η πολυπλοκότητα θα είναι τόσο μεγάλη που σύντομα δεν θα ξέρεις ποιά αλλαγή προκαλεί ποιό calculation.


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  11-12-2008, 07:15 46841 σε απάντηση της 46839

    Απ: Access DataContext μέσα από το object

    Παναγιώτη τι να σου πω - πήρες πολύ φόρα!!! Big Smile

    Σχεδόν με το ένα χέρι πίσω από την πλάτη έκανες τη μισή υλοποίηση του Subsonic 3!!! Αν είναι να ψάχνεστε για "LINQ to SQL" like ORM που βασίζεται στο Repository pattern και να παίζει με όλες τι βάσεις, τότε είναι έτοιμο - μην το προσπαθείτε από μόνοι σας.

     

    George J.


    George J. Capnias: Χειροπρακτικός Υπολογιστών, Ύψιστος Γκουράρχης της Κουμπουτερολογίας
    w: capnias.org, t: @gcapnias, l: gr.linkedin.com/in/gcapnias
    dotNETZone.gr News
  •  11-12-2008, 15:55 46849 σε απάντηση της 46841

    Απ: Access DataContext μέσα από το object

    George J. Capnias:

    Παναγιώτη τι να σου πω - πήρες πολύ φόρα!!! Big Smile

    Σχεδόν με το ένα χέρι πίσω από την πλάτη έκανες τη μισή υλοποίηση του Subsonic 3!!! Αν είναι να ψάχνεστε για "LINQ to SQL" like ORM που βασίζεται στο Repository pattern και να παίζει με όλες τι βάσεις, τότε είναι έτοιμο - μην το προσπαθείτε από μόνοι σας.

     

    George J.


    Μάλλον διαφωνώ. Το Repository είναι pattern και όχι συγκεκριμένη υλοποίηση. Δεν υπάρχει ORM που βασίζεται στο Repository Pattern, το Repository Pattern είναι ένας εύκολος τρόπος να κρύψεις τον κώδικα που αφορά τo ORM από την υπόλοιπη εφαρμογή. Ειδικά το Repository του Subsonic (το οποίο βρίσκεται στο αρχείο  Repository.tt) είναι εντελώς βασικό, παρόμοιο με την πρώτη έκδοση της Repository που έγραψα νωρίτερα. Ο χρόνος που χρειάστηκε η πρώτη έκδοση ήταν κάτι λιγότερο από 10 λεπτά.

    Δεν μπορεί κανείς να πει ότι το Repository είναι έτοιμο όπως δεν μπορεί να πει ότι το Singleton είναι έτοιμο. Υπάρχουν διάφορες υλοποιήσεις με διαφορετικούς συμβιβασμούς η καθεμία. Για παράδειγμα, το τυπικό Singleton για το .NET έχει το θέμα ότι δεν υπάρχει απόλυτως έλεγχος στο πότε δημιουργείται το instance:

    public sealed class Singleton
    {
    static readonly Singleton instance=new Singleton();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton()
    {
    }

    Singleton()
    {
    }

    public static Singleton Instance
    {
    get
    {
    return instance;
    }
    }
    }


    Έτσι και με το Repository. Ένα απλό repository με generics όπως αυτό του Subsonic σε απαλλάσει από το να χειρίζεται contexts μέσα στον κώδικα σου. Τί γίνεται όμως αν θέλεις οι αλλαγές να γίνονται άμεσα? Ή να μαζεύονται στο τέλος? Τί γίνεται με το caching? Ούτε αυτό πρέπει να το ξέρει η υπόλοιπη εφαρμογή σου, οπότε λογικά θα θέλεις να το κρύψεις πίσω από την Get<>.

    Ο λόγος που έφτιαξα την πιο ευέλικτη μορφή του Repository ήταν ότι ο cgeo είχε ήδη κάνει ερωτήσεις για caching και διαγραφές, οπότε υποψιάζομαι ότι η απλή έκδοση δεν του αρκεί. Άσε που στο thread για caching κουβεντιάζουν για τους διαφορετικούς τρόπους που μπορεί να γίνει το caching αντικειμένων, οπότε μπορώ να θεωρώ ότι μία καρφωτή υλοποίηση ... δεν ικανοποιεί.

    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  11-12-2008, 18:10 46855 σε απάντηση της 46838

    Απ: Access DataContext μέσα από το object

    Παναγιώτη tnx!. θα το μελετήσω και θα σας πω.

    Χρήστος Γεωργακόπουλος
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems