Suggérer une traduction
 
Suggestions d'autres utilisateurs :

progress indicator
Aucune autre suggestion.
MSDN Magazine > Home > Issues > 2009 > Avril >  Motifs de conception de persistance des données
Affichage du contenu :  côte à côteAffichage du contenu : côte à côte
Ce contenu traduit automatiquement peut être modifié par les membres de la communauté. Nous vous invitons à améliorer la traduction en cliquant sur le lien « Modifier » associé aux phrases ci-dessous.
Patterns in Practice
Persistence Patterns
Jeremy Miller
Data access is a popular subject among developers. No doubt you've heard plenty of opinions on specific data access technologies and persistence frameworks, but what's the best way to consume these tools in your project? What criteria should you use to select the right tool for your project? What do you need to know conceptually about these tools before you use? What if you've got too much time on your hands and want to write your own persistence tool—what do you need to know?
Unsurprisingly, the answer to all of these questions is to examine the underlying design patterns of persistence.
Domain Models
When you think about how you are going to structure and represent the business logic in your system, you have a couple major choices. In this article, I'm largely assuming that you have chosen the domain model approach to organizing business logic into entity objects.
From the formal description, a domain model is an object model of the domain that incorporates both behavior and data.
For example, my current project involves Customer Relationship Management. (CRM) We have entity objects for Case and User and Solution that contain both data and implement business rules involving that data. A domain model can range from an anemic model that is simply a set of data structures to a very rich model that jealously guards the raw data behind a narrow interface (hardcore Domain-Driven Development). Where your domain model falls in this range is largely a matter of how complicated the business logic in your system really is and how prevalent reporting or data entry are in your system requirements.
An adequate discussion of the Domain Model pattern is beyond the scope of this article. I would strongly recommend reading Chapter 2 of Martin Fowler's Patterns of Enterprise Application Architecture book for a great discussion of the major design patterns for organizing business logic.
Before getting started, let's review the two main ways to perceive the role of the database and data access code in your system:
  • The database is the keystone of the application and a business asset. The data access code and even the application or service code are simply mechanisms to connect the database with the outside world.
  • The business objects in the middle tier and the user interface or service layer are the application, and the database is a means to reliably persist the state of the business objects between sessions.
Personally, I feel like the first, datacentric viewpoint is well understood in the .NET community, so I'd like to focus on the second viewpoint. In the second viewpoint, you're typically working with entity objects in the middle tier. One way or another, you're probably doing Object/Relational Mapping (O/RM) to map data from the business entities to the database tables and vice versa. You might be doing it by hand, but more likely with tools of some sort. Those tools are great and can potentially save a lot of development time, but there are some issues you should be aware of, and it's always nice to understand how a tool works beneath the covers. Hopefully, studying the patterns in this article will help on both accounts.
I'm a big proponent of Agile practices like Test Driven Development, Behavior Driven Development, Lean Programming, and Continuous Design. I say this just to be clear that I have a very specific bias in regards to the patterns I'll be discussing in this article, and it will probably show.

Mapping Objects to Databases
I've decided to model my system in the middle tier with entity objects that have identity, data, and related behavior. My business logic will be implemented either in these entity objects or in domain services that use these entity objects. Great, but how do you seamlessly move data back and forth between the database and the entity objects?
In the case of a relational database, you need to move the fields and properties of our objects to tables and fields in the database. You can write this code completely by hand, writing out separate INSERT, UPDATE, SELECT, and DELETE SQL statements, but you'll quickly realize that you're repeating yourself in the code quite a bit. Namely, you're repeatedly specifying that the data in an object property or field should be stored into a particular column in a database table.
This is where an Object/Relational Mapper (O/RM) steps in. When you use an O/RM, you simply create a mapping of the object properties to the database table and let the O/RM tool use that metadata to figure out what the SQL statements should be and how to move data from the object to the SQL statements.
Let's get concrete and look at a very basic sample. My current project has an Address class that looks like this:
public class Address 
{
  public long Id { get; set; }
  public string Address1 { get; set; }
  public string Address2 { get; set; }
  public string City { get; set; }
  public string StateOrProvince { get; set; }
  public string Country { get; set; }
  public string PostalCode { get; set; }
  public string TimeZone { get; set; }
}
When I set up the mapping for the Address class, I need to specify which table the Address class maps to, how the Address objects will be identified (the primary key), and which properties map to which database tables.
For this example, I'm using the Fluent NHibernate tool for mapping Address.
I'm purposely doing the mapping in a longhand manner to show all the details. (In real usage, I employ convention over configuration to eliminate much of the repetitiveness.) Here's the code:
public class AddressMap : ClassMap<Address> 
{
  public AddressMap() 
  {
    WithTable("Address");
    UseIdentityForKey(x => x.Id, "id");

    Map(x => x.Address1).TheColumnNameIs("address1");
    Map(x => x.Address2).TheColumnNameIs("address2");
    Map(x => x.City).TheColumnNameIs("city");
    Map(x => x.StateOrProvince).TheColumnNameIs("province_code");
    Map(x => x.Country).TheColumnNameIs("country_name");
    Map(x => x.PostalCode).TheColumnNameIs("postal_code");
  }
}
Now that you've made a mapping of the object model to the database model, something has to actually execute the mapping, and you roughly have two choices: Active Record or Data Mapper.

Active Record
When choosing a persistence strategy, the first decision you need to make is where to place the responsibility for carrying out the mapping. You have two very different options: you can either make each entity class itself responsible for the mapping, or you can use a completely separate class to do the mapping to the database.
The first option is known as the Active Record pattern: an object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data. An Active Record approach puts persistence methods directly onto the entity object. In this case, the Address class would probably have methods like Save, Update, and Delete, as well as a static Load method that queries the database for an Address object.
The typical usage of the Active Record pattern is to essentially make a strongly typed class wrapper around a single row in a database table. As such, the Active Record classes are generally an exact mirror of the database structure (this will vary between tools). Many Active Record implementations will generate the entity objects directly from the database structure.
The most famous Active Record implementation is the ActiveRecord tool that comes as part of the Ruby on Rails Web development framework (which is usable in .NET programming with IronRuby; see " Getting Started With IronRuby And RSpec, Part 1 "). If I were using ActiveRecord, I would first create a table named addresses in the database. Then, if all I care about is having access to the address data and methods for finding, saving, and inserting address data, the entire Address class would look exactly like this Ruby class:
class Address < ActiveRecord::Base
end
The Ruby implementation is using metaprogramming to dynamically create fields and query methods that match the database table named addresses (pluralized form of the class name Address) at runtime.
With a few exceptions, .NET implementations of the Active Record pattern generally work by using code generation to create .NET classes that map directly to database tables. The benefit of this approach is that it is very easy to keep the database and object model synchronized.
Lean Programming
Lean Programming teaches you to eliminate wasted effort in development projects by favoring "pull" design over "push" design. This means infrastructure concerns like persistence should only be designed and built to satisfy the needs of business requirements (pulled on demand) instead of building the data access layer code that you think the application will need later (pushed).
In my experience, this means you should develop the system incrementally by developing vertically—by building one feature at a time instead of building the system one horizontal layer at a time. By working this way you can be sure that infrastructure development is no more than what you absolutely need by tying all infrastructure code to a feature being built into the system. When you work horizontally by building the data access layer or the database before you write the user interface, service, or business logic layer, you risk the following:
  • Not getting adequate feedback early from project stakeholders because there is no working functionality to demonstrate
  • Writing unnecessary infrastructure code for features that might not actually get built
  • Building the wrong data access code because you don't understand the business requirement early on, or the requirements change before the other layers are created
