Un exemple d'architecture .Net complète

13. décembre 2011 09:30 by Tommy in Développement  //  Tags: , , , , , , , , , , , ,   //   Commentaires (7)   //  Partager sur Facebook  / Twitter

 

Ces derniers temps, j'ai pas mal réfléchi à la meilleure façon de séparer un projet .Net en couches. Classes métier, accès simplifié à la BDD, tests unitaires avec bouchons, injection de dépendances, mapping, ... Tellement de problématiques à gérer lorsqu'on crée une architecture .Net.

Du coup je me suis fixé un objectif simple : créer une architecture de solution .Net "propre, avec

  • des couches bien séparées (classes métier, logique métier, accès aux données, services, tests unitaires, interfaces utilisateur),
  • aucun Framework pour les tests unitaires (histoire que le code reste simple à appréhender et à comprendre pour un débutant),
  • de l'injection de dépendances pour pouvoir brancher facilement les tests unitaires sur des "bouchons",
  • enfin, un accès aux données simplifié, sans SQL ni procédures stockées (donc avec Linq et Entity Framework).

Au final, j'ai créé un simpliste "moteur de blog", capable d'afficher des articles puisés dans une BDD SQL Server 2008 Express. Pour éviter que ce beau projet .Net ne se perde dans les méandres de mon disque dur avant de sombrer dans l'oubli, je me suis dit que le partager avec vous serait le meilleur moyen de lui offrir une seconde vie. Qui sait, peut être sera-t-il utile à de nombreuses personnes ! Smile

Le voici donc : projet HTA.DemoBlog.zip (1,54 mb). Le code source est sous licence LGPL, donc vous pouvez l'utiliser comme bon vous semble ! Wink

Voici, déjà, ce qu'il y a à noter sur cette architecture (pour chaque couche, j'ai créé un solution folder) :

1) Couche Business

  • BusinessEntities : les objets métier, qu'on manipulera dans l'application. L'intérêt est d'avoir des objets complètement détachés de la BDD et qu'on pourra manipuler dans une autre application, dans une bibliothèque de classes, dans un module à part, ... Les objets ne sont vraiment que des conteneurs indépendants de tout le reste.
  • BusinessLogic : Tous les traitements métier. Ici on retrouvera la logique métier, les Try Catch, les calculs compliqués, les logs, ... Ce code sera celui utilisé par les applications (qu'il s'agisse de Winforms, d'ASP.Net, de WPF, ...). Cette couche interagit avec la couche DataAccess et utilise les objets BusinessEntities.

2) Couche Resources

  • DataAccessInterfaces : les interfaces qui définiront les méthodes de la couche DataAccess.
    Intérêt double : pouvoir utiliser des bouchons (mock) qui implémentent cette interface dans les tests unitaires, afin d'avoir des tests unitaires indépendants de la BDD et donc qui peuvent être lancés à chaque build / release.
    L'autre intérêt est de pouvoir changer de source de données ou les méthodes qui interagissent avec la BDD sans avoir à recoder des choses dans d'autres couches (vu qu'elles implémentent ces interfaces et non pas les objets DataAccess directement).
  • DataAccess : la couche qui interroge la BDD. Elle renvoie des objets BusinessEntities et possède des méthodes définies dans DataAccessInterfaces. Vu qu'elle est détachée, on peux utiliser ce qu'on veut pour stocker nos données : Entity Framework, Linq 2 Sql, des procédures stockées, des fichiers XML, ... Si on doit changer de source de données ou de façon d'exploiter notre source de données, on n'aura qu'à recréer une couche qui implémentera les interfaces DataAccessInterfaces, et le tour sera joué.

3) Couche Tests

  • Tests.Mock : des classes bouchon (donc avec des données en dur) qui implémentent les interfaces DataAccessInterfaces.
  • Tests : les tests unitaires. Ces tests testent la couche BusinessLogic via les mocks plutôt que via la couche DataAccess (afin de ne tester que la logique métier, sans risquer de modifier les données en base).

4) Couche Interfaces (UI)

  • La couche qui contiendra les interfaces utilisateur (WebApp, Winforms, WPF, ASP.Net MVC, ...) qui utiliseront uniquement le code métier dans la couche BusinessLogic et qui manipulera des objets BusinessEntities.

