Single responsibility principle: method DoDatabaseBackup is doing a lot of things - software-design

I'm developing a C# library with .NET Framework 4.7 and Visual Studio 2017 Community.
I'm trying to understand and use single responsibility principle correctly.
I have this class to do a backup in a SQL Server 2012 Database:
public static class DbManagement
{
private static string sqlBackupStatement =
"BACKUP DATABASE [{0}] TO DISK = N'{1}' WITH COMPRESSION, COPY_ONLY, NAME=N'{2}'";
/// <summary>
/// Do backup for a database.
/// </summary>
/// <param name="connectionString">Connection's string to database</param>
/// <param name="path">Path to safe the database</param>
/// <param name="backupFileName">Backup's file name</param>
/// <param name="backupName">This name will be used to identify this backup.</param>
/// <returns>Database's execution result</returns>
public static int DoDatabaseBackup(
string connectionString,
string path,
string backupFileName,
string backupName)
{
if (string.IsNullOrWhiteSpace(connectionString))
throw new ArgumentNullException(nameof(connectionString));
if (string.IsNullOrWhiteSpace(path))
throw new ArgumentNullException(nameof(path));
if (string.IsNullOrWhiteSpace(backupFileName))
throw new ArgumentNullException(nameof(backupFileName));
if (string.IsNullOrWhiteSpace(backupName))
throw new ArgumentNullException(nameof(backupName));
string databaseName = GetDatabaseName(connectionString);
string fullPath = GetFullPath(path);
string pathWithFile = fullPath + backupFileName + "_" + DateTime.Now.ToString("yyyyMMddHHmm") + ".bak";
string description = string.Format("{0} - Full database Backup", backupName);
if (!Directory.Exists(fullPath))
Directory.CreateDirectory(fullPath);
string sqlStatement = string.Format(sqlBackupStatement, databaseName, pathWithFile, description);
TRZLDbContext context = new TRZLDbContext(connectionString);
return
context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, sqlStatement);
}
private static string GetDatabaseName(string connectionString)
{
SqlConnectionStringBuilder sqlConnBuilder =
new SqlConnectionStringBuilder(connectionString);
return sqlConnBuilder.InitialCatalog;
}
private static string GetFullPath(string path)
{
StringBuilder builder = new StringBuilder(path);
if (!path.EndsWith("\\"))
builder.Append("\\");
DateTime today = DateTime.Now;
builder.Append(today.Year.ToString());
builder.Append("\\");
builder.Append(today.Month.ToString());
builder.Append("\\");
return builder.ToString();
}
}
I think a do a lot of things in method DoDatabaseBackup:
Get database name.
Get full path to store backup file.
Get full path with backup's file name.
Create backup's description.
Create directory where I will store the backup if it doesn't exist.
Create the SQL statement to do the backup.
And finally... do the backup.
Am I using Single Responsibility Principle correctly here? It it is not, do I have to move points 1 to 6 to 6 methods?