Pull design also means that your choice of data access strategy should be determined by the needs of the application. Given a choice, I would choose an O/RM for a system where I was modeling the business logic in a domain model. For a reporting application, I would bypass persistence tools altogether in favor of just using SQL and datasets. For a system that is mostly data entry, I might choose an Active Record tool. The point is that the data access and persistence is shaped by the needs of its consumers.

Data Mapper
Nice as the Active Record pattern may be, it is often valuable to have an object structure that varies from the database model. You may want to map multiple classes to the same database table. You might have a legacy database schema that doesn't fit the shape of the objects that you would want to express in some business logic (a common occurrence for me on my last several projects).
This is where I would choose the Data Mapper pattern: a layer of mapper objects that move data between objects and a database while keeping them independent of each other and the mapper classes themselves. The Data Mapper pattern strips most of the responsibility of persistence from the entity objects in favor of classes external to the entities. With a Data Mapper pattern, you access, query, and save entity objects with some kind of repository (discussed later in this article).
How do you choose between the two? My personal bias is very strongly towards the Data Mapper solution. That aside, Active Record is best for systems with simpler domain logic, CRUD-intensive applications (create, read, update and delete, that is), and situations where the domain model doesn't need to diverge much from the database structure. Active Record may be more comfortable for many .NET development teams because it implies a datacentric way of working that is more common for .NET teams and, frankly, much better supported by .NET itself. I would characterize most persistence tools in the .NET space as Active Record tools.
On the other hand, Data Mapper is more appropriate for systems with complex domain logic where the shape of the domain model will diverge considerably from the database model. Data Mapper also decouples your domain model classes from the persistence store. That might be important for cases where you need to reuse the domain model with different database engines, schemas, or even different storage mechanisms altogether.
Most important to me as an Agile practitioner, the Data Mapper approach allows me to design the object model independently of the database with Test Driven Development more efficiently than an Active Record solution could. This is important to teams that want to take an incremental approach to design because it allows a team to work through the design in the object model first where refactoring tools and unit testing techniques are generally more effective, then create the database model later when the object model is stable. The Data Mapper pattern is also more applicable to the Domain Driven Design architectural approach that is gaining in popularity in the .NET community.

Using a Repository
When I was growing up as a developer in the Windows DNA days, I lived in mortal fear of forgetting to close an ADO Connection object in my code. On my first big enterprise project, I coded directly against ADO. Every time I needed to request or save data to the database, I had to do the following:
  1. Find the connection string from some sort of configuration
  2. Open a new connection to the database
  3. Create a command object or a recordset object
  4. Execute the SQL statement or stored procedure
  5. Close the connection to release the database resources
  6. And oh yeah, put some adequate error handling around the data access code
The first problem was the absurd amount of repetitive code I was writing. The second problem was that I had to write the code correctly every single time, because forgetting to close a single connection could, and unfortunately did, drive a mission-critical system offline under a heavy load. Nobody wanted to be the guy whose code collapsed out of poor database connection hygiene, but it still happened too frequently.
In my next project, my coworker and I got smarter. We implemented a single class that would do all the setup, teardown, and error handling code of the ADO Connection object. Any other class in the system could interact with the database by using this central class to invoke stored procedures. We wrote much less code and, better yet, we weren't plagued by problems arising from forgetting to close database connections because we had to get that connection management code right only one time.
What my team did was to create a crude implementation of the Repository pattern that mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
Basically, the Repository pattern just means putting a façade over your persistence system so that you can shield the rest of your application code from having to know how persistence works. My current project is using a Repository with a public interface that looks like the one in Figure 1.
public interface IRepository 
{
  // Find an entity by its primary key
  // We assume and enforce that every Entity
  // is identified by an "Id" property of 
  // type long
  T Find<T>(long id) where T : Entity;

  // Query for a specific type of Entity
  // with Linq expressions.  More on this later
  IQueryable<T> Query<T>();
  IQueryable<T> Query<T>(Expression<Func<T, bool>> where);

  // Basic operations on an Entity
  void Delete(object target);
  void Save(object target);
  void Insert(object target);

  T[] GetAll<T>();
}
When some class in the system needs to access an entity object, it can simply use IRepository to fetch that entity by its ID, or query for a list of entity objects with a LINQ expression.
When I use the concrete class of IRepository, which uses the NHibernate library for O/RM, I'm pulling a connection string from memory, loading the mapping definitions from an assembly, building the NHibernate SessionFactory (once and only once because it's a big performance hit), and wrapping the low-level ISession interface from NHibernate. With some help from the Repository class, NHibernate manages database connection lifecycle issues.
Whew. That's a lot of stuff going on behind the scenes. It's a good thing that I've swept all of the direct interaction with NHibernate behind the IRepository interface. I don't have to know all that bootstrapping NHibernate stuff just to load, save, and query objects. Even better, since every class depends on the abstract IRepository interface for data access and persistence, I can slide in an InMemoryRepository implementation of IRepository that uses LINQ to Objects internally to stub the database during testing.

Identity Map
Let's look at a common scenario. Inside a single logical transaction in some sort of shipping system, you have two completely different classes that work independently, but both classes will need to retrieve the same Customer entity during the transaction. Ideally, you want only a single Customer object inside a single transaction for each logical Customer so that each object is working off of consistent data.
In persistence tooling, preventing duplicate logical references is the job of the Identity Map pattern. As stated by Martin Fowler, an Identity Map ensures that each object gets loaded only once by keeping every loaded object in a map and looks up objects using the map when referring to them.
For the Customer class, you might build a naïve implementation of Identity Map like the one in Figure 2. I'm purposely leaving out thread locking here just to simplify the code. A real implementation would require adequate thread safety measures.
public class CustomerRepository 
{
  public IDictionary<long, Customer> _customers = 
    new Dictionary<long, Customer>();