5) Couche Core

  • La couche Core contient des méthodes génériques utiles au projet mais qui pourront être réutilisées / utiles pour d'autres projets. (Cryptographie, Logs, ...)

6) Couche Database

  • Le projet Database permet de gérer le contenu de la BDD SQL Server, de mettre à jour les procédures stockées et de modifier les scripts de déploiement, sans avoir à le faire à la main.

7) Couche Service

  • Cette couche permet de créer des services qui utilisent la couche métier et donc de rendre le code accessibles de l'extérieur. (Pour l'utiliser, par exemple, via une application web distante ou via une application Windows Phone 7).

 L'architecture ressemble donc à ceci :

 

Voyons maintenant le code un peu plus en détail.

Dans un premier temps, intéressons-nous à la classe ArticleBE, de la couche BusinessEntities, qui définit la structure d'un article :

    public class ArticleBE : ICloneable
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public string Author { get; set; }
        public string Tags { get; set; }
        public DateTime Date { get; set; }

        public object Clone()
        {
            ArticleBE result = new ArticleBE()
            {
                Id = Id,
                Date = Date,
                Author = Author.Clone() as string,
                Content = Content.Clone() as string,
                Tags = Tags.Clone() as string,
                Title = Title.Clone() as string
            };
            return result;
        }
    }

Comme vous pouvez le voir, le code est vraiment le plus simple possible. Nous avons juste nos propriétés et la méthode Clone (j'expliquerais l'intérêt de la méthode Clone plus tard). Pas de dépendances, pas de contraintes, pas de spécificités, ... ce code peut véritablement être réutilisé n'importe où. Il est possible, dans le cas d'une application demandant une exposition de services, de rendre notre classe Serializable.

Maintenant, nous allons travailler sur la couche DataAccessInterfaces. Dans cette couche, nous allons définir les interfaces et donc les méthodes que notre couche d'accès aux données devra mettre à disposition. Ici, étant donné que nous ne travaillons que sur une table, nous n'allons créer qu'une interface, à savoir IArticleDA :

public interface IArticleDA : IDisposable
    {
        List<ArticleBE> GetArticles();
        ArticleBE GetArticleById(int idArticle);
        void AddArticle(ArticleBE article);
        void EditArticle(ArticleBE article);
        void DeleteArticle(ArticleBE article);
        void Save();
    }

Le code, encore une fois, est assez simple. Nos classes qui implémenteront IArticleDA devront nous fournir toutes ces méthodes + Dispose, puisque nous implémentons IDisposable.

Maintenant que cette interface est définie, nous allons voir la partie accès aux données. Comme vous pouvez le voir, j'ai ajouté au projet DataAccess un ADO.Net Entity Data Model pour pouvoir interroger la base de données très facilement via Linq. Cependant, pour pouvoir transformer nos objets Entity en objets BusinessEntities, j'ai créé une class ArticleMap dans le dossier Mapping :

public static class ArticleMap
    {
        internal static List<ArticleBE> Map(List<Article> articles)
        {
            if (articles == null || articles.Count == 0)
            {
                return new List<ArticleBE>();
            }

            List<ArticleBE> result = new List<ArticleBE>();

            foreach (Article article in articles)
            {
                result.Add(Map(article));
            }

            return result;
        }

        internal static ArticleBE Map(Article article)
        {
            if (article == null)
            {
                return null;
            }

            ArticleBE result = new ArticleBE();

            result.Id = article.IdArticle;
            result.Author = article.Author;
            result.Content = article.FullContent;
            result.Date = article.Date;
            result.Tags = article.Tags;
            result.Title = article.Title;

            return result;
        }

        internal static Article Map(ArticleBE article, Article result = null)
        {
            if (result == null)
            {
                result = new Article();
            }

            result.Author = article.Author;
            result.FullContent = article.Content;
            result.Date = article.Date;
            result.Tags = article.Tags;
            result.Title = article.Title;

            return result;
        }
    } 

Le code est, encore une fois, très simple. Déjà, toutes les méthodes sont en internal car elles ne doivent être accessibles qu'aux classes de notre projet DataAccess. Le mapping Article => ArticleBe est simple, mais le mapping ArticleBE => Article a une petite spécificité. En effet, dans le constructeur, j'ai passé un paramètre facultatif : un objet Article.

