开发者

How to expose the DataContext from with-in a DataContext class?

Is it possible to expose the DataContext when extending a class in the DataContext? Consider this:

public partial class SomeClass {
    public object SomeExtraProperty {
        this.DataContext.ExecuteQuery<T>("{SOME_REALLY_COMPLEX_QUERY_THAT_HAS_TO_BE_IN_RAW_SQL_BECAUSE_LINQ_GENERATES_CRAP_IN_THIS INSTANCE}");
    }
}

How can I go about doing this? I have a sloppy version working now, where I pass the DataContext to the view model and from there I pass it to the method I have setup in the partial class. I'd like to avoid the whole DataContext passing around and just have a property that I can reference.

UPDATE FOR @Aaronaught

So, how would I go about writing the code? I know that's a vague question, but from what I've seen online so far, all the tutorials feel like they assume I know where to place the code and how use it, etc.

Say I have a very simple application structured as (in folders):

  • Controllers
  • Models
  • Views

Where do the repository files go? In the Models folder or can I create a "Repositories" folder just for them?

Past that how is the repository aware of the DataContext? Do I have to create a new instance of it in each method of the repository (if so that seems in-efficient... and wouldn't that cause problems with pulling an object out of one instance and using it in a controller that's using a different instance...)?

For example I currently have this setup:

public class BaseController : Controller {
    protected DataContext dc = new DataContext();
}

public class XController : BaseController {
    // stuff
}

This way I have a "global" DataContext available to all controllers who inherit from BaseController. It is my understanding that that is efficient (I could be 开发者_运维问答wrong...).

In my Models folder I have a "Collections" folder, which really serve as the ViewModels:

public class BaseCollection {
    // Common properties for the Master page
}

public class XCollection : BaseCollection {
    // X View specific properties
}

So, taking all of this where and how would the repository plug-in? Would it be something like this (using the real objects of my app):

public interface IJobRepository {
    public Job GetById(int JobId);
}

public class JobRepository : IJobRepository {
    public Job GetById(int JobId) {
        using (DataContext dc = new DataContext()) {
            return dc.Jobs.Single(j => (j.JobId == JobId));
        };
    }
}

Also, what's the point of the interface? Is it so other services can hook up to my app? What if I don't plan on having any such capabilities?

Moving on, would it be better to have an abstraction object that collects all the information for the real object? For example an IJob object which would have all of the properties of the Job + the additional properties I may want to add such as the Name? So would the repository change to:

public interface IJobRepository {
    public IJob GetById(int JobId);
}

public class JobRepository : IJobRepository {
    public IJob GetById(int JobId) {
        using (DataContext dc = new DataContext()) {
            return dc.Jobs.Single(j => new IJob {
                Name = dc.SP(JobId)  // of course, the project here is wrong,
                                     // but you get the point...
            });
        };
    }
}

My head is so confused now. I would love to see a tutorial from start to finish, i.e., "File -> New -> Do this -> Do that".

Anyway, @Aaronaught, sorry for slamming such a huge question at you, but you obviously have substantially more knowledge at this than I do, so I want to pick your brain as much as I can.


Honestly, this isn't the kind of scenario that Linq to SQL is designed for. Linq to SQL is essentially a thin veneer over the database; your entity model is supposed to closely mirror your data model, and oftentimes your Linq to SQL "entity model" simply isn't appropriate to use as your domain model (which is the "model" in MVC).

Your controller should be making use of a repository or service of some kind. It should be that object's responsibility to load the specific entities along with any additional data that's necessary for the view model. If you don't have a repository/service, you can embed this logic directly into the controller, but if you do this a lot then you're going to end up with a brittle design that's difficult to maintain - better to start with a good design from the get-go.

Do not try to design your entity classes to reference the DataContext. That's exactly the kind of situation that ORMs such as Linq to SQL attempt to avoid. If your entities are actually aware of the DataContext then they're violating the encapsulation provided by Linq to SQL and leaking the implementation to public callers.

You need to have one class responsible for assembling the view models, and that class should either be aware of the DataContext itself, or various other classes that reference the DataContext. Normally the class in question is, as stated above, a domain repository of some kind that abstracts away all the database access.

P.S. Some people will insist that a repository should exclusively deal with domain objects and not presentation (view) objects, and refer to the latter as services or builders; call it what you like, the principle is essentially the same, a class that wraps your data-access classes and is responsible for loading one specific type of object (view model).