  public Customer FindCustomer(long id) 
  {
    if (_customers.ContainsKey(id)) 
  {
      return _customers[id];
    }

    var customer = findFromDatabase(id);
    _customers.Add(id, customer);

    return customer;
  }

  private Customer findFromDatabase(long id) 
  {
    throw new System.NotImplementedException();
  }
}
In this example, a Customer object is identified by its ID. When you request an instance of Customer by ID, the CustomerRepository first checks an internal dictionary to see whether it has that particular Customer. If so, it returns the existing Customer object. Otherwise, CustomerRepository will fetch the data from the database, build a new Customer, store that Customer object in its dictionary for later requests, and return the new Customer object.
Fortunately, you generally won't write this code by hand because any mature persistence tool should include this feature. You do need to be aware that this is happening behind the scenes and scope your persistence support objects accordingly. Many teams will use the lifecycle management feature of an Inversion of Control tool (StructureMap, Windsor, Ninject, and others) to ensure that all classes in a single HTTP request or thread are using the same underlying Identity Map. The Unit of Work pattern is another way to manage a single Identity Map across multiple classes in the same logical transaction.
Just to illustrate this pattern farther, Figure 3 shows an example of how an Identity Map works. The code is written against the architecture of my current project. The instances of the IRepository interface shown in the code below wrap a single NHibernate ISession, which in turn implements the Identity Map pattern. When I run this test the output is this:
1 passed, 0 failed, 0 skipped, took 5.86 seconds.
[Test]
public void try_out_the_identity_map() 
{
  // All I'm doing here is getting a fully formed "Repository"
  // from an IoC container and letting an IoC tool bootstrap 
  // NHibernate offstage.
  IRepository repository = ObjectFactory.GetInstance<IRepository>();

  // Find the Address object where Id == 1
  var address1 = repository.Find<Address>(1);

  // Find the Address object where Id == 1 from the same Repository
  var address2 = repository.Find<Address>(1);

  // Requesting the same identified Address object (Id == 1) inside the 
  // same Repository / Identity Map should return the exact same
  // object
  address1.ShouldBeTheSameAs(address2);

  // Now, let's create a completely new Repository that has a 
  // totally different Identity Map
  IRepository secondRepository = ObjectFactory.GetInstance<IRepository>();

  // Nothing up my sleeve...
  repository.ShouldNotBeTheSameAs(secondRepository);

  var addressFromSecondRepository = secondRepository.Find<Address>(1);

  // and this is a completely different Address object, even though
  // it's loaded from the same database with the same Id
  addressFromSecondRepository.ShouldNotBeTheSameAs(address1);
}

Lazy And Eager Loading
One of the best things about using a persistence tool is the ability to load a root object (Invoice, perhaps), then navigate directly to its children (InvoiceLineItem) and related objects just by using properties of the parent class. However, sooner or later you're going to have to care about the performance of your application. Fetching an entire object graph when you may need only the top-level object most of the time or can forgo parts of the object graph isn't efficient.
That's OK. In that case, you can use the Lazy Loading pattern in which you defer initializing the object until just before it's needed.
Let's put this in more concrete terms. Say that you have a class named Customer that references an Address object:
public class Customer : DomainEntity 
{
  // The "virtual" modifier is important.  Without it,
  // Lazy Loading can't work
  public virtual Address HomeAddress { get; set; }
}
In most use cases involving the Customer object, the code never needs the Customer.HomeAddress property. In that case, you could set up the database mapping to make the Customer.HomeAddress property lazy loaded like in this Fluent NHibernate mapping:
public class CustomerMap : DomainMap<Customer> 
{
  public CustomerMap() 
  {
    // "References" sets up a Many to One
    // relationship from Customer to Address
    References(x => x.HomeAddress)
      .LazyLoad() // This marks the property as "Lazy Loaded"
      .Cascade.All();
  }
}
With Lazy Loading turned on, the Customer object is fetched without the Address data. However, as soon as any caller tries to access the Customer.HomeAddress property for the first time, that data will be transparently loaded.
Do note the virtual modifier on the Customer.HomeAddress property. Not every persistence tool does this, but NHibernate implements lazy loaded properties by creating a dynamic subclass of Customer that overrides the HomeAddress property to make it lazy loaded. The HomeAddress property needs to be marked as virtual in order to allow a subclass to override the property.
Of course, there are other times when you request an object and you know that you will most likely need its children at the same time. In this case, you will probably opt for Eager Loading and have the children data loaded at the same time as the parent. Many persistence tools will have some sort of ability to optimize Eager Loading scenarios to fetch a hierarchy of data in a single database round-trip. If you need the Customer.HomeAddress data most of the time you use a Customer object, then you would be better off doing Eager Loading to get the Customer and Address data at the same time.
At this point, I should repeat the old maxim that the only way to reliably tune an application for performance is to use an empirical measurement of performance with a profiler.

Virtual Proxy Pattern
Lazy Loading is often implemented by using a virtual proxy object that looks just like the real object to be loaded later. Let's say that the domain model includes a class named CustomerRepresentative that references a list of Customer objects. Part of that class is shown in Figure 4.
public class CustomerRepresentative 
{
  // I can create a CustomerRepresentative directly
  // and use it with a normal List
  public CustomerRepresentative() 
  {
    Customers = new List<Customer>();
  }

  // Or I can pass an IList into it.
  public CustomerRepresentative(IList<Customer> customers) 
  {
    Customers = customers;
  }

  // It's not best practice to expose a collection
  // like I'm doing here, but it makes the sample
  // code simpler ;-)
  public IList<Customer> Customers { get; set; }
}
There are many times when the system uses an instance of CustomerRepresentative without needing the list of Customers. In that case, you could simply construct a CustomerRepresentative with a virtual proxy object that looks like an IList<Customer> object and use that virtual proxy class without making any changes to CustomerRepresentative whatsoever. That virtual proxy class might look something like Figure 5. A CustomerRepresentative object could then be created with Lazy Loading as shown in Figure 6.
public class VirtualProxyList<T> : IList<T> 
{
  private readonly Func<IList<T>> _fetcher;
  private IList<T> _innerList;
  private readonly object _locker = new object();

  // One way or another, VirtualProxyList needs to 
  // find the real list.  Let's just cheat and say
  // that something else will pass it a closure
  // that can find the real List
  public VirtualProxyList(Func<IList<T>> fetcher) 
  {
    _fetcher = fetcher;
  }