Pourquoi ? Lorsqu'on veut mettre à jour un objet Article en base depuis un objet ArticleBE, plutôt que d'avoir à modifier chaque propriété dans notre code d'accès aux données, il suffit d'appeler la méthode Map avec comme second paramètre notre objet Article à mettre à jour. (Il s'agit de la méthode la plus simple que j'ai pu trouver pour mettre à jour un objet Entity à partir d'une classe BusinessEntities.)

Intéressons-nous ensuite au code de notre classe ArticleDA, qui implémente IArticleDA :

public class ArticleDA : IArticleDA
    {
        private HTADemoBlogEntities _entities = null;

        public ArticleDA()
        {
            _entities = new HTADemoBlogEntities();
        }

        public List<ArticleBE> GetArticles()
        {
            return ArticleMap.Map(_entities.Article.OrderByDescending(e => e.IdArticle).ToList());
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            return ArticleMap.Map(_entities.Article.Where(e => e.IdArticle == idArticle).FirstOrDefault());
        }

        public void AddArticle(ArticleBE article)
        {
            _entities.Article.AddObject(ArticleMap.Map(article));
        }

        public void EditArticle(ArticleBE article)
        {
            Article existingArticle = _entities.Article.Where(e => e.IdArticle == article.Id).FirstOrDefault();

            if (existingArticle != null)
            {
                ArticleMap.Map(article, existingArticle);
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            Article existingArticle = _entities.Article.Where(e => e.IdArticle == article.Id).FirstOrDefault();

            if (existingArticle != null)
            {
                _entities.Article.DeleteObject(existingArticle);
            }
        }

        public void Save()
        {
            _entities.SaveChanges();
        }

        public void Dispose()
        {
            if (_entities != null)
            {
                _entities.Dispose();
            }
        }
    }

Rien de très compliqué. J'ai juste une méthode Save (déclarée dans IArticleDA) qui permet de sauver en base les modifications faites à nos objets Article, à l'aide de SaveChanges. Ca nous permet, dans le cas où nous avons de nombreux articles à créer ou à modifier, de ne pas faire de SaveChanges à chaque fois. Nous n'appellerons la méthode qu'à la fin de notre traitement métier.

Voyons maintenant la couche BusinessLogic, avec la classe ArticleBL :

public class ArticleBL : IDisposable
    {
        private IArticleDA _dataFactory = null;

        public ArticleBL()
            : this(new ArticleDA())
        {

        }

        public ArticleBL(IArticleDA dataFactory)
        {
            _dataFactory = dataFactory;
        }

        public List<ArticleBE> GetArticles()
        {
            try
            {
                return _dataFactory.GetArticles();
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
                return new List<ArticleBE>();
            }
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            try
            {
                if (idArticle < 0)
                {
                    throw new ArgumentException();
                }

                return _dataFactory.GetArticleById(idArticle);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
                return null;
            }
        }

        public void AddArticle(ArticleBE article)
        {
            try
            {
                if (string.IsNullOrEmpty(article.Title)
                    || string.IsNullOrEmpty(article.Author)
                    || article.Date == DateTime.MinValue)
                {
                    throw new ArgumentException();
                }

                _dataFactory.AddArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void EditArticle(ArticleBE article)
        {
            try
            {
                if (article.Id < 0
                    || string.IsNullOrEmpty(article.Title)
                    || string.IsNullOrEmpty(article.Author)
                    || article.Date == DateTime.MinValue)
                {
                    throw new ArgumentException();
                }

                _dataFactory.EditArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            try
            {
                if (article.Id < 0)
                {
                    throw new ArgumentException();
                }

                _dataFactory.DeleteArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void Save()
        {
            try
            {
                _dataFactory.Save();
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void Dispose()
        {
            try
            {
                if (_dataFactory != null)
                {
                    _dataFactory.Dispose();
                }
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }
    }

Ici, le code devient un peu plus intéressant. Pour ne pas manipuler directement d'objets ArticleDA (ce qui permet de changer très facilement de source de données), le constructeur de ma classe demande en paramètre un objet IArticleDA. Si aucun objet implémentant IArticleDA n'est passé au constructeur, la classe utilisera le constructeur vide qui créera automatiquement un objet ArticleDA qu'elle passera au constructeur avec paramètre.

Vous pouvez également voir que les tests "métier", à savoir une tentative de lecture d'un article ayant un Id < 0, la tentative d'ajout d'un article sans titre, ... déclencheront la levée d'une ArgumentException. Le code de chaque méthode étant dans un Try Catch, cette exception sera attrapée et logguée (voir classe LogError dans la couche Core).

Enfin, la méthode Dispose appellera la méthode Dispose de notre classe implémentant IArticleDA, ce qui coupera la connexion à la BDD.

Voyons maintenant un exemple d'utilisation de nos méthodes métier. Si nous nous rendons dans le contrôleur ArticleController de l'application MVC, nous pouvons voir ceci :

public class ArticleController : Controller
    {
        public ActionResult Index(string message = "")
        {
            ViewBag.Message = message;

            using (ArticleBL business = new ArticleBL())
            {
                return View(business.GetArticles());
            }
        }

        public ActionResult Details(int id)
        {
            using (ArticleBL business = new ArticleBL())
            {
                return View(business.GetArticleById(id));
            }
        }

        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Create(ArticleBE article)
        {
            using (ArticleBL business = new ArticleBL())
            {
                business.AddArticle(article);
                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been created." });
        }

        public ActionResult Edit(int id)
        {
            ArticleBE article = null;

            using (ArticleBL business = new ArticleBL())
            {
                article = business.GetArticleById(id);
            }

            if (article == null)
            {
                return RedirectToAction("Index", new { message = "This article doesn't exist." });
            }

            return View(article);
        }

        [HttpPost]
        public ActionResult Edit(ArticleBE article)
        {
            using (ArticleBL business = new ArticleBL())
            {
                business.EditArticle(article);
                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been updated." });
        }

        public ActionResult Delete(int id)
        {
            ArticleBE article = null;

            using (ArticleBL business = new ArticleBL())
            {
                article = business.GetArticleById(id);

                if (article == null)
                {
                    return RedirectToAction("Index", new { message = "This article doesn't exist." });
                }

                business.DeleteArticle(article);

                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been deleted." });
        }

Et oui, pour utiliser nos données en base, il suffit de faire un using. A la sortie du using, la méthode Dispose de l'objet sera appelée, et donc la connexion sera automatiquement coupée. Cette méthode a ses avantages (sécurité, pas de risques de fuite mémoire) comme ses inconvénients (nombreuses connexions / déconnexions), mais rien ne vous empêche de la modifier.

Regardons enfin, en détail, les tests unitaires. Déjà, dans la couche Tests.Mock, le code est assez parlant :

public class MockArticleDA : IArticleDA
    {
        private List<ArticleBE> articles = new List<ArticleBE>()
        {
            new ArticleBE() {
                Id = 1,
                Title = "Demo 1",
                Tags = "demo,article,.Net",
                Date = DateTime.UtcNow,
                Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            },
            new ArticleBE() {
                Id = 2,
                Title = "Demo 2",
                Tags = "demo,article,PHP",
                Date = DateTime.UtcNow,
                Content = "PHP : Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            },
            new ArticleBE() {
                Id = 3,
                Title = "Demo 3",
                Tags = "demo,article,Java",
                Date = DateTime.UtcNow,
                Content = "JAVA : Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            }
        };

        public List<ArticleBE> GetArticles()
        {
            // we clone the list to avoid to avoid any ref problem
            return articles.Select(e => e.Clone() as ArticleBE).ToList();
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            ArticleBE result = articles.Where(e => e.Id == idArticle).FirstOrDefault();

            if (result != null)
            {
                // we clone the item to avoid to avoid any ref problem
                return result.Clone() as ArticleBE;
            }

            return null;
        }

        public void AddArticle(ArticleBE article)
        {
            article.Id = articles.Max(e => e.Id) + 1;
            articles.Add(article);
        }

        public void EditArticle(ArticleBE article)
        {
            ArticleBE result = articles.Where(e => e.Id == article.Id).FirstOrDefault();
            if (result != null)
            {
                result.Author = article.Author;
                result.Content = article.Content;
                result.Date = article.Date;
                result.Tags = article.Tags;
                result.Title = article.Title;
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            ArticleBE result = articles.Where(e => e.Id == article.Id).FirstOrDefault();
            if(result != null) 
            {
                articles.Remove(result);
            }
        }

        public void Save()
        {

        }

        public void Dispose()
        {

        }
    }

On voit donc, ici, qu'on implémente bien notre interface d'accès aux données IArticleDA. Ainsi, pour tester notre couche de logique métier sur cette classe, il nous suffira de passer un objet MockArticleDA dans le constructeur de notre classe métier, et le tour est joué !

Notre classe MockArticleDA possède une liste d'articles créée à chaque construction d'un objet. La spécificité, ici, est que j'utilise la méthode Clone de nos objets ArticleBE pour ne renvoyer que des copies de ces objets à notre classe de tests. Cela permet d'éviter de modifier involontairement les données de nos objets par référence (par exemple dans le cas d'un test de modifications des données sans sauvegarde).

Enfin, je vous laisse vous intéresser à la classe ArticleBLTest, dont voici 2/3 méthodes :

[TestInitialize()]
        public void MyTestInitialize()
        {
            IArticleDA mockDataFactory = new MockArticleDA();
            business = new ArticleBL(mockDataFactory);
        }

        [TestMethod()]
        public void DeleteArticle_Generic_Test()
        {
            // params
            int nbArticles = 3;
            // --

            // we check if we have 3 articles
            List<ArticleBE> articles = business.GetArticles();
            Assert.AreEqual(nbArticles, articles.Count);

            // we delete the article
            business.DeleteArticle(articles.First());

            List<ArticleBE> actual = business.GetArticles();
            Assert.AreEqual(nbArticles - 1, actual.Count);
        }

        [TestMethod()]
        public void GetArticleById_With_Wrong_Parameters()
        {
            // params
            int fakeId = -1;
            // --

            ArticleBE article = business.GetArticleById(fakeId);

            Assert.IsNull(article);
        }

        [TestMethod()]
        public void AddArticle_With_Wrong_Parameters()
        {
            // params
            string author = "Me";
            string content = "It's not working !";
            string title = "Test article";
            string tags = "great,article,news";
            DateTime now = DateTime.Now;
            int nbArticles = 3;
            // --

            // we check if we have 3 articles
            List<ArticleBE> articles = business.GetArticles();
            Assert.AreEqual(nbArticles, articles.Count);

            ArticleBE article1 = new ArticleBE()
            {
                Author = author,
                Content = content,
                Tags = tags,
                Title = title
            };

            ArticleBE article2 = new ArticleBE()
            {
                Content = content,
                Date = now,
                Tags = tags,
                Title = title
            };

            ArticleBE article3 = new ArticleBE()
            {
                Author = author,
                Content = content,
                Date = now,
                Tags = tags,
            };

            // we create the articles. it shouldn't work
            business.AddArticle(article1);
            business.AddArticle(article2);
            business.AddArticle(article3);

            articles = business.GetArticles();

            // we check if we still have 3 articles
            Assert.AreEqual(nbArticles, articles.Count);
        }

Il est à noter que la méthode MyTestInitialize est appelée avant chaque test unitaire. Ensuite, pour chaque test, on vérifie les données déjà présentes, les données à la fin du test, et, en cas d'erreur, on verra tout de suite que le test a échoué. Comme vous avez pu le voir, mes méthodes de tests ont des noms très clairs, afin de savoir, au premier coup d'oeil, pourquoi nos tests ont pu échouer.

Je pense avoir fait le tour des spécificités du code de cette minuscule application. Si vous ne comprenez pas quelque chose, ou si vous avez besoin d'une précision, n'hésitez pas à demander dans les commentaires.

Enfin, pour déployer cette application chez vous, il vous suffit de restaurer la BDD dans un SQL Server 2008 (script Backup.bak, dans le zip fourni) et de modifier les chaînes de connexion dans les 3 fichiers de configurations (HTA.DemoBlog.DataAccess, HTA.DemoBlog.WebService et HTA.DemoBlog.Tests).

N'hésitez donc pas à tester cette "application" (HTA.DemoBlog.zip (1,54 mb)) et à me donner votre avis. Wink

Bonne journée à tous !

P.S. Oui je sais, le code n'est pas commenté, mais étant donné la simplicité des classes, je n'ai pas pensé que c'était nécessaire.

P.S.2 Quand j'aurais le temps, je ferais probablement une autre version un peu plus poussée avec Moq + StructureMap.

P.S.3 Je crois que c'est mon article le plus long depuis le début de mon blog. 7869 mots, pour presque 30 000 caractères. L'équivalent d'un document Word de 15 pages ! Wouah !

image modifiée de Bert Kaufmann, sous licence CC

Réinitialiser le compteur d'une clef dans SQL Serveur 2008

12. mai 2011 10:05 by Tommy in Développement  //  Tags: , , ,   //   Commentaires (1)   //  Partager sur Facebook  / Twitter

Article technique / développement

Foule en délire, 15h07, ce jeudi 12 mai 2011 :

 "L'astuce du jour ! L'astuce du jour ! L'astuce du jour !

- J'arrive public adoré !"

Après cette petite entrée théatrale qui vous aura laissé tout émoustillé (ou pas), voici l'astuce du jour : comment réinitialiser un compteur dans SQL Server 2008. En effet, quand vous renseignez un champ comme "identité" dans SQL Server 2008, la valeur de ce champs sera automatiquement incrémentée à chaque insertion de données (y compris après un rollback mais ceci est une autre histoire).

Or, lorsque vous utilisez votre base de données pour des tests, ce n'est pas gênant, mais il serait agréable de pouvoir réinitialiser cet index pour recommencer avec des identifiants plus pertinents (1, 2, ...) non ?

Comment faire ? Tout simplement grâce à une requête SQL tout ce qu'il y a de plus simple :

DBCC CHECKIDENT ('schema.Ma_Table', RESEED, 0)

Grâce à cette commande, votre BDD aura enfin des index propres ! On dit merci qui ? ;)

Je vous recommande d'utiliser cette commande dans vos scripts de purge / déploiement, comme ceci par exemple :

DELETE FROM schema.Ma_Table

DBCC CHECKIDENT ('schema.Ma_Table', RESEED, 0)

Insert into schema.Ma_Table Values ...

Bon développement à tous !

image de daggett2008

Autoriser les modifications importantes sur SQL Server Management Studio

2. mai 2011 08:13 by Tommy in Développement, Systèmes et réseaux  //  Tags: , , ,   //   Commentaires (2)   //  Partager sur Facebook  / Twitter

Article technique / développement

Une petite astuce que j'aurais aimé connaître quand j'ai commencé à utiliser SQL Server Management Studio.

Lorsque vous utilisez Microsoft SQL Server Management Studio pour créer une base de données (à la main j'entends), il peut arriver que vous ayez un message d'erreur fort ennuyeux à la sauvegarde d'une table après modifications (ajout d'une relation, modification de champs, ...). Ce charmant message vous dit : "L'enregistrement des modifications n'est pas autorisé. Les modifications effectuées nécessitent que les tables suivantes soient supprimées puis recréées. Vous avez apporté des modifications à une table qui ne peut pas être recréée ou activé l'option Empêcher l'enregistrement de modifications qui nécessitent une recréation de la table."

 

Ok très bien, mais où l'enlève-t-on cette option ?

Tout simplement dans "Outils / Options / Concepteurs / Empêcher l'enregistrement de modifications qui nécessitent une recréation de la table".

 

Une fois cette option décochée, vous pourrez enfin modifier vos tables, vos relations et vos champs comme bon vous semble !

(Je sais que cette option n'est pas si dure à trouver dans Management Studio 2008, mais si je peux faire gagner quelques minutes à certains d'entre vous, tant mieux.)

Bienvenue

Logo Tommy

Bonjour à tous et bienvenue sur mon blog. Je me présente : Tommy, ingénieur développeur .Net.

Que trouverez-vous ici ? Beaucoup de choses, principalement mon point de vue sur l'actualité numérique, mais également des infos diverses, des idées, des astuces pour mieux développer, des bouts de code, ...

N'hésitez pas m'ajouter dans vos favoris ou à me suivre sur Facebook / Twitter !

Bonne visite !

Liens

Raccourci pour les développeurs : http://dev.howtommy.net

Mes liens dédiés aux développeurs : http://liens.howtommy.net/?searchtags=d%C3%A9veloppement

Commentaires

Comment RSS

Par mois

Dernier posts

Nuage de tags

Hall of fame

microsoft certified professional