You could refactor this into a number of separate classes with distinct responsibilities. If you think of the types of objects that could be represented here you would have something like:
A connection string
A database backup
A backup statement
A backup file name
With SRP it's important to think about how objects may change. For example, the algorithm for changing the backup file name may change, so that may want to be represented in a separate class so that it can be swapped out for a new algorithm if necessary. You may need to change the SQL statement if you start using a different type of database. The connection string also has a dependency on SQL which may change in future.
I had a go at refactoring your above method and came up with the following:
public class ConnectionString
{
private readonly string connectionString;
public ConnectionString(string connectionString)
{
this.connectionString = connectionString;
}
public string DatabaseName()
{
SqlConnectionStringBuilder sqlConnBuilder =
new SqlConnectionStringBuilder(this.connectionString);
return sqlConnBuilder.InitialCatalog;
}
}
public class BackupFileName
{
private readonly string backupFilePath;
private readonly string backupFileName;
public BackupFileName(string backupFilePath, string backupFileName)
{
this.backupFilePath = backupFilePath;
this.backupFileName = backupFileName;
}
public string FullPath => this.backupFilePath;
public override string ToString()
{
return this.GetFullPath(this.backupFilePath) + this.backupFileName + "_" +
DateTime.Now.ToString("yyyyMMddHHmm") + ".bak";
}
private string GetFullPath(string path)
{
StringBuilder builder = new StringBuilder(path);
if (!path.EndsWith("\\"))
builder.Append("\\");
DateTime today = DateTime.Now;
builder.Append(today.Year.ToString());
builder.Append("\\");
builder.Append(today.Month.ToString());
builder.Append("\\");
return builder.ToString();
}
}
public class BackupStatement
{
private string sqlBackupStatement =
"BACKUP DATABASE [{0}] TO DISK = N'{1}' WITH COMPRESSION, COPY_ONLY, NAME=N'{2}'";
private readonly string databaseName;
private readonly string filePath;
private readonly string description;
public BackupStatement(string databaseName, string filePath, string description)
{
this.databaseName = databaseName;
this.filePath = filePath;
this.description = description;
}
public override string ToString()
{
return string.Format(sqlBackupStatement, databaseName, filePath, description);
}
}
public class DatabaseBackup
{
private readonly ConnectionString connectionString;
private readonly string backupName;
private readonly BackupFileName backupFileName;
public DatabaseBackup(
string connectionString,
string path,
string backupFileName,
string backupName)
{
if (string.IsNullOrWhiteSpace(connectionString))
throw new ArgumentNullException(nameof(connectionString));
if (string.IsNullOrWhiteSpace(path))
throw new ArgumentNullException(nameof(path));
if (string.IsNullOrWhiteSpace(backupFileName))
throw new ArgumentNullException(nameof(backupFileName));
if (string.IsNullOrWhiteSpace(backupName))
throw new ArgumentNullException(nameof(backupName));
this.connectionString = new ConnectionString(connectionString);
this.backupFileName = new BackupFileName(path, backupFileName);
this.backupName = backupName;
}
/// <summary>
/// Creates a backup for a database.
/// </summary>
/// <returns>Database's execution result</returns>
public int Create()
{
string description = string.Format("{0} - Full database Backup", this.backupName);
if (!Directory.Exists(this.backupFileName.FullPath))
Directory.CreateDirectory(this.backupFileName.FullPath);
TRZLDbContext context = new TRZLDbContext(this.connectionString.ToString());
return
context.Database.ExecuteSqlCommand(
TransactionalBehavior.DoNotEnsureTransaction,
new BackupStatement(
this.connectionString.DatabaseName(),
this.backupFileName.ToString(),
description).ToString());
}
}
It's not perfect, and each of the above classes should really be using interfaces (as per Dependency Inversion) but I hope it illustrates a point.