  // The first call to 
  private IList<T> inner 
  {
    get 
    {
      if (_innerList == null) 
      {
        lock (_locker) 
        {
          if (_innerList == null) 
          {
            _innerList = _fetcher();
          }
        }
      }

      return _innerList;
    }
  }


  IEnumerator IEnumerable.GetEnumerator() 
  {
    return inner.GetEnumerator();
  }

  public IEnumerator<T> GetEnumerator() 
  {
    return inner.GetEnumerator();
  }

  public void Add(T item) 
  {
    inner.Add(item);
  }

  // and the rest of the IList<T> implementation

} 
public class CustomerRepresentativeRepository 
{
  private readonly ICustomerRepository _customers;

  public CustomerRepresentativeRepository(
      ICustomerRepository customers) 
  {
    _customers = customers;
  }

  // This method will "find" a CustomerRepresentative, and
  // set up the Virtual Proxy for the Customers
  public CustomerRepresentative Find(long id) 
  {
    var representative = findRepresentative(id);
    representative.Customers = 
      new VirtualProxyList<Customer>(() => 
      _customers.GetCustomersForRepresentative(id));

    return representative;
  }
}
Like most of the patterns in this article, the virtual proxy isn't something that you're likely to write by hand, but be aware that it's there in the background of your persistence tool.

Taking the Next Step
When I began programming with Windows DNA technologies, I probably spent over half of my time working with raw ADO code. Today, persistence coding and infrastructure is a very small percentage of my team's time. So what changed over the years? We use persistence tools and these design patterns to eliminate so much of the repetitive coding we used to do.
Most of these patterns were taken from Martin Fowler's book, Patterns of Enterprise Application Architecture. I highly recommend reading this book if you have anything to do with writing enterprise applications. Due to length limitations, I was unable to cover some other important patterns like Unit of Work, Specifications, and Persistence Ignorance. In addition, there are quite a number of ways to use the Repository pattern and design considerations (a single generic repository versus specific, "narrow" repository classes, whether a repository should even expose "Save" methods, etc.) I would urge you to research these topics as well, and I may write a follow-up article to continue this discussion.
Lastly, I'd like to say that the .NET ecosystem is richer than just Entity Framework and LINQ to SQL. I'm happy with NHibernate as a Data Mapper that knows relatively little about persistence. SubSonic is a popular Active Record implementation for .NET programming. iBatis.Net is fantastic for existing databases or occasions when you want full control over authoring the SQL statements. LLBLGen Pro is a very mature tool with unique querying abilities. Many of the other tools also have the ability to use LINQ queries.

Send your questions and comments to mmpatt@microsoft.com .

Jeremy Miller, a Microsoft MVP for C#, is also the author of the open-source StructureMap ( structuremap.sourceforge. net ) tool for Dependency Injection with .NET and the forthcoming StoryTeller ( storyteller.tigris.org ) tool for supercharged FIT testing in .NET. Visit his blog, " The Shade Tree Developer ," part of the CodeBetter site.

Modèles dans les exercices pratiques
Modèles de persistance
Jeremy Miller
Accès aux données est un sujet courant parmi les développeurs. Vous avez certainement entendu beaucoup d'avis de technologies d'accès des données spécifiques et infrastructures de persistance, mais que ce qui est la meilleure méthode pour utiliser ces outils de votre projet ? Quels critères utiliser pour sélectionner l'outil approprié pour votre projet ? Ce que vous voulez savoir sur le plan conceptuel sur ces outils avant d'utiliser ? Que se passe-t-il si vous avez sélectionné trop de temps dans votre main et souhaitez écrire votre propre outil de persistance, que devez-vous savoir ?
Unsurprisingly, la réponse à toutes ces questions est d'examiner les modèles de conception sous-jacent de persistance.
Modèles de domaine
Lorsque vous pensez sur comment vous allez structure et représentent la logique métier de votre système, vous avez quelques choix principaux. Dans cet article, je suis largement suppose que vous avez choisi l'approche de modèle de domaine à l'organisation logique métier dans des objets d'entité.
À partir de la description officielle, un modèle de domaine est un modèle d'objet du domaine qui intègre le comportement et les données.
Par exemple, mon projet en cours implique Customer Relationship Management. (CRM) Nous avons objets entité pour les cas d'utilisateur et solution contenant à la fois les données et implémenter les règles métier impliquant des données. Un modèle de domaine peut comprise entre un modèle anemic qui est simplement un ensemble de structures de données pour un modèle très riche qui jealously protège les données brutes derrière une interface étroite (développement Domain-Driven noyau). Où votre modèle de domaine correspond à cette plage est en grande partie une question de comment complexe la logique métier de votre système est véritablement et rapports comment courants ou Saisie de données sont dans votre configuration système.
Une étude adéquate du modèle modèle de domaine est abordée dans cet article. Je vous recommande d'est fortement lire chapitre 2 de modèles d'architecture Enterprise Application livre de Martin Fowler pour une discussion très les modèles de conception majeur pour l'organisation logique métier.
Avant de commencer, examinons les deux façons d'ont le rôle du code d'accès de la base de données et les données dans votre système :
  • La base de données est la keystone de l'application et une immobilisation d'entreprise. Le code d'accès aux données et même le code application ou service sont simplement des mécanismes pour connecter la base de données avec le monde extérieur.
  • Les objets métier dans le niveau intermédiaire et la couche d'interface ou un service utilisateur sont l'application et la base de données est un moyen fiable conserver l'état des objets métier entre les sessions.
Personnellement, je vous comme le premier point de vue datacentric est également comprise dans la communauté .NET, donc j'aimerais vous concentrer sur le deuxième point de vue. Dans le deuxième point de vue, vous travaillez généralement avec les objets entité dans le niveau intermédiaire. Une manière ou d'une autre, vous effectuez probablement un mappage objet/relationnel (o/des comptes clients) pour mapper les données les entités métier vers les tables de base de données et vice versa. Vous pourriez être faire manuellement, mais la plus probable avec des outils de certains tri. Ces outils sont très utiles et peuvent potentiellement gagner beaucoup de temps de développement, mais il existe certains problèmes que vous devez être conscient d'et il est toujours utile de comprendre comment un outil fonctionne sous les couvertures. Nous espérons que étudier les modèles de cet article vous aide sur les deux comptes.
Je suis un grand proponent de Agile pratiques comme développement piloté par tests, développement piloté par comportement, programmation légère et en continu. Je dis uniquement pour désactivez que j'ai un décalage très spécifique concernant aux motifs que je vais aborder dans cet article, et il affichera probablement.

