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

- Monitoring et Patron de méthode par Le blog de Marc Ranchin le il y a 2 heures et 16 minutes

- ADO.NET Data Services Hooking POC v2 par Matthieu MEZIL le il y a 2 heures et 30 minutes

- Back from NYC ! par .net is good... C# is better ;) le il y a 3 heures et 8 minutes

- Hello World! par Le blog de hamid le il y a 11 heures et 16 minutes

- MSBuild Extension Pack sur codeplex par Michel Perfetti [Miiitch] le il y a 12 heures et 35 minutes

- TCB : Travailler en équipe sans réseau par The Mit's Blog le il y a 15 heures et 46 minutes

- Accès anonyme et les pages Forms / viewlsts.aspx... par Nicolas Humann le il y a 19 heures et 39 minutes

- l'Atelier 4 du coach C# est disponible par Bernard Fedotoff le il y a 21 heures et 14 minutes

- [WPF] Formatter l’affichage lors d’un binding, via StringFormat par Thomas Lebrun le 10-07-2008, 10:22

- WSC08 : Le bilan, Les Photos, Les Webcasts à voir ou à revoir par Blog de Daniel TIZON [daniel] le 10-07-2008, 01:14