Both.
Don't recreate things like the backup name on the fly. Have a method/function/whatever that gives that to you.
Otherwise, it is fine for a function to do more than one thing to accomplish a goal which subsumes those things. That said, don't duplicate actions found elsewhere (like building a backup path).
The single responsibility principle exists to modularize unique objects to single places -- it is a generalization of the One Definition Rule. (Yeah, I know you're using Java. Sorry...LOL)

Related

Event and error logging in Asp.net MVC 5 project

I am looking at implementing a logging mechanism in a site of mine, I wish to do basic user action logging. I don't want to log every single button they click on, but I do want to log actions they do which makes changes.
Are there any libraries or articles / tutorials out there which can help me implement a good and efficient logging mechanism for my asp.net site. Im not sure if there are any changes in MVC5 that might come in use for logging as I know user Authentication and Authorization has changed a fair amount from 4 to 5.
I'm sure that there is a dynamic library out there that will work in many different situations.
Nice to haves:
Async capability
Scalable
Simple to use
I'm thinking along the lines of creating a custom filter or attribute that then logs the suers action, but that's just my Idea, Im here to ask what the standard / industry way to do it is.
There isn't an industry standard.
I've used filters or I've overridden the "onActionExecuting" method on the base controller class to record controller / action events.
EDIT ::
Trying to be more helpful but this is really vague.
If you're worried about errors and things like that use elmah.
For other logging use Nlog or Log4Net.
If you're trying to do extra logging like auditing or something like that you can use any combination of those, or something custom. In my site I created a table that stores every click on the site by creating an object sort of like this :
public class audit
{
public int ID { get; set; }
public DateTime AuditDate { get; set; }
public string ControllerName { get; set; }
public string ActionName { get; set; }
public Dictionary<string, object> values
}
In my base constructor, I overrode the OnActionExecuting event :
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
checkForLogging(ctx);
//do not omit this line
base.OnActionExecuting(ctx);
}
Lets say I want to log all Get Requests using my new audit object
private void checkForLogging(ActionExecutingContext ctx)
{
//we leave logging of posts up to the actual methods because they're more complex...
if (ctx.HttpContext.Request.RequestType == "GET")
{
logging(ctx.ActionDescriptor.ActionName, ctx.ActionDescriptor.ControllerDescriptor.ControllerName, ctx.ActionParameters);
}
}
Thats all the info I need to fill my logging object with the action name, the controller name and all the params passed into the method. You can either save this to a db, or a logfile or whatever you want really.
The point is just its a pretty big thing. This is just one way to do it and it may or may not help you. Maybe define a bit more what exactly you want to log and when you want to do it?
You can create a custom attribute and decorate methods with it and then check if that attribute is present when the OnActionExecuting method fires. You can then get that filter if present and read from it and use that to drive your logging if you want...
Maybe this example will help.
My focus on logging is in the CREATE, EDIT, DELETE actions.
I am using MVC 5 Code-first EF 6.1 (VS 2013) ,
and for this example I are referring to the Create action for an entity called "WorkFlow"
I actually view these logs from SSRS, but you could add a controller and Views for WriteUsageLog and view them from the MVC application
MODEL: Create a MODEL Entity called "WriteUsageLog" which will be where the log records are kept
CONTROLLER: Extract, or refactor, the HttpPost overload of the "Create" action from the WorkFlowController into a Partial Class called "WorkFlowController" (These partials are to avoid being deleted and rebuilt when I use the wizard to create Controllers)
Other Classes in the CONTROLLER folder: Then there are some helper functions that are required in a class called "General_Object_Extensions" and "General_ActiveDirectory_Extensions" (NOTE: these are not really 'extensions')
Add the following line to the DBContext:
public DbSet WriteUsageLogs { get; set; }
The advantage of this example is:
I am recording the following for the record:
User Name from Active Directory
The DateTime that the log record is being created
The computer name
And a Note that consists of the values for all the entity properties
I am recording the log in a table from which I can access it either using an MVC controller, or preferably from SQL Server Report Server. Where I can monitor all my MVC applications
/Models/WriteUsageLog.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MileageReimbursement.Models
{
public class WriteUsageLog
{
public WriteUsageLog()
{
this.DateTimeCreated = DateTime.Now; // auto-populates DateTimeCreated field
}
[Key]
public int WriteUsageLogID { get; set; }
[Column(TypeName = "nvarchar(max)")]
public string Note { get; set; }
public string UserLogIn { get; set; }
public string ComputerName { get; set; }
public DateTime DateTimeCreated { get; private set; } //private set to for auto-populates DateTimeCreated field
}
}
/Controllers/ControllerPartials.cs
using System.Linq;
using System.Web.Mvc;
using MileageReimbursement.Models;
//These partials are to avoid be deleted and rebuilt when I use the wizard to create Controllers
namespace MileageReimbursement.Controllers
{
public partial class WorkFlowController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "whatever")] WorkFlow workFlow)
{
...
if (ModelState.IsValid)
{
db.WorkFlows.Add(workFlow);
db.SaveChanges();
//===================================================================================================================
string sX = workFlow.GetStringWith_RecordProperties();
//===================================================================================================================
var logRecord = new WriteUsageLog();
logRecord.Note = "New WorkFlow Record Added - " + sX;
logRecord.UserLogIn = General_ActiveDirectory_Extensions.fn_sUser();
string IP = Request.UserHostName;
logRecord.ComputerName = General_functions.fn_ComputerName(IP);
db.WriteUsageLogs.Add(logRecord);
db.SaveChanges();
//===================================================================================================================
return RedirectToAction("Index");
}
else // OR the user is directed back to the validation error messages and given an opportunity to correct them
{
...
return View(workFlow); //This sends the user back to the CREATE view to deal with their errors
}
}
}
}
/Controllers/ControllerExtensions.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace MileageReimbursement.Controllers
{
public static class General_ActiveDirectory_Extensions
{
public static string fn_sUser()
{
char cX = '\\';
string sUser = General_functions.fn_ReturnPortionOfStringAfterLastOccuranceOfACharacter(HttpContext.Current.User.Identity.Name, cX);
return sUser; //returns just the short logon name Example for 'accessiicarewnc\ggarson', it returns 'ggarson'
}
} //General_ActiveDirectory_Extensions
public static class General_Object_Extensions
{
public static string GetStringWith_RecordProperties(this object Record)
{
string sX = null;
Dictionary<string, object> _record = GetDictionary_WithPropertiesForOneRecord(Record);
int iPropertyCounter = 0;
foreach (var KeyValuePair in _record)
{
iPropertyCounter += 1;
object thePropertyValue = _record[KeyValuePair.Key];
if (thePropertyValue != null)
{
sX = sX + iPropertyCounter + ") Property: {" + KeyValuePair.Key + "} = [" + thePropertyValue + "] \r\n";
}
else
{
sX = sX + iPropertyCounter + ") Property: {" + KeyValuePair.Key + "} = [{NULL}] \r\n";
}
}
return sX;
}
public static Dictionary<string, object> GetDictionary_WithPropertiesForOneRecord(object atype)
{
if (atype == null) return new Dictionary<string, object>();
Type t = atype.GetType();
PropertyInfo[] props = t.GetProperties();
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (PropertyInfo prp in props)
{
object value = prp.GetValue(atype, new object[] { });
dict.Add(prp.Name, value);
}
return dict;
}
} //General_Object_Extensions
public static class General_functions
{
public static string fn_ComputerName(string IP)
{
//USAGE
//From: http://stackoverflow.com/questions/1444592/determine-clients-computer-name
//string IP = Request.UserHostName;
//string compName = CompNameHelper.DetermineCompName(IP);
IPAddress myIP = IPAddress.Parse(IP);
IPHostEntry GetIPHost = Dns.GetHostEntry(myIP);
List<string> compName = GetIPHost.HostName.ToString().Split('.').ToList();
return compName.First();
}
static public string fn_ReturnPortionOfStringAfterLastOccuranceOfACharacter(string strInput, char cBreakCharacter)
{
// NOTE: for path backslash "/", set cBreakCharacter = '\\'
string strX = null;
//1] how long is the string
int iStrLenth = strInput.Length;
//2] How far from the end does the last occurance of the character occur
int iLenthFromTheLeftOfTheLastOccurance = strInput.LastIndexOf(cBreakCharacter);
int iLenthFromTheRightToUse = 0;
iLenthFromTheRightToUse = iStrLenth - iLenthFromTheLeftOfTheLastOccurance;
//3] Get the Portion of the string, that occurs after the last occurance of the character
strX = fn_ReturnLastXLettersOfString(iLenthFromTheRightToUse, strInput);
return strX;
}
static private string fn_ReturnLastXLettersOfString(int iNoOfCharToReturn, string strIn)
{
int iLenth = 0;
string strX = null;
int iNoOfCharacters = iNoOfCharToReturn;
iLenth = strIn.Length;
if (iLenth >= iNoOfCharacters)
{
strX = strIn.Substring(iLenth - iNoOfCharacters + 1);
}
else
{
strX = strIn;
}
return strX;
}
} //General_functions
}
I would agree that Log4Net and NLog seem to be the two most commonly used products on the different projects I have been a member.
If you are looking for a great tool that you can use for logging, error handling and anything else where AOP would be beneficial I would highly recommend PostSharp (http://www.postsharp.net/). You set your logging/error handling up centrally and then just decorate methods. It is a well documented and supported product. They have a community license, which is free - and it is free for individuals. They also have professional and ultimate versions of the products, which would make more sense if you're using it as a team.
I don't work at PostSharp :-) I've just used it in the past and really like it.

How to convert database table into XML in ASP.NET MVC4

I am trying to convert SQL server database table into XML file. I followed this solution.
I have created this class as shown in solution
public class XmlResult : ActionResult
{
private object objectToSerialize;
/// <summary>
/// Initializes a new instance of the <see cref="XmlResult"/> class.
/// </summary>
/// <param name="objectToSerialize">The object to serialize to XML.</param>
public XmlResult(object objectToSerialize)
{
this.objectToSerialize = objectToSerialize;
}
/// <summary>
/// Gets the object to be serialized to XML.
/// </summary>
public object ObjectToSerialize
{
get { return this.objectToSerialize; }
}
/// <summary>
/// Serialises the object that was passed into the constructor to XML and writes the corresponding XML to the result stream.
/// </summary>
/// <param name="context">The controller context for the current request.</param>
public override void ExecuteResult(ControllerContext context)
{
if (this.objectToSerialize != null)
{
context.HttpContext.Response.Clear();
XmlRootAttribute root = new XmlRootAttribute("response");
var xs = new System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType(), root);
context.HttpContext.Response.ContentType = "text/xml";
xs.Serialize(context.HttpContext.Response.Output, this.objectToSerialize);
}
}
Instead of this:
public ActionResult GetStuffAsXml(int id)
{
var dbStuff = db.GetStuff(id);
// fetch stuff in database
return new XmlResult(dbStuff);
}
I have written this(my purpose is to get all products):
public ActionResult Transfer()
{
var product = from s in db.Product
select s;
// fetch stuff in database
return new XmlResult(product);
}
In debugging process, this error came out:
To be XML serializable, types which inherit from IEnumerable must have
an implementation of Add(System.Object) at all levels of their
inheritance hierarchy.
System.Data.Entity.Infrastructure.DbQuery`1[[Overstock.Models.Product,
Overstock, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
does not implement Add(System.Object).
Source error:
Line 42: var xs = new
System.Xml.Serialization.XmlSerializer(this.objectToSerialize.GetType(),
root);
I assume that error is coming out because I am taking products in wrong way:
var product = from s in db.Product select s;
In what form I should send data to XmlResult class in order to convert SQL Server table to XML file format?
If 'db' is a DbContext in this method
var product = from s in db.Product
select s;
// fetch stuff in database
return new XmlResult(product);
Then you are not getting out a DataRow or DataTable, you're getting a collection of strongly typed classes. If you want make xml from them use this code:
public static string SerializeAsXml<T>(T element)
{
XmlSerializer xmlSerializer = new XmlSerializer(element.);
StringWriter textWriter = new StringWriter();
xmlSerializer.Serialize(textWriter, element.GetType());
return textWriter.ToString();
}
call it
var products = from s in db.Product
select s;
// fetch stuff in database
return SerializeAsXml(products);

Get value from language specific resource file without changing the site language

I have several resource files, e.g.
default.aspx.resx, default.aspx.nl.resx, default.aspx.en.resx
Now when I'm on the Dutch domain the default.aspx.nl.resx is loaded.
But now I want to access the value from default.aspx.en.resx and get the English value belonging to name "title".
I can now achieve this by changing the current culture server-side, access the value and then change it back, like so:
Dim culture As CultureInfo = New CultureInfo("en")
Threading.Thread.CurrentThread.CurrentCulture = culture
Threading.Thread.CurrentThread.CurrentUICulture = culture
Dim title as String = GetLocalResourceObject("title")
culture = New CultureInfo("nl")
Threading.Thread.CurrentThread.CurrentCulture = culture
Threading.Thread.CurrentThread.CurrentUICulture = culture
But is there a better/faster way? Preferably without have to change the culture for the current thread, so I can just define which resource file I want to access and in which language?
You can add in parameter your targe culture
GetLocalResourceObject("title","YourCulture");
link : http://msdn.microsoft.com/fr-fr/library/ms149953.aspx
Edit: (Sorry I didn't know that you wanted another method different from this, but this was the only way that I managed to do:)
I managed to do this by doing:
var userLanguage = "en-US";
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo(userLanguage);
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo(userLanguage);
HttpContext.GetGlobalResourceObject("MyAppResource", "KeyThatIWantToGet");
Where MyAppResource is how your .resx file is named and KeyThatIWantToGet explains itself.
When not using the HttpContext (general .NET applications) I use the following helper:
/// <summary>
/// Disposable class that lets us assume a specific culture while executing
/// a certain block of code. You'd typically use it like this:
///
/// using (new CultureContext("de"))
/// {
/// // Will return the German translation of "Please click here"
/// string text = SharedResource.Please_click_here;
/// }
/// </summary>
public class CultureContext : IDisposable
{
private readonly CultureInfo _previousCulture;
private readonly CultureInfo _previousUiCulture;
public CultureContext(CultureInfo culture)
{
// Save off the previous culture (we'll restore this on disposal)
_previousCulture = Thread.CurrentThread.CurrentCulture;
_previousUiCulture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
public CultureContext(string cultureCode)
: this(new CultureInfo(cultureCode))
{
}
/// <summary>
/// Syntactic sugar so that we can switch to a culture context as follows:
///
/// using (CultureContext.For("de"))
/// {
/// // Will return the German translation of "Please click here"
/// string text = SharedResource.Please_click_here;
/// }
/// </summary>
public static CultureContext For(string cultureCode)
{
return new CultureContext(cultureCode);
}
public void Dispose()
{
// Restore the culture settings that were in place before switching
// to this context
Thread.CurrentThread.CurrentCulture = _previousCulture;
Thread.CurrentThread.CurrentUICulture = _previousUiCulture;
}
}

Get Entity Framework connection string from alternate location?

How can I retrieve the Entity Framework 4 connection string from a custom config file, not web.config?
Edit:
Is it reasonable to delete the default constructor generated code and recreate it in a partial class to use the pulled in connection string?
I would really like to avoid changing all references to the EF context with an overloaded method including the connection string.
#BrokenGlass: This is what we ended up with:
public partial class STARSEntities
{
private const string _connectionStringFormat = #"metadata=res://*/STARS.EntityModel.STARSModel.csdl|res://*/STARS.EntityModel.STARSModel.ssdl|res://*/STARS.EntityModel.STARSModel.msl;provider=System.Data.SqlClient;provider connection string='Data Source={0};MultipleActiveResultSets=True'";
/// <summary>
/// Initializes a new STARSEntities object using the connection string found in the STARS.xml configuration file.
/// </summary>
/// <remarks>
/// If the STARSEntities class is regenerated from the database, the default constructor needs to be removed from the generated file.
/// </remarks>
public STARSEntities() : base(GetConnectionString(), "STARSEntities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
private static string GetConnectionString()
{
return string.Format(_connectionStringFormat, ApplicationConfiguration.GetConnectionString("STARS"));
}
}
There's a constructor overload for DataContext that you can pass a connection string - in that case you can take the setting from anywhere you like.
Edit based on updated question:
I would really like to avoid changing
all references to the EF context with
an overloaded method including the
connection string.
The problem is that the Entities context created by the T4 script generates a const property that is used as connection string
public const string ConnectionString = "name=FooEntities";
public FooEntities()
: base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
Since you can't override the default constructor of the partial class, your only other option would be to change the T4 script itself - you should see the following in your .TT script file:
public <#=code.Escape(container)#>()
: base(ConnectionString, ContainerName)
{
<#
WriteLazyLoadingEnabled(container);
#>
}
To force your connection string to be used you could modify the constructor call to determine the connection string by calling a static method that you define in a separate file (but for the same partial class FooEntities):
public <#=code.Escape(container)#>()
: base(GetCustomConnectionString(), ContainerName)
{
<#
WriteLazyLoadingEnabled(container);
#>
}
Now GetCustomConnectionString() can be defined separately
public partial class FooEntities : ObjectContext
{
public static string GetCustomConnectionString()
{
return "Foobar"; //however you want to determine connection string here
}
}
You see this is getting complicated and fragile very fast, so I would not advise doing this - but you could.
Don't know if this is what you ask for, but you can use the "configSource" attribute of the connectionStrings element:
<connectionStrings configSource="connection.config">
</connectionStrings>
Something like this perhaps?
// replace the following line with any business logic you need to get to the file
// with your connection string. If it is a config-style file, you can use the
// Frameworks's helper classes as well
string connectionString= File.ReadAllText("alternative path");
Entities ent = new Entity(connectionString);
Here's a post on Microsoft Connect that explains this.
Are you able to read the connection string from this custom config file? if so, you can use the constructor for your DataContext that takes ConnectionString.
NorthWindDataContext nwdc = new NorthWindDataContext(alternateConnectionString);
You can use EntityConnectionStringBuilder:
var ecb = new EntityConnectionStringBuilder();
ecb.Metadata = "res://*/Model.MyModel.csdl|res://*/Model.MyModel.ssdl|res://*/Model.MyModel.msl";
ecb.Provider = "System.Data.SqlClient";
ecb.ProviderConnectionString = connectionStringFromFancySource;
return new MyModel(ecb.ToString());

How to connect to a database in ASP.NET?

I moved to ASP.NET from PHP where the queries are run directly. So I always create Connection in the Page_Load Event, dispose it after I do all stuff needed, and access data with NpgsqlCommand. (Yes, I use Postgresql in my ASP.NET applications.)
After starting to learn ASP.NET MVC I was amazed how easy it is to access SQL with the LINQ to SQL thing. But... It works only with MS SQL. So my question is how to implement the same functionality in my applications? How to connect to databases easily?
I wrote my own wrapper classes for connecting to Postgresql. 1 class per a table.
This is a part of the Student class:
public class Student : User
{
private static NpgsqlConnection connection = null;
private const string TABLE_NAME = "students";
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
/// <summary>
/// Updates the student
/// </summary>
public void Update()
{
Connect();
Run(String.Format("UPDATE " + TABLE_NAME + " SET first_name='{0}', last_name='{1}', password='{2}' WHERE id={3}", FirstName, LastName, Password, Id));
connection.Dispose();
}
/// <summary>
/// Inserts a new student
/// </summary>
public void Insert()
{
Connect();
Run(String.Format("INSERT INTO " + TABLE_NAME + " (first_name, last_name, password) VALUES ('{0}', '{1}', '{2}')",FirstName, LastName, Password));
connection.Dispose();
}
private static void Run(string queryString)
{
NpgsqlCommand cmd = new NpgsqlCommand(queryString, connection);
cmd.ExecuteScalar();
cmd.Dispose();
}
private static void Connect()
{
connection = new NpgsqlConnection(String.Format("Server=localhost;Database=db;Uid=uid;Password=pass;pooling=false"));
connection.Open();
}
//....
So as you see the problem here is that with every INSERT, DELETE, UPDATE request I'm using Connect() method which connects to the database. I didn't realize how stupid it was before I had to wait for 10 minutes to have 500 rows inserted, as there were 500 connections to the database.
Using pooling while connecting does help, but still making the connection and making the server check the pool during every single query is stupid.
So I decided to move Connection property to a static DB class, and it didn't work either, because it's a really bad idea to store such objects as connections in a static class.
I really don't know what to do know. Yes, there's an option of manullay creating the connections in every Page_Load event and close them in the end like I'm doing it right now.
Student student = new Student { FirstName="Bob", LastName="Black" };
NpgsqlConnection connection = ... ;
student.Insert(connection);
But this code is pretty ugly. I will be really thankful to somebody who can hep me here.
I would not recommend this design. It is better to encapsulate each database call which means each call opens a new connection each time you need to do something on the db. This might sound inefficient if it were not for connection pooling. ASP.NET will reuse connections automatically for you in a pool. The problem in your design is that there is nothing that guarantees that the connection will be closed.
Thus, you should try something like
private static void Insert()
{
var sql = "Insert Into "....;
ExecuteActionQuery(sql);
}
private static void ExecuteActionQuery( string query )
{
using (var conn = new NpgsqlConnection(String.Format(connString))
{
conn.Open();
using ( var cmd = new NpgsqlCommand(query, connection) )
{
cmd.ExecuteNonQuery();
}
}
}
I typically make a few global functions that encapsulate the standard operations so that I need only pass a query and parameters and my method does the rest. In my example, my ExecuteActionQuery does not take parameters but this was for demonstration only.
Not really pertaining to your question but another solution, if you like linq to sql, you could try DBLinq which provides a Linq to SQL provider for Postgresql and others databases.
http://code.google.com/p/dblinq2007/

Resources