This is the perfect book you should read about how to properly model (the "M" in MVC) the repositories, models, and he includes direct Linq associations. As a bonus, he tackles how to auto-wire up all of your custom controllers with the repository they expect. Yes, it was written for a version of ASP.NET MVC that is 4 versions old (before the release), but the concepts stick with you. The information applies to all MVC (or any DDD project w/Linq rather) projects.
In short, the book introduces DDD concepts to inject a repository into your custom Controller.
public class ContentController : Controller
{
IContentRepository _repository;
public ContentController(IContentRepository repo)
{
_repository = repo;
}
public ActionResult Add(Content c)
{
_repository.Add(c);
_repository.SubmitChanges();
return View(c);
}
}
Notice the dependency injection concept in use here by forcing a repository into the constructor. Now, you could just wire it up yourself without the constructor. But, when you get into a large project (50+ repositories), it helps to use an inversion of control container that facilitates the creation of these objects for you, like Castle Windsor (again, all in that 100 page book above).
For your use-case, you'd simple access the User object from a user repository and then let the rest follow-through.
The book goes into how to model, say your User and Product entities, using LinqToSql with EntityRef and EntitySet, so all of your associations are honored. Which is too much code to write here. Basically, link a User() object to your Product() object by the UserID and decorate each of those entities with Linq attributes:
[Table(Name="ut_Content")]
public class Content : EntityBase
{
[Column(IsPrimaryKey = true
, IsDbGenerated = true
, AutoSync = AutoSync.OnInsert)]
public int ContentID { get; set; }
[Column(CanBeNull = false)]
public string ContentKey { get; set; }
[Column(CanBeNull = false)]
public string Title { get; set; }
[Column]
public string Description { get; set; }
[Column]
public string Body { get; set; }
[Column]
public string BodyFormatted { get; set; }
}
He tackles the different LinqToSql use cases, and shows why "Create your model first, do not use Schema-Generated Linq objects" works here.
And, a repository example I guess for good measure:
public class ContentRepository : IContentRepository
{
private Table<Content> _contents;
public ContentRepository(string connectionString)
{
DataContext db = new DataContext(connectionString);
_contents = db.GetTable<Content>();
}
public void Add(Content entity)
{
_contents.InsertOnSubmit(entity);
}
}
Remember, one of the best things about Linq is its deferred execution feature. You are not operating on an entire Linq Table here. Your query gets deferred so you can filter your IQueryable with Where and Joins and order bys. Only when you request the .ToList() or alike methods is when the query is executed.
Use a DDD Service
Finally, to address your original issue of "getting a user, then adding the product", the DDD (Domain Driven Design) playbook has the perfect solution: Services.
You'd create a UserProductService() class in your Domain that does exactly what you what you did above. But it has the advantage of abstracting the business logic out into your Domain, so other locations can access and use the same business rule.
public class UserProductService
{
private IProductRepository _productRepository;
private IUserRepository _userRepository;
public UserProductService()
{
// this is where CastleWindsor can really help
_productRepository = new ProductRepository();
_userRepository = new UserRepository();
}
public void AddProduct(int userID, Product product)
{
User user = userRepository.GetUserByID(userID);
// your logic above, abstracted into a service
if (user.IsValid && product.IsValid)
{
product.Owner = user.UserID;
_productRepository.AddProduct(product);
}
}
}
The DDD rule for services is: When you have two entities, but you need to perform a common action on both, use a service to act upon both entities.