88 votes

Commandes de requête et / ou spécifications bien conçues

J'ai cherché pendant très peu de temps pour une bonne solution pour les problèmes présentés par le typique modèle de Référentiel (liste croissante de méthodes pour les requêtes spécialisées, etc.. voir: http://ayende.com/blog/3955/repository-is-the-new-singleton).

J'aime vraiment l'idée de l'aide de la Commande des requêtes, en particulier grâce à l'utilisation de la Spécification du modèle. Cependant, mon problème avec la spécification, c'est qu'il ne concerne que les critères de sélections simples (en gros, la clause where), et ne pas traiter les autres questions de requêtes, comme la participation, de groupement, de sous-ensemble de la sélection ou de projection, etc.. en gros, tous les cerceaux de nombreuses requêtes doivent suivre pour obtenir le jeu correct de données.

(note: j'utilise le terme "commande", comme dans le modèle de Commande, aussi connu comme les objets de requête. Je ne parle pas de commande dans la commande/de requête de séparation où il y a une différence entre les requêtes et commandes (update, delete, insert))

Je suis donc à la recherche de solutions qui intègrent l'ensemble de la requête, mais encore suffisamment flexible que vous n'êtes pas juste en remplaçant les spaghettis de Référentiels pour une explosion de commande des classes.

J'ai utilisé, par exemple Linqspecs, et bien que je trouve une certaine valeur en pouvoir d'attribuer des noms significatifs aux critères de sélection, il est juste pas assez. Peut-être que je suis à la recherche d'une solution mixte qui combine plusieurs approches.

Je suis à la recherche de solutions que d'autres peuvent avoir développés pour répondre à ce problème, ou l'adresse d'un problème différent, mais toujours satisfait à ces exigences. Dans l'article lié, Ayende suggère d'utiliser les nHibernate contexte directement, mais j'ai l'impression qu'une grande partie complique votre entreprise de la couche, parce que maintenant il doit également contenir des informations de requête.

Je vais offrir une prime sur ce, dès que le délai d'attente est écoulé. Donc merci de faire vos solutions bounty digne, avec de bonnes explications et je vais choisir la meilleure solution, et upvote les coureurs.

NOTE: je suis à la recherche de quelque chose qui est de l'ORM en fonction. N'a pas à être EF ou nHibernate explicitement, mais ceux qui sont les plus courants et s'adapterait le mieux. Si il peut être facilement adapté à d'autres ORM qui serait un bonus. Linq compatible serait bien aussi.

Mise à JOUR: je suis vraiment surpris qu'il n'y a pas beaucoup de bonnes suggestions ici. Il semble que les gens sont soit totalement CQRS, ou ils sont complètement dans le Référentiel de camp. La plupart de mes applications ne sont pas suffisamment complexe pour justifier CQRS (quelque chose avec la plupart des CQRS défenseurs facilement dire que vous ne devriez pas l'utiliser pour).

Mise à JOUR: Il semble y avoir un peu de confusion ici. Je ne suis pas à la recherche d'une nouvelle technologie d'accès aux données, mais plutôt une raisonnablement bien conçu interface entre l'entreprise et les données.

Idéalement, ce que je cherche est une sorte de croisement entre des objets de Requête, la Spécification du modèle, et de référentiel. Comme je l'ai dit ci-dessus, la Spécification du modèle ne traite qu'avec la clause where aspect, et pas les autres aspects de la requête, tels que les jointures, les sous-sélections, etc.. Référentiels de traiter l'ensemble de la requête, mais sortir de la main après un certain temps. Les objets de requête traitent aussi de l'ensemble de la requête, mais je ne veux pas simplement remplacer les référentiels avec des explosions d'objets de requête.

89voto

david.s Points 5261

Avertissement: comme il n'y a pas toutes les réponses grands encore, j'ai décidé de poster une partie d'un grand blog, j'ai lu il y a longtemps, repris presque mot pour mot. Vous pouvez trouver l'article complet ici. Il est donc ici:


On peut définir les deux interfaces suivantes:

public interface IQuery<TResult>
{
}

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

L' IQuery<TResult> indique un message qui définit une requête spécifique avec les données qu'il retourne à l'aide de l' TResult de type générique. Avec le défini précédemment interface, nous pouvons définir une requête de message comme ceci:

public class FindUsersBySearchTextQuery : IQuery<User[]>
{
    public string SearchText { get; set; }
    public bool IncludeInactiveUsers { get; set; }
}

Cette classe définit une opération de requête avec deux paramètres, ce qui aura pour résultat un tableau d' User objets. La classe qui gère ce message peut être défini comme suit:

public class FindUsersBySearchTextQueryHandler
    : IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
    private readonly NorthwindUnitOfWork db;

    public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
    {
        this.db = db;
    }

    public User[] Handle(FindUsersBySearchTextQuery query)
    {
        return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
    }
}

Nous pouvons maintenant laisser les consommateurs dépendent du générique IQueryHandler interface:

public class UserController : Controller
{
    IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;

    public UserController(
        IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
    {
        this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        User[] users = this.findUsersBySearchTextHandler.Handle(query);    
        return View(users);
    }
}

Immédiatement ce modèle nous donne une grande flexibilité, car on peut maintenant décider de ce à injecter dans l' UserController. Nous pouvons injecter un de complètement différent de la mise en œuvre, ou qui encapsule la mise en œuvre réelle, sans avoir à apporter des modifications à l' UserController (et tous les autres consommateurs de cette interface).

L' IQuery<TResult> interface nous donne au moment de la compilation de soutien lors de la spécification ou l'injection d' IQueryHandlers dans notre code. Lors du changement de la FindUsersBySearchTextQuery de revenir UserInfo[] à la place (par la mise en œuvre de IQuery<UserInfo[]>), l' UserController échoue à compiler, depuis le générique de type contrainte sur IQueryHandler<TQuery, TResult> de ne pas être en mesure de cartographier FindUsersBySearchTextQuery de User[].

L'injection de l' IQueryHandler interface à un consommateur, cependant, a certains moins évidents problèmes qui doivent encore être traités. Le nombre de dépendances de nos consommateurs pourraient devenir trop grand et peut conduire à des constructeur sur-injection - lorsqu'un constructeur prend trop d'arguments. Le nombre de requêtes à une classe exécute peuvent changer fréquemment, ce qui nécessiterait des changements constants dans le nombre d'arguments du constructeur.

Nous pouvons résoudre le problème d'avoir à injecter trop d' IQueryHandlers avec une couche d'abstraction supplémentaire. Nous avons créer un médiateur qui se trouve entre les consommateurs et la demande des gestionnaires:

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);
}

