web-dev-qa-db-fra.com

Devez-vous utiliser une classe partielle sur plusieurs projets?

J'ai une bibliothèque de classe avec toute ma logique de base de données. Mon DAL/BLL.

J'ai quelques projets Web qui utiliseront la même base de données et les mêmes classes, j'ai donc pensé que c'était une bonne idée de résumer la couche de données dans son propre projet.

Cependant, lorsqu'il s'agit d'ajouter des fonctionnalités aux classes de certains projets, je souhaite ajouter des méthodes à certaines classes.

Par exemple, ma couche de données contient les objets Product et SomeItem:

// Data Access Layer project

namespace DAL {
  public class Product { 
     //implementation here 
  }

  public class SomeItem {
     //implementation here 
  }
}

Dans un projet, je souhaite ajouter une interface utilisée par différents éléments de contenu. J'ai donc une classe appelée:

// This is in Web Project
namespace DAL {
  public partial class Product : ICustomBehaviour {

    #region ICustomBehaviour Implementation
       TheSharedMethod();
    #endregion
  }
}

Est-ce une bonne idée d'écrire une classe partielle dans un projet séparé (en créant une dépendance) en utilisant l'espace de noms same ? Si c'est une mauvaise idée, comment puis-je utiliser ce type de fonctionnalité?

Il ne semble pas vouloir les fusionner au moment de la compilation, alors je ne suis pas sûr de ce que je fais mal.

37
Armstrongest

Vous ne pouvez pas écrire une classe partielle sur plusieurs projets. Une classe partielle est un morceau de sucre syntaxique réservé à la compilation. Tout le type se termine en un seul assemblage, c’est-à-dire un projet.

(Au fait, votre fichier DAL d'origine devrait également déclarer la classe partielle).)

75
Jon Skeet

Je ne peux pas répondre à votre question sur la meilleure façon d'organiser vos couches, mais je peux essayer de répondre à votre question sur la meilleure façon d'imiter des classes partielles.

Voici quelques réflexions:

  • La première chose qui me vient à l’esprit est l’héritage. Ce n’est pas toujours la meilleure solution, mais vous n’aurez peut-être pas le choix, car vous devrez peut-être pouvoir traiter vos objets de la même manière que la classe de base.
  • La composition est également un bon choix (c’est-à-dire, envelopper la classe dans une autre classe). Cela vous donne un découplage un peu plus agréable de votre DAL, mais peut être fastidieux à mettre en œuvre.
  • Si vous avez vraiment besoin d'ajouter une ou deux méthodes à une classe existante, vous pouvez également utiliser une méthode extension , mais elles peuvent rapidement créer du code spaghetti si vous les utilisez trop.
5
Jason Baker

Les classes partielles doivent exister dans la même assemblée. Sinon, comment le compilateur déciderait-il où fusionner les classes partielles?

4
Kent Boogaart

Avec Visual Studio 2015 et les versions ultérieures il est possible de scinder des classes partielles entre projets: utilisez projets partagés (voir aussi ce blog MSDN ).

Pour ma situation j'avais besoin de ce qui suit:

  • Une bibliothèque de classes qui définit certaines classes utilisées comme interface par plusieurs applications clientes.
  • L'application client.
  • Une application d'installation qui crée des tables dans la base de données.
    En raison des limites imposées par l’installateur, cette configuration doit être autonome. Il ne peut référencer aucun assemblage au-delà du framework .NET. La configuration doit insérer des constantes d’énumération dans les tables, elle doit donc idéalement faire référence à la bibliothèque de classes.
    La configuration peut importer un projet partagé.
  • Comme un projet partagé est similaire au copier-coller de code, je souhaite déplacer le moins possible dans le projet partagé.

L'exemple suivant montre comment des classes partielles et des projets partagés autorisent le fractionnement de classes sur différents projets.

Dans la bibliothèque de classes, Address.cs:

namespace SharedPartialCodeTryout.DataTypes
{
    public partial class Address
    {
        public Address(string name, int number, Direction dir)
        {
            this.Name = name;
            this.Number = number;
            this.Dir = dir;
        }

        public string Name { get; }
        public int Number { get; }
        public Direction Dir { get; }
    }
}

La bibliothèque de classes est une bibliothèque de classes Visual Studio normale. Il importe le SharedProject, au-delà de son .csproj ne contient rien de spécial:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
<!-- standard Visual Studio stuff removed -->
    <OutputType>Library</OutputType>
<!-- standard Visual Studio stuff removed -->
  </PropertyGroup>
<!-- standard Visual Studio stuff removed -->
  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Address.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="..\SharedProject\SharedProject.projitems" Label="Shared" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Address.Direction est implémenté dans le SharedProject:

namespace SharedPartialCodeTryout.DataTypes
{
    public partial class Address
    {
        public enum Direction
        {
            NORTH,
            EAST,
            SOUTH,
            WEST
        }
    }
}

SharedProject.shproj est:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <PropertyGroup Label="Globals">
    <ProjectGuid>33b08987-4e14-48cb-ac3a-dacbb7814b0f</ProjectGuid>
    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
  </PropertyGroup>
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
  <PropertyGroup />
  <Import Project="SharedProject.projitems" Label="Shared" />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

Et son .projitems est:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
    <HasSharedItems>true</HasSharedItems>
    <SharedGUID>33b08987-4e14-48cb-ac3a-dacbb7814b0f</SharedGUID>
  </PropertyGroup>
  <PropertyGroup Label="Configuration">
    <Import_RootNamespace>SharedProject</Import_RootNamespace>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="$(MSBuildThisFileDirectory)Address.Direction.cs" />
  </ItemGroup>
</Project>

Le client standard utilise Address dont Address.Direction:

using SharedPartialCodeTryout.DataTypes;
using System;

namespace SharedPartialCodeTryout.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create an Address
            Address op = new Address("Kasper", 5297879, Address.Direction.NORTH);
            // Use it
            Console.WriteLine($"Addr: ({op.Name}, {op.Number}, {op.Dir}");
        }
    }
}