Mappage des objets vers les bases de données
J'ai décidé de modèle que mon système dans le niveau intermédiaire avec entité objets que qu'identité, données et comportement connexe. Ma logique métier à implémenter dans ces objets entité ou dans les services de domaine qui utilisent ces objets d'entité. Tant mieux, mais comment voulez-vous parfaitement données de déplacement avant et arrière entre la base de données et les objets d'entité ?
Dans le cas d'une base de données relationnelle, vous devez déplacer les champs et propriétés de nos objets aux tables et champs de la base de données. Vous pouvez écrire ce code entièrement en main, écriture de distinctes, mise à jour, SELECT, instructions INSERT et DELETE SQL, mais vous devez réaliser rapidement que vous êtes extensible vous-même dans le code très un peu. À savoir, vous à plusieurs reprises spécifiez que les données dans un champ ou propriété objet doivent être stockées dans une colonne spécifique dans une table de base de données.
C'est dans lequel les étapes d'un mappeur objet/relationnel (o/des comptes clients). Lorsque vous utilisez un o/des comptes clients, vous simplement créez un mappage des propriétés à la table de base de données objet et laisser l'outil d'O/des comptes clients utiliser ces métadonnées pour que les instructions SQL doivent être et comment faire pour déplacer données à partir de l'objet vers les instructions SQL.
Nous allons obtenir concrète et examinez un exemple très simple. Mon projet en cours comporte une classe d'adresse qui ressemble à ceci :
public class Address 
{
  public long Id { get; set; }
  public string Address1 { get; set; }
  public string Address2 { get; set; }
  public string City { get; set; }
  public string StateOrProvince { get; set; }
  public string Country { get; set; }
  public string PostalCode { get; set; }
  public string TimeZone { get; set; }
}
Lorsque J'AI configuré le mappage de la classe d'adresse, je dois spécifier qui les mappages de classe adresse à la table, comment les objets adresse va être identifié (la clé primaire), et les propriétés mappent aux tables de base de données.
Pour que cet exemple, j'utilise l'outil NHibernate Fluent pour mapper l'adresse.
Je fais volontairement le mappage d'une manière longhand pour afficher les détails. (Dans l'utilisation réelle, J'AI utilisent convention de configuration Pour éliminer partie le repetitiveness.) Voici le code :
public class AddressMap : ClassMap<Address> 
{
  public AddressMap() 
  {
    WithTable("Address");
    UseIdentityForKey(x => x.Id, "id");

    Map(x => x.Address1).TheColumnNameIs("address1");
    Map(x => x.Address2).TheColumnNameIs("address2");
    Map(x => x.City).TheColumnNameIs("city");
    Map(x => x.StateOrProvince).TheColumnNameIs("province_code");
    Map(x => x.Country).TheColumnNameIs("country_name");
    Map(x => x.PostalCode).TheColumnNameIs("postal_code");
  }
}
Maintenant que vous avez apportées un mappage du modèle objet au modèle de base de données, quelque chose est en réalité exécuter le mappage et vous avez environ deux possibilités : enregistrement active ou de mapper des données.

Enregistrement actif
Lors du choix d'une stratégie de persistance, la première décision que vous devez effectuer est où placer la responsabilité d'effectuer le mappage. Vous avez deux options très différentes : vous pouvez soit rendre chaque classe d'entité responsable du mappage ou vous pouvez utiliser une classe complètement distincte pour effectuer le mappage à la base de données.
La première option est appelée le modèle d'enregistrement active : un objet qui encapsule une ligne dans une table de base de données ou un affichage, encapsule l'accès de base de données et ajoute domaine logique sur ces données. Une approche d'enregistrement active place des méthodes de persistance directement sur l'objet d'entité. Dans ce cas, la classe adresse serait probablement avoir méthodes telles qu'enregistrer, mettre à jour et supprimer, ainsi que d'une méthode statique charge qui interroge la base de données d'un objet adresse.
L'utilisation habituelle du modèle d'enregistrement active consiste à rendre essentiellement un wrapper classe fortement typée autour d'une seule ligne dans une table de base de données. En tant que tels, les classes d'enregistrement active sont généralement un miroir exactement de la structure de base de données (cela va varier entre Outils). De nombreuses implémentations enregistrement active va générer les objets d'entité directement à partir de la structure de base de données.
L'implémentation d'enregistrement active plus célèbre est l'outil ActiveRecord fourni cadre de la Ruby sur Infrastructure de développement Web rails (qui est utilisable dans la programmation .NET avec IronRuby ; voir » Mise en route de IronRuby et RSpec, partie 1 "). Si j'utilisais ActiveRecord, je serait d'abord créer une table nommée adresses dans la base de données. Ensuite, si tout j'intéresse rencontre des accès aux données d'adresses et aux méthodes de recherche, l'enregistrement et insérer des données d'adresses, la classe adresse entière aurait l'apparence exactement cette classe Ruby :
class Address < ActiveRecord::Base
end
L'implémentation Ruby utilise metaprogramming dynamiquement créer des champs et méthodes qui correspondent à la table de base de données nommée adresses (écran pluralized le nom de classe adresse) au moment de l'exécution de requête.
À quelques exceptions près, les implémentations .NET du modèle d'enregistrement active utiliser généralement à l'aide de génération de code pour créer des classes .NET ce mappage directement aux tables de base de données. L'avantage de cette approche est qu'il est très facile conserver le modèle de base de données et objet synchronisé.
Lean programmation
Légère programmation vous apprend à éliminer gaspillage supplémentaire par l'effort de projets de développement par préférence création « pull » sur la création de « commande ». Cela signifie problèmes infrastructure comme persistance doit uniquement être conçu et créé pour satisfaire les besoins de besoins (extraites à la demande) au lieu de créer le code de couche d'accès aux données que vous pensez que l'application devra par la suite (envoyée).
Dans mon expérience, cela signifie vous devez développer le système de manière incrémentielle en développant verticalement, en créant une fonctionnalité à la fois au lieu de créer la couche horizontale un système à la fois. En utilisant cette façon, vous pouvez être sûr que le développement infrastructure est plus que ce que vous avez absolument besoin en liant tout code d'infrastructure à une fonction est intégrée au système. Lorsque vous travaillez horizontalement en créant la couche d'accès de données ou la base de données avant d'écrire l'interface utilisateur, un service ou une couche de logique métier, vous risquez suivantes :
  • Ne pas Obtention commentaires suffisamment tôt à partir participants au projet car il n'existe aucune fonctionnalité de travail pour démontrer
  • Écriture de code infrastructure inutiles de fonctionnalités qui ne peut-être pas en fait obtenir intégré
  • Créer le code d'accès de données incorrect, car vous ne comprenez pas exigence très tôt ou les conditions modifier avant les autres calques sont créés