Let's say you're building an auto trading site and need to display information about the domain model (the actual car/listing) as well as some related-but-not-linked information that has to be obtained separately (let's say the price range for that particular model). So you'd have a view model like this:

public class CarViewModel
{
    public Car Car { get; set; }
    public decimal LowestModelPrice { get; set; }
    public decimal HighestModelPrice { get; set; }
}

Your view model builder could be as simple as this:

public class CarViewModelService
{
    private readonly CarRepository carRepository;
    private readonly PriceService priceService;

    public CarViewModelService(CarRepository cr, PriceService ps) { ... }

    public CarViewModel GetCarData(int carID)
    {
        var car = carRepository.GetCar(carID);
        decimal lowestPrice = priceService.GetLowestPrice(car.ModelNumber);
        decimal highestPrice = priceService.GetHighestPrice(car.ModelNumber);
        return new CarViewModel { Car = car, LowestPrice = lowestPrice,
            HighestPrice = highestPrice };
    }
}

That's it. CarRepository is a repository that wraps your DataContext and loads/saves Cars, and PriceService essentially wraps a bunch of stored procedures set up in the same DataContext.

It may seem like a lot of effort to create all these classes, but once you get into the swing of it, it's really not that time-consuming, and you'll ultimately find it way easier to maintain.


Update: Answers to New Questions

Where do the repository files go? In the Models folder or can I create a "Repositories" folder just for them?

Repositories are part of your model if they are responsible for persisting model classes. If they deal with view models (AKA they are "services" or "view model builders") then they are part of your presentation logic; technically they are somewhere between the Controller and Model, which is why in my MVC apps I normally have both a Model namespace (containing actual domain classes) and a ViewModel namespace (containing presentation classes).

how is the repository aware of the DataContext?

In most instances you're going to want to pass it in through the constructor. This allows you to share the same DataContext instance across multiple repositories, which becomes important when you need to write back a View Model that comprises multiple domain objects.

Also, if you later decide to start using a Dependency Injection (DI) Framework then it can handle all of the dependency resolution automatically (by binding the DataContext as HTTP-request-scoped). Normally your controllers shouldn't be creating DataContext instances, they should actually be injected (again, through the constructor) with the pre-existing individual repositories, but this can get a little complicated without a DI framework in place, so if you don't have one, it's OK (not great) to have your controllers actually go and create these objects.

In my Models folder I have a "Collections" folder, which really serve as the ViewModels

This is wrong. Your View Model is not your Model. View Models belong to the View, which is separate from your Domain Model (which is what the "M" or "Model" refers to). As mentioned above, I would suggest actually creating a ViewModel namespace to avoid bloating the Views namespace.

So, taking all of this where and how would the repository plug-in?

See a few paragraphs above - the repository should be injected with the DataContext and the controller should be injected with the repository. If you're not using a DI framework, you can get away with having your controller create the DataContext and repositories, but try not to cement the latter design too much, you'll want to clean it up later.

Also, what's the point of the interface?

Primarily it's so that you can change your persistence model if need be. Perhaps you decide that Linq to SQL is too data-oriented and you want to switch to something more flexible like Entity Framework or NHibernate. Perhaps you need to implement support for Oracle, mysql, or some other non-Microsoft database. Or, perhaps you fully intend to continue using Linq to SQL, but want to be able to write unit tests for your controllers; the only way to do that is to inject mock/fake repositories into the controllers, and for that to work, they need to be abstract types.

Moving on, would it be better to have an abstraction object that collects all the information for the real object? For example an IJob object which would have all of the properties of the Job + the additional properties I may want to add such as the Name?

This is more or less what I recommended in the first place, although you've done it with a projection which is going to be harder to debug. Better to just call the SP on a separate line of code and combine the results afterward.

Also, you can't use an interface type for your Domain or View Model. Not only is it the wrong metaphor (models represent the immutable laws of your application, they are not supposed to change unless the real-world requirements change), but it's actually not possible; interfaces can't be databound because there's nothing to instantiate when posting.

So yeah, you've sort of got the right idea here, except (a) instead of an IJob it should be your JobViewModel, (b) instead of an IJobRepository it should be a JobViewModelService, and (c) instead of directly instantiating the DataContext it should accept one through the constructor.

Keep in mind that the purpose of all of this is to keep a clean, maintainable design. If you have a 24-hour deadline to meet then you can still get it to work by just shoving all of this logic directly into the controller. Just don't leave it that way for long, otherwise your controllers will (d)evolve into God-Object abominations.


Replace {SOME_REALLY_COMPLEX_QUERY_THAT_HAS_TO_BE_IN_RAW_SQL_BECAUSE_LINQ_GENERATES_CRAP_IN_THIS INSTANCE} with a stored procedure then have Linq to SQL import that function.

You can then call the function directly from the data context, get the results and pass it to the view model.

I would avoid making a property that calls the data context. You should just get the value from a service or repository layer whenever you need it instead of embedding it into one of the objects created by Linq to SQL.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