Le client standard csproj fait référence à la bibliothèque de classes et not SharedProject:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
    <OutputType>Exe</OutputType>
<!-- Removed standard Visual Studio Exe project stuff -->
  </PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\SharedPartialCodeTryout.DataTypes\SharedPartialCodeTryout.DataTypes.csproj">
      <Project>{7383254d-bd80-4552-81f8-a723ce384198}</Project>
      <Name>SharedPartialCodeTryout.DataTypes</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

DbSetup utilise uniquement les enums:

Le fichier DbSetup.csproj ne fait pas référence à la bibliothèque de classes; il importe uniquement SharedProject:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
    <OutputType>Exe</OutputType>
<!-- Removed standard Visual Studio Exe project stuff -->
  <?PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="..\SharedProject\SharedProject.projitems" Label="Shared" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

De conclure:

Pouvez-vous diviser une classe partielle entre plusieurs projets?

Oui, utilisez les projets partagés de Visual Studio.

Est-ce une bonne idée d'écrire une classe partielle dans un projet séparé (créant une dépendance) en utilisant le même espace de noms?

Souvent non (voir les autres réponses); dans certaines situations et si vous savez ce que vous faites, cela peut être pratique.

3
Kasper van den Berg

Je ne vois aucune raison pour que ce schéma ne fonctionne pas:

Deux fichiers contiennent des mécanismes de stockage (ou une autre fonctionnalité). Ils spécifient l'héritage mais ne contiennent aucune logique métier:

  • ProductDataAccess.cs
  • ProductWeb.cs

Un fichier contient la logique métier:

  • ProductBusinessLogic.cs

Maintenant, créez deux projets:

  • WebProject contient ProductWeb.cs et ProductBusinessLogic.cs.
  • DataProject contient ProductDataAccess.cs et ProductBusinessLogic.cs

Les deux projets utilisent la même logique métier.

1
Johan Nilsson

Vous ne pouvez pas écrire de classes partielles dans différents projets.Parce que le compilateur obtient un seul projet pour la compilation, il scanne la liste des classes, méthodes, champs, etc. de ce projet uniquement. autres projets, le compilateur ne peut pas trouver ceux-ci.

0
user3363647

Bien que je sois d’accord avec vous pour ce qui est du développement pré-linq, Neil, je souhaiterais également pouvoir le faire afin de séparer la logique de gestion des classes partielles générées par le concepteur Linq2SQL. Par exemple:

Northind.DAL (prj)
-NorthindDataContext (EntityNamespace set to "Northwind.BLL")
--Product() (Entity, partial class auto-generated)
--Category() (Entity, partial class auto-generated)
--Supplier() (Entity, partial class auto-generated)

Northind.BLL (prj)
-Product() : IMyCustomEnityInterface, BaseEntity (override OnValidate(), etc)
-Category() : IMyCustomEnityInterface, BaseEntity (override OnValidate(), etc)
-Supplier() : IMyCustomEnityInterface, BaseEntity (override OnValidate(), etc)

Malheureusement, nous ne pouvons pas faire cela. En fait, j'aimerais savoir quelle est la méthode recommandée pour diviser les couches/niveaux en utilisant LINQ.

0
user47460

Je suis d'accord avec la réponse de Jon Skeet.

Je ne pense pas que ce serait un bon choix d’aborder une question comme celle-là de toute façon. Il existe déjà de bons modèles de conception qui démontrent le meilleur moyen de scinder vos niveaux/couches de code. Il ne s'agit que d'un simple sucre syntaxique permettant à Microsoft de séparer les fichiers du concepteur WinForms/WebForms et d'empêcher les utilisateurs de les décomposer.

0
Neil Barnwell