views:

526

answers:

7

Say I have a 3-tier architecture (UI, Business, and Data). Usually, I create a 4th project called "Model" or "Common" to keep my data access objects and each of the other projects would then use this project.

Now I'm working on a project where some of my data access objects have methods like Save() etc that need access to the Data project. So, I would have a circular reference if I attempted to use the Model/Common project in the Data project.

In this scenario, where is the best place to keep the data access objects? I could keep it within the Data project itself, but then my UI project which needs to know about the data access objects, would need to access the Data layer, which is not good.

+1  A: 

The Data layer should store information in terms of rows and columns (maybe using typed DataSets, if you like), if you are using a relational backend. No "business objects".

The Business layer should use your "business objects". It can have a reference to the BusinessObjects project.

In summary:

  • UI has references to Business and BusinessObjects
  • Business has references to BusinessObjects and Data

Hope this helps.

CesarGon
A: 

I would suggest creating and interface of what you want in the model project, and implementing that definition in the data layer. That way all three (four?) projects can use that definition, without knowing how it's implemented.

C. Ross
A: 

In my opinion, only the business layer should have knowledge of the data access objects. It should use them for data operations while applying its own business rules and logic, then return dumb objects (e.g. data transfer objects) to the UI layer above.

You could use some thing like AutoMapper to automatically map between you data and business objects.

cxfx
+5  A: 

I don't think you have your n-tier quite right. It sounds like you're building more 2-tier systems.

In a real 3-tier project, only your data tier is allowed to talk to the database. You have that with your "Model" or "Common" projects. Those projects are your data tier. But where you veer off is that only the business tier should be allowed to talk to them. Your presentation code should not be allowed to talk to the data tier projects at all.

n-Tier comes in when you have more than 3 "tiers", but the same principle appliers: each tier only knows how to use (and only needs a reference to) the one below it, and then provides an api for the tier above it. In my own projects, I take your typical presentation, business, and data tiers and provide a 4th "translation" tier between business and data. This way the data tier can return generic types like dataset, datatable, and datarow, and the business tier only has to work in terms of strongly-typed business objects. The translation tier only converts between the generic data objects and strongly-typed objects. This way a change to one of the traditional tiers is less likely to require a change in another.

Joel Coehoorn
Good points, but you need a "not" in the last sentence of the second paragraph.
Jason
fixed, thanks for pointing it out.
Joel Coehoorn
Actually my models never talk to the database. Only now I am on a project that I took over in which the custom objects have data access methods--hence my question.BTW, why do you need a translation tier? Can you not have your business tier directly use the generic objects?
Prabhu
A: 

It really depend on the pattern, if you are using MVC (Front Controller Pattern), the model Is the domain-specific representation of the data upon which the application operates (generally an ORM help with this) we use a DATA project for this classes.

Models are not data access objects, so the data access becomes in form of repositories in a Different project. Services for Business Rules and finally the Web project. In this approach the Data.dll is referenced in all projects. The Model is like omnipresent.

DATA(Domain Model) -> REPOSITORY(Data Access) -> SERVICE(Business Rules) -> WEB
Omar
+1  A: 

I have a BusinessObjects project, server side storing the mappings (ORM) and a corresponding DataAccess service exposing CRUD operations on them (and others also like GetAll) etc.

Aggelos Mpimpoudis
+2  A: 

This is what I have in my project.

1.) Application.Infrastructure

  • Base classes for all businessobjects, busines object collection, data-access classes and my custom attributes and utilities as extension methods, Generic validation framework. This determines overall behavior organization of my final .net application.

2.) Application.DataModel

  • Typed Dataset for the Database.
  • TableAdapters extended to incorporate Transactions and other features I may need.

3.) Application.DataAccess

  • Data access classes.
  • Actual place where Database actions are queried using underlying Typed Dataset.

4.) Application.DomainObjects

  • Business objects and Business object collections.
  • Enums.

5.) Application.BusinessLayer

  • Provides manager classes accessible from Presentation layer.
  • HttpHandlers.
  • My own Page base class.
  • More things go here..

6.) Application.WebClient or Application.WindowsClient

  • My presentation layer
  • Takes references from Application.BusinessLayer and Application.BusinessObjects.

Application.BusinessObjects are used across the application and they travel across all layers whenever neeeded [except Application.DataModel and Application.Infrastructure]

All my queries are defined only Application.DataModel.

Application.DataAccess returns or takes Business objects as part of any data-access operation. Business objects are created with the help of reflection attributes. Each business object is marked with an attribute mapping to target table in database and properties within the business object are marked with attributes mapping to target coloumn in respective data-base table.

My validation framework lets me validate each field with the help of designated ValidationAttribute.

My framrwork heavily uses Attributes to automate most of the tedious tasks like mapping and validation. I can also new feature as new aspect in the framework.

A sample business object would look like this in my application.

User.cs

[TableMapping("Users")]
public class User : EntityBase
{
    #region Constructor(s)
    public AppUser()
    {
        BookCollection = new BookCollection();
    }
    #endregion

    #region Properties

    #region Default Properties - Direct Field Mapping using DataFieldMappingAttribute

    private System.Int32 _UserId;

    private System.String _FirstName;
    private System.String _LastName;
    private System.String _UserName;
    private System.Boolean _IsActive;

    [DataFieldMapping("UserID")]
    [DataObjectFieldAttribute(true, true, false)]
    [NotNullOrEmpty(Message = "UserID From Users Table Is Required.")]
    public override int Id
    {
        get
        {
            return _UserId;
        }
        set
        {
            _UserId = value;
        }
    }

    [DataFieldMapping("UserName")]
    [Searchable]
    [NotNullOrEmpty(Message = "Username Is Required.")]
    public string UserName
    {
        get
        {
            return _UserName;
        }
        set
        {
            _UserName = value;
        }
    }

    [DataFieldMapping("FirstName")]
    [Searchable]
    public string FirstName
    {
        get
        {
            return _FirstName;
        }
        set
        {
            _FirstName = value;
        }
    }

    [DataFieldMapping("LastName")]
    [Searchable]
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            _LastName = value;
        }
    }

    [DataFieldMapping("IsActive")]
    public bool IsActive
    {
        get
        {
            return _IsActive;
        }
        set
        {
            _IsActive = value;
        }
    }

    #region One-To-Many Mappings
    public BookCollection Books { get; set; }

    #endregion

    #region Derived Properties
    public string FullName { get { return this.FirstName + " " + this.LastName; } }

    #endregion

    #endregion

    public override bool Validate()
    {
        bool baseValid = base.Validate();
        bool localValid = Books.Validate();
        return baseValid && localValid;
    }
}

BookCollection.cs

/// <summary>
/// The BookCollection class is designed to work with lists of instances of Book.
/// </summary>
public class BookCollection : EntityCollectionBase<Book>
{
    /// <summary>
    /// Initializes a new instance of the BookCollection class.
    /// </summary>
    public BookCollection()
    {
    }

    /// <summary>
    /// Initializes a new instance of the BookCollection class.
    /// </summary>
    public BookCollection (IList<Book> initialList)
        : base(initialList)
    {
    }
}
this. __curious_geek