Création de l'extraction signifie également que votre choix de stratégie d'accès aux données doit être déterminée par les besoins de l'application. Étant donné un choix, J'AI choisissez un o/des comptes clients pour un système où j'a été modélisation la logique métier dans un modèle de domaine. Pour une application de génération d'états, J'AI serait ignorer complètement outils persistance en faveur de simplement l'utilisation SQL et groupes de données. Pour un système qui est principalement la saisie des données, je peut choisir un outil d'enregistrement active. Le point est que l'accès aux données et la persistance en par les besoins de ses consommateurs.

Mapper des données
Bien que l'enregistrement Active modèle peut être, il est souvent utile pour une structure d'objet qui ne varie pas du modèle de base de données. Vous souhaitez mapper plusieurs classes à la même table de base de données. Vous pouvez avoir un schéma de base de données héritée qu'elle n'est pas s'adapte à la forme des objets qui vous souhaitez exprimer dans une logique métier (une commune occurrence pour moi projets de mon dernier plusieurs).
Il s'agit où je Choisissez le motif de mapper des données : une couche d'objets mappeur qui déplacent des données entre les objets et une base de données tout en conservant les indépendante de l'autre et le mappeur de classes eux-mêmes. Le modèle de données mappeur supprime la plupart de la responsabilité de persistance des objets entité en faveur des classes externes aux entités. Avec un motif de mapper des données, vous accédez, requête et enregistrez des objets entité avec un type de stockage (décrite plus loin dans cet article).
Comment choisir entre les deux ? Mon décalage personnel est très fortement vers la solution de mapper des données. Qui part enregistrement active est idéale pour systèmes avec logique domaine plus simple, applications avec beaucoup de CRUD (Créer, lire, mettre à jour et supprimer, c'est-à-dire) et les situations où le modèle de domaine n'a pas besoin divergent beaucoup de la structure de base de données. Enregistrement actif peut être plus confortable pour plusieurs équipes de développement .NET car elle implique un moyen datacentric d'utilisation c'est-à-dire plus courants pour les équipes .NET et, pris en franchement, beaucoup mieux charge par .NET. J'AI serait caractériser la plupart des outils de persistance de l'espace de .NET comme outils d'enregistrement active.
D'autre part, mapper des données est plus approprié pour les systèmes avec logique complexe domaine où la forme de modèle de domaine est divergent considérablement à partir du modèle de base de données. Mapper des données decouples également vos classes de modèle de domaine à partir de la banque persistance. Qui peut être important pour les cas où vous devez réutiliser le modèle de domaine avec les moteurs de base de données différente, schémas et mécanismes de stockage même différents complètement.
Plus important pour moi comme un praticien Agile, l'approche de mapper des données me permet de concevoir le modèle d'objet indépendamment de la base de données avec le développement piloté par tests plus efficacement à un enregistrement active solution peut. Ceci est important pour les équipes qui veulent franchir une approche incrémentielle pour concevoir, car elle permet une équipe pour réaliser la conception dans le modèle d'objet tout d'abord où la refactorisation d'outils et techniques test d'unité sont généralement plus efficaces, puis créez le modèle de base de données ultérieurement lorsque le modèle d'objet est stable. Le motif de mapper des données est également plus applicable à l'approche architecturale domaine géré par création qui le de popularité de la communauté .NET.

L'aide d'un espace de stockage
Lorsque je devenait en tant que développeur dans les jours de Windows DNA, J'AI résidaient mortal peur d'oubli fermer un objet Connection ADO dans mon code. Sur mon premier projet de grande entreprise, J'AI codé directement par rapport à ADO. Chaque fois que J'AVAIS besoin demander ou enregistrez des données dans la base de données, J'AI dû effectuer les opérations suivantes :
  1. Pour trouver la chaîne de connexion à partir d'un type de configuration
  2. Ouvrir une nouvelle connexion à la base de données
  3. Créer un objet de commande ou un objet recordset
  4. Exécuter l'instruction SQL ou une procédure stockée
  5. Fermer la connexion pour libérer les ressources de base de données
  6. Et oh Oui, placez une erreur adéquate gestion autour le code d'accès aux données
Le premier problème était la quantité absurd de code répétitif que je a été écrire. Le deuxième problème était que J'AI devaient écrire le code correctement chaque fois unique, car oubli fermer une seule connexion peut et Malheureusement n'a, lecteur un système critiques en mode hors connexion sous une charge lourde. Personne ne souhaite être la personne dont le code réduit d'hygiène de la connexion de base de données une mauvaise, mais est qu'il reste devenu trop fréquemment.
Dans mon projet suivant Mon collègue et ai plus intelligents. Nous avons implémenté une classe unique qui serait ne tous les paramètres, destruction et code de l'objet Connection ADO de gestion des erreurs. Toute autre classe dans le système peut interagir avec la base de données en utilisant cette classe centrale pour appeler des procédures stockées. Nous avons écrit beaucoup moins de code et, mieux encore, nous n'ont pas été plagued par des problèmes résultant de l'oubli fermer les connexions de base de données, car nous avions obtenir ce code de gestion de connexion droite qu'une seule fois.
Ce que mon équipe a fait était de créer une implémentation crude du motif référentiel qui gère entre le domaine et les données couches de mise en correspondance avec une interface semblable à la collection pour accéder aux objets du domaine.
En fait, le modèle d'espace de stockage signifie simplement placer une façade sur votre système de persistance afin que vous pouvez protègent le reste de votre code d'application de devoir connaître le fonctionne de persistance. Mon projet en cours utilise un espace de stockage avec une interface publique qui ressemble à celle de la figure 1 .
public interface IRepository 
{
  // Find an entity by its primary key
  // We assume and enforce that every Entity
  // is identified by an "Id" property of 
  // type long
  T Find<T>(long id) where T : Entity;

  // Query for a specific type of Entity
  // with Linq expressions.  More on this later
  IQueryable<T> Query<T>();
  IQueryable<T> Query<T>(Expression<Func<T, bool>> where);

  // Basic operations on an Entity
  void Delete(object target);
  void Save(object target);
  void Insert(object target);

  T[] GetAll<T>();
}
Lorsqu'une classe dans le système doit accéder à un objet entité, il peut simplement utiliser IRepository pour extraire cette entité par son ID ou une requête pour une liste d'objets entité avec une expression LINQ.
Lorsque j'utilise la classe concrète de IRepository qui utilise la bibliothèque NHibernate o/des comptes clients, je vais extraire une chaîne de connexion de la mémoire, chargement les définitions de mappage à partir d'un assembly, création de la SessionFactory NHibernate (une seule fois et une seule fois, car elle est un accès performances grand) et l'interface ISession plus bas niveau à partir de NHibernate d'habillage. Avec certains Aide de la classe référentiel, NHibernate gère les problèmes de cycle de vie de connexion de base de données.
Whew. C'est beaucoup de choses en cours d'exécution en arrière-plan. C'est une bonne chose que j'ai swept tous l'interaction directe avec NHibernate derrière l'interface IRepository. Je n'ai pas connaître tout que choses NHibernate amorçage uniquement pour charger, enregistrer et objets de requête. Mieux encore, car chaque classe dépend de l'interface IRepository abstraite d'accès aux données et la persistance, J'AI peut diapositive dans une implémentation InMemoryRepository de IRepository utilise LINQ to Objects en interne pour talon la base de données au cours des tests.

Carte d'identité
Examinons un scénario courant. Dans une transaction unique logique dans certains tri du système destinataire, vous avez deux classes totalement différents qui fonctionnent indépendamment, mais les deux classes devez récupérer la même entité client au cours de la transaction. Idéalement, vous souhaitez que seul un objet client unique dans une transaction unique pour chaque client logique afin que chaque objet fonctionne de données cohérentes.
Dans la persistance Outillages, empêchant les références logiques en double est le travail du motif Identity Map. Comme indiqué par Fowler Martin, une carte d'identité garantit que chaque objet obtient chargé une seule fois par conservant chaque objet chargé dans un plan et recherche des objets utilisant la carte pour faire référence à leur.
Pour la classe de clients, vous pouvez créer une implémentation naïf de carte d'identité semblable à celui dans la figure 2 . Je suis volontairement laisser des threads verrouillage ici, simplement pour simplifier le code. Une implémentation réelle nécessiterait mesures de sécurité thread adéquate.
public class CustomerRepository 
{
  public IDictionary<long, Customer> _customers = 
    new Dictionary<long, Customer>();

  public Customer FindCustomer(long id) 
  {
    if (_customers.ContainsKey(id)) 
  {
      return _customers[id];
    }

    var customer = findFromDatabase(id);
    _customers.Add(id, customer);

    return customer;
  }

  private Customer findFromDatabase(long id) 
  {
    throw new System.NotImplementedException();
  }
}
Dans cet exemple, un objet client est identifié par son code. Lorsque vous demandez une instance du client par code, le CustomerRepository vérifie d'abord un dictionnaire interne pour voir si elle a de ce client particulier. Si tel est le cas, elle renvoie l'objet client existant. Dans le cas contraire, CustomerRepository va extraire les données de la base de données, créer un nouveau client, stocker cet objet client dans son dictionnaire des demandes ultérieures et renvoyer le nouvel objet client.
Heureusement, vous en général n'est pas écrire ce code à la main car un outil de persistance arrivent à maturité doit inclure cette fonctionnalité. Vous devez N'oubliez pas que cela se passe en coulisses et étendue des objets de prise en charge de persistance en conséquence. De nombreuses équipes sont utilisent la fonctionnalité de gestion du cycle de vie d'une Inversion de contrôle outil (StructureMap, Windsor, Ninject et d'autres utilisateurs) pour vous assurer que toutes les classes dans une seule demande HTTP ou thread sont utilisant le même sous-jacente carte d'identité. Le motif d'unité de travail est un autre moyen de gérer une carte d'identité unique entre plusieurs classes dans une même transaction logique.
Pour illustrer ce modèle plus loin, figure 3 illustre un exemple du fonctionnement d'une carte d'identité. Le code est écrit par rapport à l'architecture de mon projet en cours. Les instances de l'interface IRepository illustrée dans le code ci-dessous ajuster un seul NHibernate ISession, qui à son tour implémente le modèle carte d'identité. Lorsque j'exécute ce test, la sortie est cela :
1 passed, 0 failed, 0 skipped, took 5.86 seconds.
[Test]
public void try_out_the_identity_map() 
{
  // All I'm doing here is getting a fully formed "Repository"
  // from an IoC container and letting an IoC tool bootstrap 
  // NHibernate offstage.
  IRepository repository = ObjectFactory.GetInstance<IRepository>();

  // Find the Address object where Id == 1
  var address1 = repository.Find<Address>(1);

  // Find the Address object where Id == 1 from the same Repository
  var address2 = repository.Find<Address>(1);

  // Requesting the same identified Address object (Id == 1) inside the 
  // same Repository / Identity Map should return the exact same
  // object
  address1.ShouldBeTheSameAs(address2);

  // Now, let's create a completely new Repository that has a 
  // totally different Identity Map
  IRepository secondRepository = ObjectFactory.GetInstance<IRepository>();

  // Nothing up my sleeve...
  repository.ShouldNotBeTheSameAs(secondRepository);

  var addressFromSecondRepository = secondRepository.Find<Address>(1);

  // and this is a completely different Address object, even though
  // it's loaded from the same database with the same Id
  addressFromSecondRepository.ShouldNotBeTheSameAs(address1);
}

