views:

1213

answers:

2

I want to create a DDD repository that returns IQueryable Entities that match the Linq to SQL underlying classes, minus any relations. I can easily return Entities minus the relations with a Linq select new {field, field, ... } projection. How do I code the Repository Entity class? How would I return an object from the repository having the repository class not the Linq to SQL class and still fill it with multiple entities from the Linq selection? How would I reference this returned class in my ViewModel?

I am very new to this, thus the obvious mistakes. Am I missing the boat and should only return complete Entities from the repository, not a projection? I would still need to strip out the Linq to SQL relations before I send them from the repository. Am I totally off base? I really want to keep the IQueryable data type.

For example, my Linq to SQL code in my repository:

public class MiniProduct
{
    public MiniProduct( string ProductNo, string Name, string Description, double Price)
    {    this.ProductNo = ProductNo;
         this.Name = Name;
         this.Description = Description;
         this.Price = Price;
    }
}

public IQueryable<MiniProduct> GetProductsByCategory( string productCategory)
{
    return ( from p in db.Products
             from c in db.Categories
             from pc in db.ProductCategories
             where c.CategoryName == productCategory &&
                   c.CatID == pc.CatID &&
                   pc.ProductID == p.ProductID
             select new { p.ProductNo, p.Name, p.Description, p.Price } );
    // how to return IQueryable<MiniProduct> instead of IQueryable<AnonymousType>???
}

And in the View (trying to strongly type ViewModel) what would be my model data type and how to reference from the view?

<% Page Inherits="System.Web.Mvc.ViewPage<MyStore.Models.MiniProduct>" %>

Edit:

Cottsak empowered the code and made it work, so he earns the checkbox. However, Mark Seemann pointed out that this technique will cause side effects. He was right in that projecting or sub-setting your POCO is bad. After making the code work, I ended up making a ton more one off entity objects, which causes unnecessary complications. Ultimately I changed the code to reflect Mark's suggestions.

To add to Cottsak's suggestions: My repository return value was IQueryable. The page directive model reference type was

Inherits="System.Web.Mvc.ViewPage<IQueryable<MyStore.Models.MiniProduct>>"

The Model fields were accessed by:

Model.SingleOrDefault().ProductNo
Model.SingleOrDefault().Name
...

And this lead to a foreach (MyStore.Models.MiniProduct myproduct in Model) {}

Thank you both for the answers.
+15  A: 

Assuming that your LINQ to SQL (L2S) classes are auto-generated and reflects your underlying database, the short answer is: Don't expose IQueryable of any of your L2S classes - it would be a Leaky Abstraction.

The slightly longer answer:

The whole point of Repositories is to hide data access behind an abstraction so that you can replace or vary your data access code independently of your Domain Model. That is not going to be possible when you base the Repository interface on types defined within a specific implementation (your L2S-based Data Access Component (DAC)) - even if you could provide a new implementation of your Repository interface, you would need to reference your L2S DAC. That wouldn't play particularly nice if you suddenly decided to switch to LINQ to Entities, or the Azure Table Storage Service.

Domain Objects should be defined in a technology-neutral way. This is best done as Plain Old C# Objects (POCO).

Furthermore, exposing IQueryable gives you the opportunity to perform projections. That may sound attractive, but is actually rather dangerous in a DDD context.

In DDD, we should design Domain Objects so that they encapsulate Domain Logic and ensure all invariants.

As an example, consider the concept of an Entity (not a LINQ to Entitities Entity, but a DDD Entity). Entities are almost always identified by a persistent ID, so one common invariant is that the ID must be defined. We could write a base Entity class like this:

public abstract class Entity
{
    private readonly int id;

    protected Entity(int id)
    {
        if(id <= 0)
        {
            throw new ArgumentOutOfRangeException();
        }
        this.id = id;
    }

    public int Id
    {
        get { return this.id; }
    }
}

Such a class nicely enforces its invariants (in this case that the ID must be a positive number). There may be a lot of other, more Domain-specific invariants implemented in particular classes, but many of these are very likely to be at odds with the concept of projection: You might be able to define a projection that omits certain properties (such as the ID), but it will crash at run-time because the default values for the types (such as 0 for int) is going to throw exceptions.

In other words, even if you need only certain properties of a Domain Object, it will only make sense to hydrate it in its entirety, because that is the only way you can satisfy its invariants.

In any case, if you frequently find that you need to select only certain parts of your Domain Objects, it may be because they violate the Single Responsibility Principle. It might be a better idea to spilt the Domain Class into two or more classes.

