Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
210 views
in Technique[技术] by (71.8m points)

ASP.MVC: Repository that reflects IQueryable but not Linq to SQL, DDD How To question

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 led to a

foreach (MyStore.Models.MiniProduct myproduct in Model) {}

Thank you both for the answers.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


For a more thorough discussion of a very similar topic, see IQueryable is Tight Coupling.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...