views:

290

answers:

2

How can I share the auto-generated entity data model (generated object classes) amongst all layers of my C# web app whilst only granting query access in the data layer? This uses the typical 3 layer approach: data, business, presentation.

My data layer returns an IEnumerable<T> to my business layer, but I cannot return type T to the presentation layer because I do not want the presentation layer to know of the existence of the data layer - which is where the entity framework auto-generated my classes.

It was recommended to have a seperate layer with just the data model, but I'm unsure how to seperate the data model from the query functionality the entity framework provides.

+1  A: 

You could create DTOs from your data entities and pass your DTOs to the rpesentation layer.

Ben Robinson
Right, but since the DTO's already exist (correct me if I'm wrong) in the data layer, why can't I just reuse that... or is there an easy way to auto generate DTO's from the data entities? I was hoping there was a way to automagically do this.
Chris Klepeis
@Chris: EF 2 (in .NET 4) will "automagically" create "DTO entities" using a T4 template. Those entities won't have any dependencies to the EF. You can then move those to a separate assembly to share across your 3 layers and not have any circular references.
Nelson
If you at least have Visual Studio 2010, you can stick to EF 1 and use a custom T4 template to generate the DTOs for you. Otherwise I don't think it's possible without a 3rd party tool.
Nelson
@Nelson: Thanks, looks like I'll be reading up on t4 http://blogs.msdn.com/efdesign/archive/2009/01/22/customizing-entity-classes-with-t4.aspx Right now I'm on vs 2010 express
Chris Klepeis
You may want to make sure the express edition includes that functionality.
Nelson
I created my DataModel.tt (ADO.Net EntityObject Generator), copied it to another project, but that project throw errors because it references things in my DataLayer project.
Chris Klepeis
Another thing to keep in mind... If you use the POCO proxies for lazy-loading, you are indirectly granting all layers "query access". Basically, if the data hasn't been loaded yet and you try to access the property, it will run a query in the background without going through your DAL methods.
Nelson
@Chris: Open the tt file and change the path to your edmx model. It should be something like "../DalProject/Model.edmx". It should run fine afterwards.
Nelson
@Chris: Oh, and inside the tt file you'll probably want to change the namespace to match your project (e.g. Project.Entities) The T4 template is basically a design-time script that will read files in your project and generate files with the contents you specify. It reminds me a lot of inline ASP/PHP. :)
Nelson
@Nelson - I definately don't want to allow lazy-loading.
Chris Klepeis
@Chris: If you want any of the "advanced" features such as lazy loading and change tracking (without doing a full object comparison), you'll have to keep proxies on. You can turn off lazy loading individually in the context file's constructor.
Nelson
+3  A: 

If you use POCO entities (.NET 4+), then this is easy (or at least easier). Is that a possibility?

You can create DTOs as Ben said, but then you're basically dumbing down and duplicating each of the entities. EF2 will create the "dumbed down" entities and dynamically add change tracking, lazy loading, etc. if you wish.

Otherwise the answer is you can't. If the entities depend on the Entity Framework, then you can't use them throughout your application without dragging that dependency along. In that case you have to use DTOs. Here's a 3rd party option for EF 1 or EF 2 without POCO entities. http://automapper.codeplex.com/

Edit: Here are some useful links to learn more about all this:

  1. General MS guidelines: http://msdn.microsoft.com/en-us/library/bb738470.aspx
  2. POCO templates: http://blogs.msdn.com/adonet/pages/walkthrough-poco-template-for-the-entity-framework.aspx
  3. POCO templates, including how to move to separate project: http://blogs.msdn.com/adonet/pages/feature-ctp-walkthrough-poco-templates-for-the-entity-framework.aspx
  4. POCO proxies: http://blogs.msdn.com/adonet/archive/2009/12/22/poco-proxies-part-1.aspx
  5. How to split up model: http://blogs.msdn.com/adonet/archive/2008/11/25/working-with-large-models-in-entity-framework-part-2.aspx
  6. Employee Tracker sample application (layers, unit tests, mocking, repository, etc.): http://code.msdn.microsoft.com/ef4/Release/ProjectReleases.aspx?ReleaseId=4279
Nelson
@Nelson - When I try to call a function returning IEnumerable from the DAL (calling from the BAL).. I receive an incorrect argument exception in the DataModel on the constructor public MyEntities() : base(ConnectionString, ContainerName)
Chris Klepeis
Ok, so MyEntities() is your ObjectContext. You can have more than one instance of the object context, but you have to create it yourself. You should be able to do something like `var context = new MyEntities(); var employees = context.Employees.ToList();` Are you saying it gives an error at `new MyEntities()`? Which argument is it complaining about?
Nelson
@Chris: I think I know what your problem is. The model will create a connection string. If you have it in a separate class library, it will add it to the app.config, but DLL app.configs never get used. You have to copy/paste it to your main application's app/web.config
Nelson
Note the name in `<add name="connectionName" .../>`. connectionName has to match in the context file generated by the *.Context.tt template (you can search for `public const string ConnectionString = "name=`)
Nelson
Hrmmm.. the context is only created and destroyed in each of my DAL functions. I think when I try to access the returned content, it craps out because the context no longer exists.
Chris Klepeis
You're right again, I had to add the connection string to my web.config in each project referencing my Entities project.
Chris Klepeis
@Chris: There are many ways on handling the life of the context. One of the most common is Unit of Work (http://rlacovara.blogspot.com/2009/04/entity-framework-patterns-unit-of-work.html), but not everyone agrees (http://stackoverflow.com/questions/2818029/is-the-entity-framework-4-unit-of-work-pattern-the-way-to-go-for-generic-reposi). It's up to you how you want to handle that.
Nelson
There are also varying opinions on how much to abstract into layers. Do you fully abstract it with a repository pattern, do you allow IQueryable to leak through (thus tying you to LINQ, but not necessarily EF), do you let most of it leak through to the business layer since it makes it easier, etc.? From what I've been reading, most people are ok with leaking some pieces on small projects, but mostly no on large ones.
Nelson
@Chris: Another thing to keep in mind is that your POCO objects can be detached/detached from contexts. In other words, you can manually create a POCO object, use it as a DTO anywhere you want. Then, you can attach it to the context and it should be automatically added to the object graph and allow saving changes, etc. The POCO objects are only "special" if you are using proxies and they're attached to the context.
Nelson
@Chris: "I think when I try to access the returned content, it craps out because the context no longer exists." Sounds right. If you are using context.Entity then the context has to exist. You can handle that at least two ways: Detach it from the context before you return it or use the Unit of Work pattern in your BLL. One interesting way I have seen this handled: http://dotnetslackers.com/articles/PrintArticle.aspx?ArticleId=302 You would do something like `using (new UnitOfWork()) { UnitOfWork.CurrentContext.Entity... }` When the class falls out of scope the context is "disposed".
Nelson
@Nelson: to answer your question above.. I return IEnumerable, and do not plan on allowing IQueryable to leak through. I have to do some research though because it appears as if query execution is available via my entity objects navigation properties
Chris Klepeis
@Chris: You can still use the UnitOfWork pattern with just IEnumerable. It's mostly meant for a sort of "in-memory transaction" for any changes. If lazy loading is not enabled, I believe your navigation properties should not be executing queries.
Nelson