L' IQueryProcessor est un non-interface générique, avec une méthode générique. Comme vous pouvez le voir dans la définition de l'interface, l' IQueryProcessor dépend de l' IQuery<TResult> interface. Cela nous permet d'avoir le temps de compilation de l'aide dans notre consommateurs qui dépendent de l' IQueryProcessor. Réécrivons l' UserController à utiliser le nouveau IQueryProcessor:

public class UserController : Controller
{
    private IQueryProcessor queryProcessor;

    public UserController(IQueryProcessor queryProcessor)
    {
        this.queryProcessor = queryProcessor;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        // Note how we omit the generic type argument,
        // but still have type safety.
        User[] users = this.queryProcessor.Process(query);

        return this.View(users);
    }
}

L' UserController maintenant dépend de l' IQueryProcessor qui peut s'occuper de nos requêtes. L' UserControllers' SearchUsers des appels à la méthode de l' IQueryProcessor.Process méthode de passage dans une initialisé objet de requête. Depuis l' FindUsersBySearchTextQuery implémente l' IQuery<User[]> interface, nous pouvons passer à la générique Execute<TResult>(IQuery<TResult> query) méthode. Merci à C# inférence de type, le compilateur est capable de déterminer le type générique et cela nous évite d'avoir à déclarer explicitement le type. Le type de retour de la Process méthode est également connue.

Il est désormais de la responsabilité de la mise en œuvre de l' IQueryProcessor à trouver le bon IQueryHandler. Cela demande un peu de typage dynamique, et éventuellement l'utilisation d'un framework Injection de Dépendance, et peut être fait avec seulement quelques lignes de code:

sealed class QueryProcessor : IQueryProcessor
{
    private readonly Container container;

    public QueryProcessor(Container container)
    {
        this.container = container;
    }

    [DebuggerStepThrough]
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        var handlerType = typeof(IQueryHandler<,>)
            .MakeGenericType(query.GetType(), typeof(TResult));

