What is the "best practice" for designing ascx user controls regarding separating the UI from the Data Access? Should my user control use 3 tier archetecture as in my projects or can I do the data acess from within the user control?
You should never access the database from a user control. You should create a class for accessing the database, and everything within your application should use that class. Basically, the methods in the class would be wrappers around your stored procedure calls, but all that the application (and therefore user controls) sees is a method with the needed parameters. No knowledge of the database from the application's point of view. That allows you to make changes to the database without changing your application.
This link might help you:
http://www.simple-talk.com/dotnet/.net-framework/.net-application-architecture-the-data-access-layer/
Now matter how simple or complex a project, at the very least, all should be separated into a Presentation Layer, Business Layer, and Data Layer. At any given time any one of the three could change without affecting the others.
A user control is part of the presentation layer, it should supply data and user actions to the business layer which in turn interprets the data and those actions to make decisions. If necessary the business layer would call the Data Layer. The Data Layer in turn would handle all communication with the database/source files.
It's not that hard to separate the three and keep them separate.
I would definitely suggest to access data from some kind of business layer. UI never should access databases directly.
What if your access rules changed?
What if your storage changed?
Can you ensure, that every UI control is able to enforce business rules?
etc.
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)
{
}
}
You need at a minimum, a 2 layer solution: data, then everything else. For a more complex project, you need to take that everything else and abstract it out into presentation, logic, data. Data can also be separated into data access and data model layers.
Related
I have a project consisting of 2 parts:
ASP.NET API using Entity Framework
.NET MAUI Client App
I use DTOs for comunication from/to the API in order not to expose other properties of my entities. Thanks to this approach I was able to separate Entity data and data that are sent from the API.
At first I used these DTOs also in the MAUI UI. But after some time I started to notice that they contains UI-specific properties, attributes or methods that have no purpose for the API itself, so they are redundant in requests.
EXAMPLE:
1 - API will receive request from MAUI to get exercise based on it's name
2- ExerciseService returns: ExerciseEntity and ExerciseController use AutoMapper to Map ExerciseEntity -> ExerciseDto ommiting ExerciseId field (only admin can see this info in the DB) and returning it in the API response
3 - MAUI receives from the API ExerciseDto. But in the client side it also want to know if data from ExerciseDto are collapsed in the UI. So because of that I add IsCollapsed property into the ExerciseDto. But now this is a redundant property for the API, because I dont want to persist this information in the database.
QUESTIONS:
Should I map these DTOs to new objects on the client side ?
Or how to approach this problem ?
Is there an easier way how to achieve the separation ?
Because having another mapping layer will add extra complexity and a lot of duplicate properties between DTOs and those new client objects.
Normally if you use clean architecture approach your DTOs shoud contain no attributes and other specific data relevant just for some of your projects, to be freely usable by other projects in a form of dependency.
Then you'd have different approaches to consume DTOs in a xamarin/maui application, for example:
APPROACH 1.
Mapping (of course) into a class that is suitable for UI. Here you have some options, use manual mapping, write your own code that uses reflection or use some third party lib using same reflection. Personally using all of them, and when speaking of third party libs Mapster has shown very good to me for api and mobile clients.
APPROACH 2.
Subclass DTO. The basic idea is to deserialize dto into the derived class, then call Init(); if needed. All properties that you manually implemented as new with OnPropertyChanged will update bindings after being popupated by deserializer/mapper and you alse have a backup plan to call RaiseProperties(); for all of the props, even thoses who do not have OnPropertyChanged in place so they can update bindings if any.
Example:
our Api DTO
public class SomeDeviceDTO
{
public int Id { get; set; }
public int Port { get; set; }
public string Name { get; set; }
}
Our derived class for usage in mobile client:
public class SomeDevice : SomeDeviceDTO, IFromDto
{
// we want to be able to change this Name property in run-time and to
// reflect changes so we make it bindable (other props will remain without
// OnPropertyChanged BUT we can always update all bindings in code if needed
// using RaiseProperties();):
private string _name;
public new string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
// ADD any properties you need for UI
// ...
#region IFromDto
public void Init()
{
//put any code you'd want to exec after dto's been imported
// for example to fill any new prop with data derived from what you received
}
public void RaiseProperties()
{
var props = this.GetType().GetProperties();
foreach (var property in props)
{
if (property.CanRead)
{
OnPropertyChanged(property.Name);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public interface IFromDto : INotifyPropertyChanged
{
//
// Summary:
// Can initialize model after it's being loaded from dto
void Init();
//
// Summary:
// Notify all properties were updated
void RaiseProperties();
}
We can get it like: var device = JsonConvert.DeserializeObject<SomeDevice>(jsonOfSomeDeviceDTO);
We then can call Init(); if needed..
Feel free to edit this answer to add more approaches..
In database I have table: Notes and table Comments. In my solution I have 3 projects: DAL, BLL and Web.
I need to show a user notes with comments which aren't set as spam so I have created in DAL project that class:
public class NotesWithComments
{
public Notes Note { get; set; }
public IEnumerable<Comments> Comments { get; set; }
}
I use above class in each project: DAL, BLL and Web. Is this class Data Transfer Object, Business Object, Domain Object or what?
In a repository class I have that query:
public class NotesRepository
{
DatabaseContext context;
public NotesRepository(DatabaseContext context)
{
this.context = context;
}
public IQueryable<NotesWithComments> GetNotesWithNoSpamComments()
{
IQueryable<NotesWithComments> notesWithNoSpamComments = context.Notes.Include(x => x.Comments).OrderByDescending(x => x.CreateDate)
.Select(x => new NotesWithComments
{
Note = x,
Comments = x.Comments.Where(y => y.IsSpam == false).OrderBy(y => y.CreateDate)
});
return notesWithNoSpamComments;
}
}
In BLL project I use the method from the repository class:
public class NotesService
{
private readonly IUnitOfWork _unitOfWork;
public NotesService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public IEnumerable<NotesWithComments> GetNotesWithComments()
{
IQueryable<NotesWithComments> result = _unitOfWork.NotesRepository.GetNotesWithNoSpamComments();
return result;
}
}
And in Web project I use the method form the service class:
public ActionResult Index()
{
List<NotesWithComments> result = _notesService.GetNotesWithComments();
return View(result);
}
Since it neither exposes any behavior (properties or getters/setters don't qualify) nor encapsulates its structure (again, properties or getters/setters that do nothing but expose the underlying data don't qualify) it is no object at all.
No matter if the language you use calls it an object or not. It is just a data structure (which is perfectly fine if you only want to move data from one place, like a database, to another, like a UI.)
Or, to quote Dan North:
Data Transfer Object is an oxymoron
Is this class Data Transfer Object, Business Object, Domain Object or
what?
A DTO is typically a class that is mainly used for transferring data between layers or some type of boundaries..typically just an object with no behavior.
I have always referred to Domain Objects as something that maps directly to a database table. So In your example, your domain models would be Notes, and Comments.
I would consider your NotesWithComments object a dto, or possibly a view model (as you're using it as your asp.net mvc model for the view).
The practice I would normally use here is use your NotesWithComments as a dto (transfer data, no behavior, easily serializable, very clean ect), and create another class to act as your view model.
In the beginning these classes would probably be very similar, possibly the same..but if you make changes over time, or your view needs to display different things, you would just change your view model, and populate it from other dtos, or tranform your data however you need to. You could also then get rid of the properties on your view model that your view doesn't need.. (unless your view magically maps directly to every property on your current dto). It's a bit more work up front but if you're working on a big long living project I think you'd be happy you did it later on.
So you would populate your domain models using EF in your data layer, you would then use your dto and transfer that data to the Biz layer, do w/e you need there, then use your dto (could be the same one) to transfer your data to your presentation layer (mvc), and populate your view model from the dtos you receive.
Anyway that's my take on it.
Unlike normal, I have code that actually works, but I'm wondering if it's the only (or best approach).
The basic Idea is I have an existing application that's handmade data layer is being ported to Entity Framework. As a compromise to minimize code changes, I'm working with existing methods, which tend to take a more disconnected approach. For example I have a lot of things like this:
UpdateNote(int noteId, string note)
I seem to have a method that works for this type of update without requiring a re-fetch:
var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;
var note = new Model.Note{ Id = noteId, Note = ""};
context.Notes.Attach(note);
note.Note = "Some Note";
context.SaveChanges();
It's a little ugly (though concise enough), so I would like to know if there is there a better approach to use with EF? Any downsides to this method, other than loosing built-in validation?
This is a pattern that will be used all over my app.
The following extension method for DbContext is an approach which would avoid to initialize your entities with some values different to the values you want to change it to.
public static class EFExtensions
{
public static void MarkAsModified(this DbContext context, object entity,
params string[] properties)
{
foreach (var property in properties)
context.Entry(entity).Property(property).IsModified = true;
}
}
You could then use it this way:
var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;
var note = new Model.Note { Id = noteId }; // only key properties required to set
note.Note = "Some Note";
note.SomeOtherProperty = 1234;
note.AndAnotherProperty = "XYZ";
context.Notes.Attach(note);
context.MarkAsModified(note, "Note", "SomeOtherProperty" , "AndAnotherProperty");
context.SaveChanges();
Note: This only works for scalar properties, not navigation properties.
Besides validation I could imagine that this approach is problematic for a proper concurrency checking.
Edit
According to #Adam Tuliper's comment below concurrency is likely not a problem because the concurrency check is skipped when an entity is attached manually to the context (without reading it from the database) and marked as modified to send an UPDATE command to the database. It just overwrites the lastest version in the DB. Thanks to Adam for pointing this out!
See the following code I use to easily attach a disconnected object back to the graph, assuming we're now going to save it.
public static class EntityFrameworkExtensions
{
/// <summary>
/// This class allows you to attach an entity.
/// For instance, a controller method Edit(Customer customer)
/// using ctx.AttachAsModified(customer);
/// ctx.SaveChanges();
/// allows you to easily reattach this item for udpating.
/// Credit goes to: http://geekswithblogs.net/michelotti/archive/2009/11/27/attaching-modified-entities-in-ef-4.aspx
/// </summary>
public static void AttachAsModified<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
/// <summary>
/// This marks an item for deletion, but does not currently mark child objects (relationships).
/// For those cases you must query the object, include the relationships, and then delete.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectSet"></param>
/// <param name="entity"></param>
public static void AttachAsDeleted<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
}
public static void AttachAllAsModified<T>(this ObjectSet<T> objectSet, IEnumerable<T> entities) where T : class
{
foreach (var item in entities)
{
objectSet.Attach(item);
objectSet.Context.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
}
}
}
I have an existing multi-tenant ASP.NET application where all users authenticate against a single SQL Server database. This database also contains several other settings type data that is used within the application. Each client after authentication, utilizes their own SQL Server database for data storage, for isolation purposes. Essentially all of the client database are identical and reside on the same server, but reside on one or more servers as well.
The application is currently written in asp.net 2.5 framework and utilizes the Microsoft Practices Enterprise Library for DAL, and we are looking to migrate to 4.0 and implement NHibernate to replace the MPEL.
I have implemented a solution already using NHibernate and the 4.0 framework, so I am familiar with the concepts. I found the resources for my current session manager here as a matter of fact. But that application only had a single database, so not much too it. The implementation is essentially what you see here:
http://www.lostechies.com/blogs/nelson_montalvo/archive/2007/03/30/simple-nhibernate-example-part-4-session-management.aspx
The other solutions that I have seen suggest multiple config entries and/or files to manage this, but that is not desirable, since we may add new clients frequently and all of the connection information is already maintained in the authentication database.
Does anyone have any suggestions on how I might be able to pass in the client's connection string to a session manager?
The following is my current session manager class, which is based on the article mentioned above.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Web;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Cache;
using singlepoint.timeclock.domain;
namespace singlepoint.timeclock.repositories
{
/// <summary>
/// Handles creation and management of sessions and transactions. It is a singleton because
/// building the initial session factory is very expensive. Inspiration for this class came
/// from Chapter 8 of Hibernate in Action by Bauer and King. Although it is a sealed singleton
/// you can use TypeMock (http://www.typemock.com) for more flexible testing.
/// </summary>
public sealed class nHibernateSessionManager
{
private ISessionFactory idadSessionFactory;
private ISessionFactory clientSessionFactory;
private string _client;
#region Thread-safe, lazy Singleton
// lazy initialisation, therefore initialised to null
private static nHibernateSessionManager instance = null;
/// <summary>
/// This is a thread-safe, lazy singleton. See http://www.yoda.arachsys.com/csharp/singleton.html
/// for more details about its implementation.
/// </summary>
public static nHibernateSessionManager Instance
{
get { return GetInstance(); }
}
public static nHibernateSessionManager GetInstance()
{
// lazy init.
if (instance == null)
instance = new nHibernateSessionManager();
return instance;
} // GetInstance
/// <summary>
/// Initializes the NHibernate session factory upon instantiation.
/// </summary>
private nHibernateSessionManager()
{
InitSessionFactory();
}
/// <summary>
/// Initializes the NHibernate session factory upon instantiation.
/// </summary>
private nHibernateSessionManager(string client)
{
InitSessionFactory();
InitClientSessionFactory(client);
}
/// <summary>
/// Assists with ensuring thread-safe, lazy singleton
/// </summary>
private class Nested
{
static Nested()
{
}
internal static readonly nHibernateSessionManager nHibernatenHibernateSessionManager = new nHibernateSessionManager();
}
#endregion
private void InitSessionFactory()
{
var configuration = new Configuration();
configuration.Configure(System.Configuration.ConfigurationManager.AppSettings["IDAD_HBM"]);
configuration.AddAssembly(typeof(enterprise).Assembly);
idadSessionFactory = configuration.BuildSessionFactory();
}
private void InitClientSessionFactory(string client)
{
var configuration = new Configuration();
configuration.Configure(System.Configuration.ConfigurationManager.AppSettings["Client_IDAD_HBM"]);
configuration.SetProperty("connection.connection_string", client);
configuration.AddAssembly(typeof(enterprise).Assembly);
clientSessionFactory = configuration.BuildSessionFactory();
}
/// <summary>
/// Allows you to register an interceptor on a new session. This may not be called if there is already
/// an open session attached to the HttpContext. If you have an interceptor to be used, modify
/// the HttpModule to call this before calling BeginTransaction().
/// </summary>
public void RegisterInterceptor(IInterceptor interceptor)
{
ISession session = ThreadSession;
if (session != null && session.IsOpen)
{
throw new CacheException("You cannot register an interceptor once a session has already been opened");
}
GetSession(interceptor);
}
public ISession GetSession()
{
return GetSession(null);
}
/// <summary>
/// Gets a session with or without an interceptor. This method is not called directly; instead,
/// it gets invoked from other public methods.
/// </summary>
private ISession GetSession(IInterceptor interceptor)
{
ISession session = ThreadSession;
if (session == null)
{
if (interceptor != null)
{
session = idadSessionFactory.OpenSession(interceptor);
}
else
{
session = idadSessionFactory.OpenSession();
}
ThreadSession = session;
}
return session;
}
public void CloseSession()
{
ISession session = ThreadSession;
ThreadSession = null;
if (session != null && session.IsOpen)
{
session.Close();
}
}
public void BeginTransaction()
{
ITransaction transaction = ThreadTransaction;
if (transaction == null)
{
transaction = GetSession().BeginTransaction();
ThreadTransaction = transaction;
}
}
public void CommitTransaction()
{
ITransaction transaction = ThreadTransaction;
try
{
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Commit();
ThreadTransaction = null;
}
}
catch (HibernateException ex)
{
RollbackTransaction();
throw ex;
}
}
public void RollbackTransaction()
{
ITransaction transaction = ThreadTransaction;
try
{
ThreadTransaction = null;
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Rollback();
}
}
catch (HibernateException ex)
{
throw ex;
}
finally
{
CloseSession();
}
}
/// <summary>
/// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms
/// specific <see cref="CallContext" />. Discussion concerning this found at
/// http://forum.springframework.net/showthread.php?t=572.
/// </summary>
private ITransaction ThreadTransaction
{
get
{
if (IsInWebContext())
{
return (ITransaction)HttpContext.Current.Items[TRANSACTION_KEY];
}
else
{
return (ITransaction)CallContext.GetData(TRANSACTION_KEY);
}
}
set
{
if (IsInWebContext())
{
HttpContext.Current.Items[TRANSACTION_KEY] = value;
}
else
{
CallContext.SetData(TRANSACTION_KEY, value);
}
}
}
/// <summary>
/// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms
/// specific <see cref="CallContext" />. Discussion concerning this found at
/// http://forum.springframework.net/showthread.php?t=572.
/// </summary>
private ISession ThreadSession
{
get
{
if (IsInWebContext())
{
return (ISession)HttpContext.Current.Items[SESSION_KEY];
}
else
{
return (ISession)CallContext.GetData(SESSION_KEY);
}
}
set
{
if (IsInWebContext())
{
HttpContext.Current.Items[SESSION_KEY] = value;
}
else
{
CallContext.SetData(SESSION_KEY, value);
}
}
}
private static bool IsInWebContext()
{
return HttpContext.Current != null;
}
private const string TRANSACTION_KEY = "CONTEXT_TRANSACTION";
private const string SESSION_KEY = "CONTEXT_SESSION";
[Obsolete("only until we can fix the session issue globally")]
internal ISession OpenSession()
{
return idadSessionFactory.OpenSession();
}
}
}
This is being called from a repository class like so:
public string getByName(string name)
{
return getByName(nHibernateSessionManager.Instance.GetSession(), name);
}
What I would really like to be able to do is the following:
public string getByName(string name, string clientConnectionString)
{
return getByName(nHibernateSessionManager.Instance.GetSession(clientConnectionString), name);
}
But I am having trouble modifying my existing session manager to accomodate this.
You appear to be asking to swap a connection for a given session. Or rather that is certainly what the code you have written is asking - "return a session identified by the name parameter, and it should also now use the connection string provided by this method."
That is not possible. NHibernate builds a session (and actually really a session factory) per connection and once built the factory and session are immutable. You cannot change connections for an existing session.
I got the impression that your application involves mostly in initial connection string that is the moving target, but after that your "real" session is on a single database. If that is the case, NHibernate can easily do this. If that is not the case, well, some things NHibernate is just not that well suited for. Maybe understanding a little more about the basis NHibernate operates on is helpful either way?
One of my genuine criticisms of NHibernate is that you have a somewhat arcane use of terminology and the well known unhelpful nature of it's exception messages. These coupled with the fact that what it is doing is in reality mechanically complicated tends to really obscure that there is a relatively simple and technically sound underlying model.
In this case, if you think about it this business of an immutable session makes a lot of sense. NHibernate connects to a database, but it also maintains objects in the session so they can be persisted back to that database at a later time. NHibernate does not support changing connections per session because there may already be other objects in the session and if you change connections their persistence is no longer assured.
Now, what you can do is create a factory/session per database for multiple databases and access them in one application, but objects still belong to their own session. You can even move objects to a new session with a different connection. In this case you have what would logically be a "replication" scenario. NHibernate supports this but you have to do most of the work. This also makes sense - they really cannot give you that as stable out of the box functionality, you have to manage a process like that on your own.
You can also build code to do exactly what you are asking. But think about what that is. Make a session, not per database, but only for this specific instance of this specific repository. I am thinking that is most likely not really what you want. But that is exactly what the semantics of your request are saying to do. Your existing class, On the other hand, was built on different semantics which are more typically what people want - "Build a session for this particular connection definition, i.e this database."
A real need to inject a connection string at the repository level implies that now not only is the database a moving target, but at the actual table level the target also moves. If that is the case, NHibernate is possibly not a good option. If that is not the case, you may be trying to mix programming paradigms. NHiberate imposes a few what I would call "assumptions" rather than any sort of real "limitations" and in return you don't have to write a bunch of code that would allow you a finer grain of control because often you really don't need that additional control.
Sorry if this is no longer a direct answer to your question, hopefully it is helpful somehow.
Original Answer:
Sure, since the connection info is in the authentication database this is easy.
1) Configure NHibernate in the "usual" fashion and point the config at the authentication database. Get the db connection for the user, and then close that session and session factory. You are done with that one now.
2) Create a new session etc this time in code instead of a config file.
class MyNewSession
{
private ISession _session;
private ISessionFactory _factory;
public void InitializeSession()
{
NHibernate.Cfg.Configuration config = new NHibernate.Cfg.Configuration();
config.Properties.Clear();
IDictionary props = new Hashtable();
// Configure properties as needed, this is pretty minimal standard config here.
// Can read in properties from your own xml file or whatever.
// Just shown hardcoded here.
props["proxyfactory.factory_class"] = "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle";
props["connection.provider"] = "NHibernate.Connection.DriverConnectionProvider";
props["dialect"] = "NHibernate.Dialect.MsSql2000Dialect";
props["connection.driver_class"] = "NHibernate.Driver.SqlClientDriver";
props["connection.connection_string"] = "<YOUR CONNECTION STRING HERE>";
props["connection.isolation"] = "ReadCommitted";
foreach (DictionaryEntry de in props)
{
config.Properties.Add(de.Key.ToString(), de.Value.ToString());
}
// Everything from here on out is the standard NHibernate calls
// you already use.
// Load mappings etc, etc
// . . .
_factory = config.BuildSessionFactory();
_session = _factory.OpenSession();
}
}
I know this is old but if you have not found a solution I hope this will help,
I created a solution that uses multisessionfactory using unhaddins (I made alterations to suit my needs).
Basically the multisession factory creates session factories for each database and stores in Application object.
Depending on the client the call to getfactory("name of factory from factory config file") returns correct database to query on.
You will have to alter your management module to support this and all of your repositories to support the change in management. This may be impractical at first but you have to alter them anyway. Your calls from your repository can be something like this:
public string getByName(string name)
{
return getByName(nHibernateSessionManager.SessionFactoryManager.GetFactory(Session["session variable that holds client session factory name that was set on login"]).GetCurrentSession(), name);
}
or (creating a method in the sessionmanager to return session of a given factory) your code could be like this
public string getByName(string name)
{
return getByName(nHibernateSessionManager.GetSession(Session["session variable that holds client session factory name that was set on login"]), name);
}
I currently have an application which consists of:
User Interface (web page)
BLL (Manager & Domain Objects)
DAL (DataAccess class for each of my Domain Objects).
I use the following in the UI to search for a domain object.
protect sub Button1_Click()
{
IBook book = BookManager.GetBook(txtID.Text);
}
Here is my BLL
public class BookManager
{
public static IBook GetBook(string bookId)
{
return BookDB.GetBook(bookId);
}
}
public class Book : IBook
{
private int? _id
private string _name;
private string _genre;
public string Name
{
get { return _name; }
private set
{
if (string.IsNullOrEmpty(value))
throw new Exception("Invalid Name");
_name = value;
}
}
public string Genre
{
get { return _serial; }
private set
{
if (string.IsNullOrEmpty(value))
throw new Exception("Invalid Genre");
_genre = value;
}
}
// Other IBook Implementations
}
And finally here is my DAL
public class BookDB
{
public static IBook GetBook(int id)
{
// Get Book from database using sproc (not allowed to use any ORM)
// ?? Create IBook Item?
// return IBook
}
How would one create a IBook Object and return it to the Manager?
I'm thinking of returning a DataTable from BookDB to BookManager and having it create the Book Object and return it, but that doesn't seem right.
Is there another way to do this?
Edit:
I decided to seperate each layer into a project and ran into a circular dependency problem in the DAL layer when trying to add a reference to the BLL.
I can't access the Book Class or Interface or anything in BLL from DAL.
Should i just use ado.net objects here and have my manager create the actual object from the ado.net object?
Here's how its layed out
BLL.Managers - BookManager
BLL.Interfaces IBook
BLL.Domain - Book
DAL - BookDB.
Thanks!
You could create dummy Book objects that contain only data. Get, set properties and member values. This book, has 1 property for each field in the database, but doesn't validate anything.
You fill the object from the db, then send it to the BLL.
When you want to save the object, you also send it to the BLL.
Your classes in the BLL could wrap aroud those objects, if that makes sense. This way, it is easy to just send it back to the DAL.
Dummy Book:
public class DummyBook:IBook
{
private nullable<int> _id;
private string _name;
private string _genre;
public string Id
{
get {return _id;}
set {_id = value;}
}
public string Name
{
get {return _name;}
set {_name = value;}
}
public string Genre
{
get {return _genre;}
set {_genre= value;}
}
}
DAL Book:
public class DALBook
{
public static IBook:GetBook(int id)
{
DataTable dt;
DummyBook db = new DummyBook();
// Code to get datatable from database
// ...
//
db.Id = (int)dt.Rows[0]["id"];
db.Name = (string)dt.Rows[0]["name"];
db.Genre = (string)dt.Rows[0]["genre"];
return db;
}
public static void SaveBook(IBook book)
{
// Code to save the book in the database
// you can use the properties from the dummy book
// to send parameters to your stored proc.
}
}
BLL Book:
public class Book : IBook
{
private DummyBook _book;
public Book(int id)
{
_book = DALBook.GetBook(id);
}
public string Name
{
get {return _book.Name;}
set
{
if (string.IsNullOrEmpty(value))
{
throw new Exception("Invalid Name");
}
_book.Name = value;
}
}
// Code for other Properties ...
public void Save()
{
// Add validation if required
DALBook.Save(_book);
}
}
Edit1: The dummy classes should go in their own project(Model, just as stated in the comments is fine). The references would work as follow:
The DAL References the Model Project.
The BLL References the Model and the DAL.
The UI References the BLL.
BookDB should return the IBook instance. I like the repository pattern, which is all about mapping from the db to the domain.
The repository implementation returns instances of the domain objects. This shields the rest of the code from the particular persistence implementation, which can be affected by the technology (database type, web service, [insert something else]) and the format used to save the data.
I would probably use ExecuteReader to create an object in code from the database. The reason for this is that the datatable has more overhead than a reader, because it has more functionality (and was probably created by a reader). Since you aren't doing updates/deletes using the datatable, you don't need the overhead.
That being said, I would make a static helper method in the BookManager class.
internal static IBook BookFromReader(IDataReader reader)
{
Book B = new Book();
B.Prop = reader.GetString(0);
B.Rinse = reader.Repeat();
return B;
}
The reason for this is because the reason you have an interface is because you might want to change the implementation. You may eventuallu have INovel : IBook, IReference : IBook etc and then you'll want to have an abstract factory implementation in your data layer.
public static IBook GetBook(int id)
{
// SqlCommand Command = new Command("SQL or sproc", ValidConnection);
using(IDataReader DR = Command.ExecuteReader(id))
{
// checking omitted
switch(DR.GetInt32(1))
{
case 0:
return BookManager.BookFromReader(DR);
case 1:
return BookManager.NovelFromReader(DR);
etc
}
}
}
Another benefit of the DAL here is that you can cache lookups. You can have a Dictionary that holds books you've looked up, to reduce extra db calls on objects you've already returned. When an update takes place, you remove the cached entity... That's another post though.
If you're using multiple assemblies, interfaces and helper methods will need to reside in a neutral (non-dependent) assembly. Right now in the blog-o-sphere, there is movement towards less assemblies, which means less dependencies, etc.
Here is a link from a blog I read on this topic:
http://codebetter.com/blogs/patricksmacchia/archive/2008/12/08/advices-on-partitioning-code-through-net-assemblies.aspx
Ultimately, I think the answer is that the data layer returns an instance of your interface to the business layer.
Good luck :-)
In my opinion you should never let DAL access BLL. That is an unnecessarily dependency.
Putting the Book class into a new project (perhaps named DomainModel) will fix the circular reference. You could do something like this:
Project BLL reference DAL and DomainModel
Project DAL reference DomainModel
Project UI reference BLL and DomainModel
Project DomainModel reference nothing
The DataTable you want to return is database related, and for BLL, it shouldn't care about what database you are using and what the schema is.
You may use a DB-Object Mapper to map the dbtable to an object in DAL.
If you don't want to return a DataTable, you can pass in an IBook implementation from BookManager for the DAL to populate.
To follow the intended model. the Data Access Layer (DAL) is responsible for retrieving and sending data from and to the data source.
The DAL must not care about any of the business entities your BLL is using as its only job is to retrieve data and return it in a neutral object. It must be neutral for generic reuability, otherwise you might as well not separate the layers as you are defiting its purpose.
Your Business Logic Layer (BLL) must not care how the DAL achieves retrieveing or writing data.
To communicate between the BLL and the DAL you must use neutral objects.
Your BLL passes an object's properties as individual paramters to the methods in the DAL.
the parameters in the DAL are neutral using strings, int, bool, any other .NET objects which are neither specific to a version of the database you are communicating with nor are specific types only existing in your BLL.
The DAL will retrieve the data from where ever by what ever means and return a neutral data object to the caller. This for example could be a DataSet or DataTable or any other object NOT specific to a database type/version your are using. Hence DataSet and DataTable are objects within the System.Data namespace and not the System.Data.SQL,etc... namespace.
In essence:
- BLL passes neutral types to the DAL (e.g.: string, int, bool, long,float, etc..)
- DAL is responsible for converting those types to database specifci types if required before passing them on to the data source
DAL returns neutral data types to the BLL (e.g.: DataSet, DataTable,etc..)
- BLL is responsible for using the content of those neutral data types to create, populate and return specifci Business Entities
Your BLL must reference your DAL. that's it.
You can off course completly ignore this model and hack about as many suggested previously using IBOOK,etc... but than your are not using the intended model and might as well throw it all into a single assembly as you won't be able to maintain it independantly anyway.