Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Matthieu MEZIL

I love .Net

Abonnements

Actualités

Locations of visitors to this page English blog

Manipulation d'EDM : les relations many to many, test

J'en avais parlé aux techdays mais je vais profiter du post d'Alex pour en reparler et pour aller un peu plus loin que ce que j'avais dit aux techdays.

En base, on ne peut pas avoir de relation many to many. Il faut donc passer par une table intermédiaire qui ne sert bien souvent à rien d'autre. Avec EDM, on va se passer d'une entité intermédiaire (cela est fait automatiquement par le wizard).

Cependant, dans le cas où la table intermédiaire est utile (ie, elle a d'autre colonnes que les clés étrangères qui sont aussi ces clés primaires), c'est différent. Dans Northwind par exemple, on a

   Orders <- (1-*) -> OrderDetails <- (*-1) -> Products

avec OrderDetails définit avec les colonnes suivantes :

  [OrderID] [int] NOT NULL,
  [ProductID] [int] NOT NULL,
  [UnitPrice] [money] NOT NULL CONSTRAINT [DF_Order_Details_UnitPrice]  DEFAULT (0),
  [Quantity] [smallint] NOT NULL CONSTRAINT [DF_Order_Details_Quantity]  DEFAULT (1),
  [Discount] [real] NOT NULL CONSTRAINT [DF_Order_Details_Discount]  DEFAULT (0)

L'idée serait de se passer de OrderDetails.

Très simple, il suffit de supprimer l'entité OrderDetail puis de rajouter une relation entre Order et Product et enfin de mapper cette relation sur la table OrderDetails.

Cependant, ça ne marche pas car UnitPrice, Quantity et Discount n'authorisent pas NULL.

Je me suis arrêter là aux Techdays.

Allons un peu plus loin. Comme une valeur par défaut est définie, on va pouvoir tricher et changer le ssdl afin de lui faire croire que ces colonnes authorisent le NULL en se disant qu'il va utiliser les default values de SQL Server. Non Sad Ca compile maintenant mais quand on va vouloir ajouter un Product à un Order, il va essayer d'insérer NULL.

Même si le wizard ne l'a pas importé, il est possible dans le ssdl d'utiliser l'attribut xml Default mais manque de chance ça ne marche toujours pas. Sad

Une solution simple qui marche est la suivante : supprimer les colonnes UnitPrice, Quantity et Discount du ssdl. L'EF ne les renseignera plus et elles ont déjà une valeur par défaut en base.

Cependant, quand on y réfléchit bien, dans le cas présent, ce n'est pas forcément l'idéal d'utiliser les valeurs par défaut. En effet, on voudra avoir une quantité différente de 1 et un prix unitaire différent de 0. Niveau objet, pas de pb, dans la propriété Products de Order, il est possible de retourner plusieurs fois la même instance de Product (une fois par quantité). Cependant comment faire cela ? Mon idée était de passer par des fonctions ssdl (procédure stockée ou code directement écrit en SQL dans le ssdl) et d'associer ces fonctions à l'INSERT et le DELETE de la relation. Mais même comme ça je n'ai pas réussi. Sad Alors en attendant la super réponse d'Alex, je propose une solution alternative qui implique moins EDM :

Pour commencer, dans mon modèle, j'ai défini les navigate property Order -> OderDetail et Product -> OrderDetail private.

Ensuite, j'ai étendu les classes Order et Product. Voici ce que ça donne pour Order :

internal static class ContextHelper

{

    public static NorthwindEFEntities GetContext(IEntityWithRelationships entity)

    {

        return typeof(RelationshipManager).GetProperty("Context", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).

                        GetValue(entity.RelationshipManager, null) as NorthwindEFEntities;

    }

}

 

partial class Order

{

    private float _discount;

    public float Discount

    {

        get { return _discount; }

        set { _discount = value; }

    }

 

    private MMEntityCollection<Product> _products;

    public MMEntityCollection<Product> Products

    {

        get

        {

            if (_products == null)

            {

                _products = new MMEntityCollection<Product>(ContextHelper.GetContext(this as IEntityWithRelationships),

                    () =>

                    {

                        var context = ContextHelper.GetContext(this as IEntityWithRelationships); // I did it again because context can change

                        if (!OrderDetails.IsLoaded)

                            OrderDetails.Load();

                        var products = new List<Product>();

                        foreach (OrderDetail od in OrderDetails)

                        {

                            if (!od.ProductReference.IsLoaded && (od.EntityState & (System.Data.EntityState.Added | System.Data.EntityState.Detached)) == 0)

                                od.ProductReference.Load();

                            for (int i = 0; i < od.Quantity; i++)

                                products.Add(od.Product);

                        }

                        return products;

                    }, (p) =>

                    {

                        var context = ContextHelper.GetContext(this as IEntityWithRelationships); // I did it again because context can change

                        if (!OrderDetails.IsLoaded)

                            OrderDetails.Load();

                        // We suppose one order can't be change by two different persons on same time

                        context.Refresh(RefreshMode.StoreWins, OrderDetails.Where(od => od.ProductID == p.ProductID));

                        var addOD = OrderDetails.FirstOrDefault(od => od.ProductID == p.ProductID);

                        if (addOD == null)

                        {

                            addOD = new OrderDetail { Order = this, Product = p, Quantity = 1, UnitPrice = p.UnitPrice.HasValue ? p.UnitPrice.Value : 0, Discount = Discount };

                            context.AddToOrderDetails(addOD);

                        }

                        else

                            addOD.Quantity++;

                    }, (p) =>

                    {

                        var context = ContextHelper.GetContext(this as IEntityWithRelationships); // I did it again because context can change

                        if (!OrderDetails.IsLoaded)

                            OrderDetails.Load();

                        // We suppose one order can't be change by two different persons on same time

                        context.Refresh(RefreshMode.StoreWins, OrderDetails.Where(od => od.ProductID == p.ProductID));

                        var removeOD = OrderDetails.FirstOrDefault(od => od.ProductID == p.ProductID);

                        if (removeOD == null)

                            return false;

                        else

                        {

                            removeOD.Quantity--;

                            if (removeOD.Quantity == 0)

                                context.DeleteObject(removeOD);

                            return true;

                        }

                    })

                    {

                        ClearMethod = (p) =>

                            {

                                var context = ContextHelper.GetContext(this as IEntityWithRelationships); // I did it again because context can change

                                if (!OrderDetails.IsLoaded)

                                    OrderDetails.Load();

                                // We suppose one order can't be change by two different persons on same time

                                context.Refresh(RefreshMode.StoreWins, OrderDetails.Where(od => od.ProductID == p.ProductID));

                                var removeOD = OrderDetails.FirstOrDefault(od => od.ProductID == p.ProductID);

                                if (removeOD != null)

                                {

                                    removeOD.Quantity = 0; // useful until saving changes

                                    context.DeleteObject(removeOD);

                                }

                            }

                    };

            }

            return _products;

        }

    }

}

 

public class MMEntityCollection<TEntity> : ICollection<TEntity>, IEnumerable<TEntity>, IEnumerable, IListSource where TEntity : EntityObject

{

    private Func<IEnumerable<TEntity>> _selectMethod;

    private Action<TEntity> _addMethod;

    private Func<TEntity, bool> _removeMethod;

    private Action<TEntity> _clearMethod;

 

    public MMEntityCollection(NorthwindEFEntities context, Func<IEnumerable<TEntity>> selectMethod, Action<TEntity> addMethod, Func<TEntity, bool> removeMethod)

    {

        if (selectMethod == null || addMethod == null || removeMethod == null)

            throw new NullReferenceException();

        _selectMethod = selectMethod;

        _addMethod = addMethod;

        _removeMethod = removeMethod;

    }

 

    public Action<TEntity> ClearMethod

    {

        get { return _clearMethod; }

        set { _clearMethod = value; }

    }

 

    public void Add(TEntity item)

    {

        if (_addMethod != null)

            _addMethod(item);

    }

    public void Clear()

    {

        foreach (var entity in this.Distinct())

            Clear(entity);

    }

    public void Clear(TEntity item)

    {

        if (ClearMethod == null)

        {

            foreach (var entity in this.Where(i => i == item))

                Remove(entity);

        }

        else

        {

            ClearMethod(item);

        }

    }

    public bool Contains(TEntity item)

    {

        return Contains(item);

    }

    public void CopyTo(TEntity[] array, int arrayIndex)

    {

        CopyTo(array, arrayIndex);

    }

    public int Count

    {

        get { return Count; }

    }

    public bool IsReadOnly

    {

        get { return false; }

    }

    public bool Remove(TEntity item)

    {

        if (_removeMethod != null)

            return _removeMethod(item);

        return false;

    }

    public IEnumerator<TEntity> GetEnumerator()

    {

        if (_selectMethod == null)

            return null;

        return _selectMethod().GetEnumerator();

    }

    IEnumerator IEnumerable.GetEnumerator()

    {

        return GetEnumerator();

    }

    bool IListSource.ContainsListCollection

    {

        get { return false; }

    }

    IList IListSource.GetList()

    {

        if (_selectMethod == null)

            return null;

        return _selectMethod().ToList();

    }

}

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Publié lundi 25 février 2008 07:52 par Matthieu MEZIL

Classé sous : , , , , ,

Commentaires

Pas de commentaires

Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- [WPF] Nouvel article sur c2i.fr par Richard Clark le il y a 1 heure et 41 minutes

- F# nouvelle CTP 1.9.6.2 (update) par Pierrick's Blog le il y a 5 heures et 46 minutes

- La suite ...Proposition de collaboration rédactionnelle entre les communautés de développeurs et Microsoft France par LucasR le 09-05-2008, 17:45

- [Fun] Votre simulateur de vol avec Microsoft ESP par Julien Chable le 09-05-2008, 12:02

- [Best Practices] Customisation du My Site : Comment le modifier en amont et en aval par The Mit's Blog le 09-05-2008, 10:47

- Patrick Tisseghem s'en est allé ... par The Mit's Blog le 09-05-2008, 10:04

- MS AutoCollage par alex# le 09-05-2008, 09:18

- Un grand SharePointeur nous a quitte : Patrick Tisseghem manquera à la communauté ! par RedoBlog - The .NET Gentleman !!! le 09-05-2008, 08:52

- [WPF] Comment charger dynamiquement un fichier XAML qui définit des eventhandler ? par Thomas Lebrun le 09-04-2008, 10:56

- Article sur le filtrage des modèles de site SharePoint par The Grib's Lair [Sébastien PICAMELOT - MVP SharePoint] le 09-04-2008, 00:11