        dynamic handler = container.GetInstance(handlerType);

        return handler.Handle((dynamic)query);
    }
}

L' QueryProcessor classe de constructions spécifiques IQueryHandler<TQuery, TResult> type en fonction du type de la requête de l'instance. Ce type est utilisé pour demander le récipient fourni de classe pour obtenir une instance de ce type. Malheureusement, nous devons appeler l' Handle méthode à l'aide de la réflexion (en utilisant le C# 4.0 dymamic mot-clé dans ce cas), car à ce stade, il est impossible de lancer le gestionnaire d'exemple, depuis le générique de l' TQuery argument n'est pas disponible au moment de la compilation. Cependant, à moins que l' Handle méthode est renommé ou d'autres arguments, cet appel ne manquera jamais, et si vous le souhaitez, il est très facile d'écrire un test unitaire pour cette classe. L'utilisation de la réflexion donnera une légère baisse, mais rien de vraiment s'inquiéter.


La réponse à l'une de vos préoccupations:

Je suis donc à la recherche de solutions qui intègrent l'ensemble de la requête, mais encore assez souple que vous n'êtes pas juste en remplaçant spaghetti Référentiels pour une explosion de commande des classes.

Une conséquence de cette conception est qu'il y aura beaucoup de petites classes dans le système, mais le fait d'avoir beaucoup de petites/concentré classes (avec des noms clairs) est une bonne chose. Cette approche est clairement beaucoup mieux que d'avoir de nombreuses surcharges avec des paramètres différents pour la même méthode dans un référentiel, comme vous pouvez le groupe de personnes dans une classe de requête. Donc, vous avez encore beaucoup moins classes de requêtes que les méthodes dans un référentiel.

4voto

MikeSW Points 7100

Ma façon de traiter avec que est en fait simpliste et ORM agnostique. De mon point de vue pour un dépôt est ceci: Le référentiel de l'emploi est de fournir de l'application avec le modèle requis pour le contexte, et donc, l'application demande à l'repo pour ce qu' il veut, mais ne pas le dire comment l'obtenir.

J'offre le référentiel de la méthode avec un critère (oui, DDD style), qui sera utilisé par les pensions de titres à créer la requête (ou tout ce qui est nécessaire - c'est peut-être un webservice demande). Les jointures et les groupes à mon humble avis sont les détails de la façon dont, pas la ce et un critère devrait être la base pour construire une clause where.

Modèle = la finale de l'objet ou de la structure de données devez par l'application.

public class MyCriteria
{
   public Guid Id {get;set;}
   public string Name {get;set;}
    //etc
 }

 public interface Repository
  {
       MyModel GetModel(Expression<Func<MyCriteria,bool>> criteria);
   }

Probablement, vous pouvez utiliser l'ORM critères (Nhibernate) directement si vous le souhaitez. Le référentiel de mise en œuvre doivent savoir comment utiliser les Critères de stockage sous-jacent ou DAO.

Je ne sais pas votre nom de domaine et le modèle d'exigences, mais il serait étrange si le meilleur moyen est que l'application à construire la requête elle-même. Le modèle change tellement que vous ne pouvez pas définir quelque chose de stable?

Cette solution exige clairement un code supplémentaire, mais il n'est pas le couple le reste de la à un ORM ou ce que vous utilisez pour accéder à l'espace de stockage. Le référentiel fait son travail pour agir comme une façade et de l'OMI c'est propre et les 'critères de traduction de code est réutilisable

2voto

Stu Points 7999

J'ai fait cela, pris en charge ce et défaits.

Le problème majeur est ceci: peu importe comment vous le faites, l'ajout d'abstraction n'est pas le gain de l'indépendance. Il sera de fuite, par définition. En essence, vous êtes à inventer toute une couche, juste pour rendre votre code mignon... mais il ne permet pas de réduire la maintenance, l'amélioration de la lisibilité ou de gain vous tout type de modèle de l'agnosticisme.

La partie amusante est que vous avez répondu à votre propre question en réponse à Olivier réponse: "il s'agit essentiellement de reproduire la fonctionnalité de Linq, sans tous les avantages que vous obtenez à partir de Linq".

Demandez-vous: "comment pourrait-il ne pas l'être?

1voto