Différée et impatient de chargement
Une des choses meilleurs sur l'utilisation d'un outil de persistance est la possibilité de charger un objet racine (facturation, par exemple), puis accédez directement à ses enfants (InvoiceLineItem) et les objets associés uniquement en utilisant les propriétés de la classe parent. Toutefois, tôt ou tard vous allez à la charge sur les performances de votre application. Extraction d'un graphique d'objet dans son intégralité lorsque vous avez peut-être besoin seulement le niveau supérieur de la plupart du temps d'objet ou pouvez renoncer parties de l'objet graphique n'est efficace.
C'est OK. Dans ce cas, vous pouvez utiliser le modèle de chargement différée reporter l'initialisation de l'objet jusqu'à ce que juste avant qu'il est nécessaire.
Nous allons placer ce dans conditions plus concrets. Que que vous ayez une classe nommée client qui fait référence à un objet adresse :
public class Customer : DomainEntity 
{
  // The "virtual" modifier is important.  Without it,
  // Lazy Loading can't work
  public virtual Address HomeAddress { get; set; }
}
Dans la plupart utiliser cas concernant l'objet client, le code doit jamais la propriété Customer.HomeAddress. Dans ce cas, vous pouvez configurer le mappage de base de données pour rendre la Customer.HomeAddress propriété différée chargée comme dans ce mappage NHibernate Fluent :
public class CustomerMap : DomainMap<Customer> 
{
  public CustomerMap() 
  {
    // "References" sets up a Many to One
    // relationship from Customer to Address
    References(x => x.HomeAddress)
      .LazyLoad() // This marks the property as "Lazy Loaded"
      .Cascade.All();
  }
}
Avec différée chargement activé, l'objet Customer est extraites sans les données d'adresse. Toutefois, dès que n'importe quel appelant tente d'accéder à la propriété Customer.HomeAddress pour la première fois, ces données seront en toute transparence chargées.
Notez le modificateur virtuel de la propriété Customer.HomeAddress. Pas chaque outil persistance fait cela, mais NHibernate implémente des propriétés chargées différées en créant une sous-classe dynamique du client qui remplace la propriété HomeAddress pour le rendre différée chargé. La propriété HomeAddress doit être marquée comme virtuelle afin de permettre une sous-classe de remplacer la propriété.
Bien sûr, il existe d'autres fois lorsque vous demandez un objet et vous savez qu'il est probablement nécessaire ses enfants à la fois. Dans ce cas, vous êtes probablement opter pour impatient de chargement et ont les données enfants chargées en même temps que le parent. De nombreux outils de persistance auront un type de capacité pour optimiser le chargement impatient de scénarios pour extraire une hiérarchie de données dans un aller-retour de base de données unique. Si vous avez besoin des données Customer.HomeAddress la plupart du temps, vous utilisez un objet client, puis vous serait préférable de faire impatient de chargement pour obtenir les données client et adresse en même temps.
À ce stade, je doit Répétez le maxim ancien que le seul moyen fiable optimiser les performances d'une application doit utiliser une mesure empirical de performances avec un générateur de profils.