In conclusion, exposing IQueryable sounds like a very attractive strategy, but with the current implementation of L2S it will lead to Leaky Abstractions and Anemic Domain Models.

With the next version of the Entity Framework, we should get POCO support, so it may move us closer to that goal. However, as far as I'm concerned, the jury is still out on that account.

Mark Seemann
One big improvement to my learning was your comment about never sub-setting repository classes along with stripping out Linq to Sql's IQueryable. However, doesn't the POCO class we create act as a cookie cutter stripping out the original Linq to Sql IQueryable and relationships, creating a new IQueryable object? I wouldn't think this is any different than List<class>. This also allows you to return things like LazyList<class>(IQueryable<class> object), or do you think this should be a layer on top of the Repository, like AOP or cross cutting concerns?
Dr. Zim
@Zim: I don't think that's what Mark is saying. I'm not as clear with DDD as Mark but i believe that core principals of DDD seem to be violated in Mark's opinion: namely, correctly encapsulating the 'Domain' within the Domain Model (data and behaviour [Fowler]). One of the easiest. I can see how enabling the projection of L2S onto POCO (Domain?) objects might make it difficult do design Domain Objects in a DDD fashion, however i would be happy to attempt to justify my implementation of an `IQueryable` returning Repo under DDD constraints for practical purposes...
cottsak
... I add the 'practical purposes' caveat because i know DDD purists might not agree with certain aspects of my design but i believe it encompass -enough- DDD to benefit from DDD and be practical with the technologies available. With specific reference to LinqToSql, i would confess that 'IQueryable' runs the risk of coupling the POCO model tightly to the LinqToSql differences to pure LINQ, but i am also confident that my 'Lazy*' classes appropriately remove this coupling such that my model flies under it's own steam.
cottsak
I would generally disagree that a 'IQueryable' Repo creates a Leaky Abstraction. Knowing some of the intricacies of LinqToSql allows me to agree that an 'IQueryable' Repo implementing LinqToSql -can- create a leaky abstraction, but i think there are methods to avoid it.
cottsak
To clarify: In theory I find the idea of exposing IQueryable very attractive and not at all at odds with DDD. It (currently) fails when it comes to the practical implementation details because it's too hard to decouple the implementation details from the interface. One day we may get there, but we are not there now. It's not IQueryable itself that causes the Leaky Abstraction, but rather the 'T' in IQueryable.
Mark Seemann
Mark, did you have reconsidered your position with EF POCO about IQueryable ?
Thomas Jaskula
Last time I checked, the 'POCO support' in EF4 still does not allow non-default constructors, so I have yet to change my position.
Mark Seemann
However, you could vote for that feature here: http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/1051641-support-for-poco-entities-without-default-construc
Mark Seemann
+2  A: 

Try something like this:

public class AnnouncementCategory : //...
{
    public int ID { get; set; }
    public string Name { get; set; }
}

.. and in your repo:

public IQueryable<AnnouncementCategory> GetAnnouncementCategories()
{
    return from ac in this._dc.AnnouncementCategories
           let announcements = this.GetAnnouncementsByCategory(ac.ID)
           select new AnnouncementCategory
           {
               ID = ac.ID,
               Name = ac.Text,
               Announcements = new LazyList<Announcement>(announcements)
           };
}

private IQueryable<Announcement> GetAnnouncementsByCategory(int categoryID)
{
    return GetAnnouncements().Where(a => a.Category.ID == categoryID);
}

This way, instead of projecting into an anonymous type, i'm projecting into a new instance of my AnnouncementCategory class. You can ignore the GetAnnouncementsByCategory function if you like, this is used to chain the collection of associated Announcement objects for each category but in a way so that they can be lazy loaded with IQueryable (ie. so that when i do eventually call on this property, i don't have to call the entire collection. i can do more LINQ on this to filter).

cottsak
Let me know if you need more info about the chaining of object graphs with lazy loading classes. It's good fun
cottsak
Wow. I am going to print this out and put it on my wall of all time coolest code I have ever seen. Using the let statement define a set then lazy load that set later in the same query is really remarkable. Where you do find out such patterns?
Dr. Zim
Wow.. i didn't think it was that awesome. But it did take me quite a bit of research to find the "let" part - before i used let (and the separate function - which btw is -absolutely- necessary for this 2 work) everything would work but except the lazy querying (deferred execution). Without the use of let and the function all chained objects in the graph would query immediately (like an enumeration triggers with LinqToSql). This method is documented by MS and as far as i know is the only way to map the deferred execution to another lazy init pattern. Lazy classes i use: http://bit.ly/1Aaxx5
cottsak