Vous pouvez utiliser une interface fluide. L'idée de base est que les méthodes d'une classe de rendement actuel de l'instance de cette classe, après avoir effectué une action. Cela vous permet d'enchaîner les appels de méthode.

En créant une hiérarchie de classe, vous pouvez créer un flux logique des méthodes accessibles.

public class FinalQuery
{
    protected string _table;
    protected string[] _selectFields;
    protected string _where;
    protected string[] _groupBy;
    protected string _having;
    protected string[] _orderByDescending;
    protected string[] _orderBy;

    protected FinalQuery()
    {
    }

    public override string ToString()
    {
        var sb = new StringBuilder("SELECT ");
        AppendFields(sb, _selectFields);
        sb.AppendLine();

        sb.Append("FROM ");
        sb.Append("[").Append(_table).AppendLine("]");

        if (_where != null) {
            sb.Append("WHERE").AppendLine(_where);
        }

        if (_groupBy != null) {
            sb.Append("GROUP BY ");
            AppendFields(sb, _groupBy);
            sb.AppendLine();
        }

        if (_having != null) {
            sb.Append("HAVING").AppendLine(_having);
        }

        if (_orderBy != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderBy);
            sb.AppendLine();
        } else if (_orderByDescending != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderByDescending);
            sb.Append(" DESC").AppendLine();
        }

        return sb.ToString();
    }

    private static void AppendFields(StringBuilder sb, string[] fields)
    {
        foreach (string field in fields) {
            sb.Append(field).Append(", ");
        }
        sb.Length -= 2;
    }
}

public class GroupedQuery : FinalQuery
{
    protected GroupedQuery()
    {
    }

    public GroupedQuery Having(string condition)
    {
        if (_groupBy == null) {
            throw new InvalidOperationException("HAVING clause without GROUP BY clause");
        }
        if (_having == null) {
            _having = " (" + condition + ")";
        } else {
            _having += " AND (" + condition + ")";
        }
        return this;
    }

    public FinalQuery OrderBy(params string[] fields)
    {
        _orderBy = fields;
        return this;
    }

    public FinalQuery OrderByDescending(params string[] fields)
    {
        _orderByDescending = fields;
        return this;
    }
}

public class Query : GroupedQuery
{
    public Query(string table, params string[] selectFields)
    {
        _table = table;
        _selectFields = selectFields;
    }

    public Query Where(string condition)
    {
        if (_where == null) {
            _where = " (" + condition + ")";
        } else {
            _where += " AND (" + condition + ")";
        }
        return this;
    }

    public GroupedQuery GroupBy(params string[] fields)
    {
        _groupBy = fields;
        return this;
    }
}

Vous serait-il appeler comme ça

string query = new Query("myTable", "name", "SUM(amount) AS total")
    .Where("name LIKE 'A%'")
    .GroupBy("name")
    .Having("COUNT(*) > 2")
    .OrderBy("name")
    .ToString();

Vous ne pouvez créer une nouvelle instance d' Query. Les autres classes ont protégé constructeur. Le point de la hiérarchie est de "désactiver" les méthodes. Par exemple, l' GroupBy méthode retourne un GroupedQuery qui est la classe de base Query et ne pas avoir un Where méthode (la méthode est déclarée en Query). Par conséquent, il n'est pas possible d'appeler Where après GroupBy.

Il n'est cependant pas parfait. Avec cette hiérarchie de classe, vous pouvez successivement masquer les membres, mais de ne pas montrer de nouvelles. Par conséquent, Having déclenche une exception quand il est appelé avant GroupBy.

Notez qu'il est possible de faire appel Where plusieurs fois. Cela ajoute de nouvelles conditions avec un AND pour les conditions existantes. Cela rend plus facile de construire des filtres par programme à partir de simples conditions. La même chose est possible avec Having.

Les méthodes d'accepter listes de champs ont un paramètre params string[] fields. Il vous permet de passer seul les noms de champ ou un tableau de chaînes.


Couramment les interfaces sont très souples et ne vous oblige pas à créer un grand nombre de surcharges de méthodes avec différentes combinaisons de paramètres. Mon exemple fonctionne avec des chaînes, mais l'approche peut être étendue à d'autres types. Vous pouvez également déclarer des méthodes prédéfinies pour des cas particuliers ou des méthodes d'accepter des types personnalisés. Vous pouvez également ajouter des méthodes comme l' ExecuteReader ou ExceuteScalar<T>. Cela vous permettra de définir des requêtes comme ceci