Modèle de proxy virtuel
Chargement différée est souvent implémenté en utilisant un objet proxy virtuel qui ressemble à l'objet réel doit être chargé ultérieurement. Supposons que le modèle de domaine inclut une classe nommée CustomerRepresentative qui fait référence à une liste d'objets de client. Partie de cette classe est illustré figure 4 .
public class CustomerRepresentative 
{
  // I can create a CustomerRepresentative directly
  // and use it with a normal List
  public CustomerRepresentative() 
  {
    Customers = new List<Customer>();
  }

  // Or I can pass an IList into it.
  public CustomerRepresentative(IList<Customer> customers) 
  {
    Customers = customers;
  }

  // It's not best practice to expose a collection
  // like I'm doing here, but it makes the sample
  // code simpler ;-)
  public IList<Customer> Customers { get; set; }
}
Il existe souvent quand le système utilise une instance de CustomerRepresentative sans devoir la liste des clients. Dans ce cas, vous pouvez simplement créer un CustomerRepresentative avec un objet proxy virtuel que se présente comme un IList <customer> objet et utilise cette classe de proxy virtuel sans modifier CustomerRepresentative fasse. Cette classe de proxy virtuel pourrait ressembler à la figure 5 . Un objet CustomerRepresentative peut ensuite être créé avec chargement différée comme illustré figure 6 .
public class VirtualProxyList<T> : IList<T> 
{
  private readonly Func<IList<T>> _fetcher;
  private IList<T> _innerList;
  private readonly object _locker = new object();

  // One way or another, VirtualProxyList needs to 
  // find the real list.  Let's just cheat and say
  // that something else will pass it a closure
  // that can find the real List
  public VirtualProxyList(Func<IList<T>> fetcher) 
  {
    _fetcher = fetcher;
  }

  // The first call to 
  private IList<T> inner 
  {
    get 
    {
      if (_innerList == null) 
      {
        lock (_locker) 
        {
          if (_innerList == null) 
          {
            _innerList = _fetcher();
          }
        }
      }

      return _innerList;
    }
  }


  IEnumerator IEnumerable.GetEnumerator() 
  {
    return inner.GetEnumerator();
  }

  public IEnumerator<T> GetEnumerator() 
  {
    return inner.GetEnumerator();
  }

  public void Add(T item) 
  {
    inner.Add(item);
  }

  // and the rest of the IList<T> implementation

} 
public class CustomerRepresentativeRepository 
{
  private readonly ICustomerRepository _customers;

  public CustomerRepresentativeRepository(
      ICustomerRepository customers) 
  {
    _customers = customers;
  }

  // This method will "find" a CustomerRepresentative, and
  // set up the Virtual Proxy for the Customers
  public CustomerRepresentative Find(long id) 
  {
    var representative = findRepresentative(id);
    representative.Customers = 
      new VirtualProxyList<Customer>(() => 
      _customers.GetCustomersForRepresentative(id));

    return representative;
  }
}
Comme la plupart des modèles dans cet article, le proxy virtuel est quelque chose que vous êtes susceptible d'écrire à la main, mais n'oubliez pas qu'il est là dans l'arrière-plan de votre outil de persistance.

Prise de l'étape suivante
Au début de programmation avec technologies Windows DNA, probablement j'passé sur la moitié de mon temps à travailler avec du code ADO brut. Aujourd'hui, persistance codage et l'infrastructure est un pourcentage très petit du temps de mon équipe. Alors que changé au fil des années ? Nous utilisons outils persistance et ces modèles de conception pour éliminer, partie le codage répétitives que nous utilisé pour faire.
La plupart de ces modèles ont été extraites de livre de Martin Fowler, modèles d'architecture Enterprise Application. Je vous recommande vivement de lire ce livre si vous avez rien à voir d'écrire des applications d'entreprise. En raison de limitations longueur, J'AI Impossible couvrir certains autres motifs importants comme unité de travail, les spécifications et ignorance persistance. En outre, il existe assez de plusieurs façons d'utiliser les référentiel motif et la conception Considérations (un générique référentiel unique et spécifique, "restreindre « classes du référentiel, si un espace de stockage doit exposer même « enregistrer » méthodes, etc..) J'AI serait demandent vous permet de rechercher les rubriques ainsi et je peut écrire un article suivi pour continuer cette discussion.
Enfin, j'aimerais que à dire que l'écosystème .NET est plus riche que simplement Entity Framework et LINQ to SQL. Je suis satisfait NHibernate comme un mappeur de données qui sait relativement peu sur la persistance des objets. SubSonic est une implémentation d'enregistrement active courante pour la programmation .NET. iBatis.Net est fantastique pour bases de données existantes ou parfois que vous souhaitiez complet contrôler les instructions SQL de création. LLBLGen Pro est un outil très maturité avec capacités interrogation uniques. Plusieurs autres outils ont également la possibilité pour utiliser les requêtes LINQ.

Veuillez envoyer vos questions et commentaires à mmpatt@microsoft.com .

Jeremy Miller, MVP Microsoft pour Visual c# est également l'auteur de la (StructureMap open source structuremap.sourceforge. NET ) outil pour l'injection de dépendance avec .NET et le prochain (StoryTeller storyteller.tigris.org ) outil supercharged FIT test dans .NET. Visitez son blog » Le développeur d'organigramme Affichage « partie du site CodeBetter.

Page view tracker