var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
    .Where(new CurrentMonthCondition())
    .Where(new DivisionCondition{ DivisionType = DivisionType.Production})
    .OrderBy(new StandardMonthlyReportSorting())
    .ExecuteReader();

0voto

svidgen Points 4012

Toutes mes excuses si je suis loin de la marque ici. Je pense, par le biais de "de-repositorizing" une application actuellement. Et, bien que je ne suis pas entièrement familier avec tous les modèles et de cadres que vous avez mentionné, j'ai pensé faire quelques recommandations de base sur la base d'où ma réflexion et de test m'a conduit jusqu'à présent (dans le cas de mon plus simples, moins entachée (... ok, moins instruits) l'esprit est utile ici).

Première

Faire vos classes (ou DAL sous-classes) conscient de soi. Qui est, faire des sous-classes ou des implémentations d'un DataObject de la classe ou de l'interface, qui insiste sur le fait qu'ils seront en mesure d'identifier, de sérialiser et désérialiser leurs colonnes et de relation. Cela peut être assez complexe, vous pouvez tirer parti d'un outil de cartographie ou de la bibliothèque, mais il pourrait aussi être relativement simple:

interface IDataObject {
  Dictionary<String, String> ToDictionary();
  void Populate(Dictionary d);
  void Populate(IDataReader r);
  static DataDefinition Definition;
  DataDefinition Definition;
} // interface IDataObject

public class DataDefinition {
  public String Table;
  public Dictionary<String, DbType> Columns;
  public List<DataDefinition> Parents;
  public List<DataDefinition> Children;
} // class DataDefinition

public class User : IDataObject {
  public Int32 UserID;
  public String Name;
  public List<Comment> Comments;

  public void Populate(Dictionary<String, Object> d) {
    UserID = Convert.ToInt32(d["user_id"]);
    Name = d["username"];
  }

  public void Populate(IDataReader r) {
    while (r.Read()) {
      Dictionary<String, String> row = SomeMagicIDataReaderToDictionaryMethod(r);

    }
  }

  public Dictionary<String, String> ToDictionary() {
    return new Dictionary<String, String>() {
      {"user_id", UserID},
      {"username", Name}
    }
  }

  public static DataDefinition Definition = new DataDefinition() {
    DataEntity = "users",
    Fields = new Dictionary<String, DbType>() {
      "user_id", DbType.Int,
      "username", DbType.String
    },
    Children = new List<DataDefinition>() {
      Comment.Definition
    }
  }

  public DataDefinition Definition
  {
    get {
      return User.Definition;
    }
  }

} // class User

Deuxième

En général, seule extraction de la "main" de l'entité d'intérêt. C'est, ne pas utiliser toute forme de magie pour récupérer des Commentaires pour chaque utilisateur récupérés; vous ne savez pas si vous en avez besoin! L'utilisation des singletons. Et créer statique multi-relation population-nécessaire. Si ces multi-méthodes get live sur le parent ou l'enfant sera déterminée principalement par la façon dont vous comprenez la relation:

public class User : IDataObject {

  ...

  public List<Comment> _Comments;
  public List<Comment> Comments {
    get {
      if (_Comments == null) {
        LoadComments(new List<User> { this });
      }
      return _Comments;
    }
  }

  // we want to be able to populate comments for LIST of users all at once,
  // if necessary. but, we can all use our generalized method to populate them
  // for a single user, keeping any user-comment specific fetching logic in one place.
  public static Boolean LoadComments(List<User> users) {

    Dictionary<Int32, User> index = new Dictionary<Int32, User>();
    foreach (User u in users) {
      index.Add(u.UserID, user);
    }

    ...

    using (IDataReader r = <whatever>) {
      while (r.Read()) {
        Dictionary<String, String> row = SomeMagicIDataReaderToDictionaryMethod(r);        
        Int32 userID = Convert.ToInt32(row["user_id"]);
        Comment c = (new Comment()).Populate(row);
        if (index[userID].Comments == null) {
          index[userID].Comments = new List<Comments>();
        }
        index[userID].Comments.Add(c);
      }
    }
  }

  ...

}

Troisième

Écrire parametized SQL (ou JSON ou XML, les autres éléments de la syntaxe, ou à exécuter les données appropriées ou des appels de l'API) basé sur une définition et un contexte (un tas d'objets liés) ou des paramètres.

Alors, nous faisons un "simple" moteur de traitement de données, ou de nous en trouver un, ou nous de prolonger l'un ... ou quelque chose.

class DataEngine {

  // our workhorse takes in a definition -- the thing we're looking for.
  public static List<T> Query<T>(List<IDataObject> context) where T : IDataObject, new() {
    List<T> rv = new List<T>();
    IDataReader r;

    if (context.Count > 0) {
      // the exact details of what this does will probably differ based on the
      // underlying data engine, query engine, and frameworks you're using.
      // but in essence, because our DataObjects have DataDefinitions that refer
      // to each other, we can perform an arbitrarily limited BFS to
      // produce an ordered JOIN() list of some sort.

      List<DataDefinition> entities = new List<String>() {
        T.Definition
      };
      Dictionary<String, Object> conditions = new Dictionary<String, Object>();
      List<SqlParameter> parameters = new List<SqlParameter>();

      foreach (DataObject o in context) {
        // special case
        if (o.Definition == T.Definition) {
          // T.Definition is already there.
          // some special casing is necessarily to account for T.Definition's containing
          // a Parent or Children that are of the same type.
          Dictionary<String, Object> c = o.ToDictionary();
          foreach (KeyValuePair p in c) {
            if (p.Value != null) {
              conditions.Add(p);
              parameters.Add(new SqlParameter(<whatever>));
            }
          }
        } else {
          // BFS for link between o.Definition and T.Definition
          <search and add definitions to entities>;

          Dictionary<String, Object> c = o.ToDictionary();
          foreach (KeyValuePair p in c) {
            if (p.Value != null) {
              conditions.Add(p);
              parameters.Add(new SqlParameter(<whatever>));
            }
          }
        }
      }

      IQuery q = <joined entity clauses> + <joined parametized conditions>;
      q.IterativelyAppendSqlParameters(parameters);
      r = q.Execute();
    } else {
      // construct query based on definition to grab ALL objects ... 
      IQuery q = <whatever>;
      r = q.Execute();
    }

    while (r.Read()) {
      T row = new T();
      row.Populate(SomeMagicIDataReaderToDictionaryMethod(r));
      rv.Add(row);
    }

    return rv;

  } // static Query()

} // class DataEngine

Demande de base:

List<User> loggedInUser = DataEngine.Query(
  // template user -- find a user that matches the non-null, non-zero fields here
  new User() {
    Name = "Bob Jones"
  }
);

Plus compliqué, à la demande:

// in this case, we assume User.Definition contains a reference to
// Comment.Definition (and possibly vice versa) which is linked to
// Item.Definition (and possibly vice versa).
// DataEngine.Query searches the definitions for a path and builds the query.
List<Item> itemsUserHasCommentedOn = DataEngine.Query(
  // a list of related items, without saying *how* they're related.
  new List<DataObject>() { loggedInUser }
);

Comme corollaire à une solution de ce genre, votre IDataObject ne devriez pas faire quoi que ce soit "utile" lors de l'instanciation. Il y a peut être un moyen de contourner cela, mais mon relativement inexpérience m'empêche de les voir.

Quatrième

Ne pas faire semblant, surtout dans les grands projets, que vous aurez jamais jusqu'à la fin avec un dépôt de requêtes spécialisées. Automagic de liaison de données de stratégies sont presque jamais assez intelligent pour appliquer vos excentrique connaissance encyclopédique de données ou les options que vous aurez besoin dans 6 mois. Et, dans certains cas, il est plus facile de simplement écrire la requête spécifique dont vous avez besoin, plutôt que de pirater un système magique de travailler convenablement avec votre déglingués de données.

Avertissement

Je ne suis pas la moitié .NET-expérience de l'autre .NET développeurs ici. (Perl et PHP arrière-plan, la plupart du temps.) Donc, je suis pas très familier avec la pléthore de .NET d'accès aux données des modèles et des cadres. Ce que j'ai donné ici est un général (et, éventuellement, naïve) chemin que j'avais l'intention de descendre à mon emploi actuel pour repousser le tas de l'absurde-spécifique et très sensible DAL + procédures stockées j'ai hérité.

En d'autres termes, si c'est terrible ou totalement à côté de la question. Soyez doux ...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X