Connect to SQL Server and SQL Server CE database in same code - c#

Let me preface this by saying I'm kind of new to C# and database implementation. I'm trying to create some code that will connect to a database, but the type of database can vary between SQL Server, SQL Server CE, and maybe later on MySQL or something like that. I'm trying to implement the code using the DbConnection, DbCommand, DbDataAdapter, etc classes that is the base class of all these types of database classes. However, I am having difficulty trying to set this up.
I've tried using DatabaseFactory.CreateDatabase("connectionString") but it wants me to put connectionString the the app.config file, but I don't want the program to be set up that way.
I've also tried DbProviderFactories.GetFactory(providerStr) but in order to get the correct providerStr, I need to know which database I'm using.
Basically I want it so that no matter what type of database I am using, the code will still work. Is there any easy way to do this or is it possible? Any help is appreciated.

I've come to the conclusion that there is no way to do this without exactly specifying the type of database to connect to. So my answer was to add an enumeration of the varying types of databases and use DbProviderFactories.GetFactory(providerStr) to create a DbProviderFactory instance. Here's some of the code if anyone is interested or trying to do what I'm doing.
DbProviderFactory dbFactory;
private DatabaseTypes m_dbType;
public DatabaseTypes DbType
{
get { return m_dbType; }
set
{
m_dbType = value;
dbFactory = DbProviderFactories.GetFactory(DatabaseProvider.GetDatabaseProvider(m_dbType));
}
}
In another file I have the enumeration and DatabaseProvider class defined. It looks like this:
public enum DatabaseTypes
{
SQL,
SQL_CE
}
public static class DatabaseProvider
{
private const string SQL_Provider = "System.Data.SqlClient";
private const string SQL_CE_Provider = "System.Data.SqlServerCe.3.5";
public static string GetDatabaseProvider(DatabaseTypes dbType)
{
switch (dbType)
{
case DatabaseTypes.SQL_CE:
return SQL_CE_Provider;
case DatabaseTypes.SQL:
default:
return SQL_Provider;
}
}
}
Now if I want to add another database type, I'll just add it to the enumeration, get add another provider string, and put another case in the switch statement! All I need to do now is figure out how I am going to handle the queries.

Related

Load an object from SQL Server but use different object properties for the c# class

I'm not sure if I'm being clear in the title but I'd like to "load" information from a SQL Server database into a list of objects. I'm new to c# and honestly haven't done any coding in a while.
Essentially the table would have columns: app_name, app_type, app_disposition and the object has properties: name, type, disposition. I've got what I want working using Dapper and simply making the object properties the same as the table columns.
Just curious if you could load but using different object property names.
With Dapper, simplest solution is to use aliases.
Your class is:
public class MyPoco
{
public string Name {get;set;}
//Declare other properties here
}
And, you fill this class as below:
string sql = "SELECT app_name as Name, [include other columns here]
FROM MyTable";
using (var conn = GetOpenConnection())
{
var myPocoList = conn.Query<MyPoco>(sql);
}
GetOpenConnection method above simply returns open connection depending on your RDBMS.
Please note that there are many other ways to map the miss-matching column and property names. Please refer this Q&A for more details.
Yes, you can, and you have to use the "Custom Mapping" feature. Here's a detailed article I wrote on the subject, along with code samples, to shows how you can do it.
https://medium.com/dapper-net/custom-columns-mapping-1cd45dfd51d6
Hint: Use Dapper.Fluent-Map plugin

Switchable connection strings via code at run time

I have a WPF application. It has many blocks that access the database. The connection string is determined by a setting in my App.config file.
using (var dbContext = new MyEntities())
{
// ...
}
But now I need the ability to switch between different databases at run time. I've updated the code to manage a list of databases, along with a connection string to each one.
My question is, is there any way to have the existing code using the connection string indicated by my code without having to change the existing code? For example, perhaps I can create a static connection string property in my entities class, and then modify that class to use the property each time it connects to the database.
Has anyone done this? Any tips to do this in a painless way?
So the best idea I came up with was to add a static ConnectionString property to my entities class. And then modify the constructor to pass this property to the base class.
One issue is that any change to the entities class would be overwritten by the T4 scripts when the model is updated. You could create a partial class and define your own constructor, but if that constructor has no arguments then it will conflict with the existing constructor.
My solution for this was to actually edit the existing T4 script directly, as shown below.
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
// Original code
//public <#=code.Escape(container)#>()
// : base("name=<#=container.Name#>")
//{
// Modified code
public static string ConnectionString { get; set; }
public <#=code.Escape(container)#>()
: base(ConnectionString)
{
// End of modified code
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
When you save the T4 script, it will immediately regenerate the code.

What is the best practice to manage stored procedures in C# code?

I'm quite new here, so please forgive me if I made any deviation from the rules of this website.
I'm trying to find the best way possible to manage the names of a stored procedure in code.
Currently when I'm calling a stored procedure I'm using this code:
public static DataSet GetKeyTables()
{
DataSet ds = new DataSet();
ds = SqlDBHelper.ExecuteMultiSelectCommand("Sp_Get_Key_Tables",
CommandType.StoredProcedure);
return ds;
}
But I don't think that stating the name of the stored procedure in code is a wise idea, since it will be difficult to track.
I thought about Enum or app.config solutions, but I'm not sure these are the best ways.
Any idea will be highly appreciated.
You can have a class with constant properties having names of the SPs.And have this class in a seperate class library (dll). Also it is not good to have sp_ as start of procedure see the link http://msdn.microsoft.com/en-us/library/dd172115(v=vs.100).aspx
public class StoredProcedures
{
public const string GetKeyTables = "Sp_Get_Key_Tables";
}
In the end, it always boils down to the concrete name string of the SP, no matter what you do. You have to keep them in sync manually. - No way around it...
You could use configuration files for that, but that additional effort will only pay when the names change frequently or they need to remain changeable after compilation.
You can wrap the calls in a simple gateway class:
public static class StoredProcedures
{
public static DataSet GetKeyTables()
{
return SqlDBHelper.ExecuteMultiSelectCommand(
"Sp_Get_Key_Tables",
CommandType.StoredProcedure);
}
public static DataSet GetFoobars()
{
return SqlDBHelper.ExecuteMultiSelectCommand(
"Sp_Get_Foobars",
CommandType.StoredProcedure);
}
}
Alternatively you can have POCOs that know how to interact with the database:
public class KeyTable
{
public int Id { get; set; }
// whatever data you need
public static List<KeyTable> GetKeyTables
{
var ds = SqlDBHelper.ExecuteMultiSelectCommand(
"Sp_Get_Key_Tables",
CommandType.StoredProcedure);
foreach (var dr in ds.Tables[0].Rows)
{
// build the POCOs using the DataSet
}
}
}
The advantage of this is that not only the SP name is kept in a unique place, but also the logic of how to extract data out of the dataset is in the same place.
I don't see a huge issue with what you are doing. You would need to store the SP name somewhere, so either in the query or in another config or helper function.
Depending on the specification, I tend towards a repository for CRUD operations, so I know all data access, including any SP calls, are in the ISomeRepository implementation.
When I work with stored procedures in C# I follow the following rules.
Every stored procedure is used only once in the code so that I have
only one place to update. I do hate Magic strings and avoid
them but stored procedures are used in Data Access Layer only once (e.g. Repositories, ReadQueries, etc).
For CRUD operations the pattern "sp_{Create/Read/Update/Delete}{EntityName}" is used.
If you want to have single place with all your stored procedures, you can create a static class with logic to create stored procedure's names.

Read from multiple similar unknown databases

I am new to coding in C#, most of my experience is in plain old C.
I am trying to code an application in C# that will look up information from one of many Access databases. Each database is similar in that they have the same tables (named appropriate to the database itself(PIC, PICenum, etc)). The same table in each database contains the same field names (LnetVar, Description, etc). Each database applies to a different data set, which is different from the other databases, which is why I have multiple databases instead of one all encompassing database. It also makes maintaining it much easier.
I currently have code that can access one specific database and pull out the data I need for use elsewhere in the code.
class DB_Handler
{
lsftTestDataSet.PICDataTable ds;
public DB_Handler(lsftTestDataSet.PICDataTable ds)
{
this.ds = ds;
}
public string GetDescription(byte lnetVar)
{
foreach (lsftTestDataSet.PICRow currentRow in ds)
{
if (currentRow.LnetVar == lnetVar)
{
return currentRow.Description;
}
}
return "";
}
}
I don't want to do the old Copy-Paste-Modify trick so that it can interact with each database. Rather I want a single function to call that I send the database I want to use as well as the relevant record and field information. From that it searches the database and returns the stored data.
I found a similar question here, but it was focused on optimizing, not on actually doing it. I was also unable to follow the code snippet given to modify it to use with my code.
Any help you can give me would be greatly appreciated!
As you said, what you need is a method/function receiving a parameter containing the connection string you want to use.
I would do something like this :-
In Web.config or App.Config
<connectionStrings>
<add name="DbConnnectionString" connectionString="server=myserver;database={MyDatabasePlaceHolder};Integrated Security=SSPI;"
</connectionStrings>
and then in code (also, need to add a reference of System.Configuration.dll)
using System.Configuration;
class DB_Handler
{
string connectionString=string.Empty;
public DB_Handler(string databaseName)
{
this.connectionString = ConfigurationManager.ConnectionStrings["DbConnnectionString"].ConnectionString.Replace ("{MyDatabasePlaceHolder}", databaseName)
}
public GetData()
{
// Make use of this.connectionString to fetch data
}
}

NHibernate.Spatial and Sql 2008 Geography type - How to configure

I am trying to use Nhibernate with the Sql 2008 Geography type and am having difficulty. I am using Fluent Nhibernate to configure which I am fairly new to so that may be the problem as well.
First, the class I am trying to persist looks something like:
public class LocationLog : FluentNHibernate.Data.Entity
{
public virtual new int Id {get;set;}
public virtual DateTime TimeStamp {get;set;}
public virtual GisSharpBlog.NetTopologySuite.Geometries.Point Location {get;set;}
}
The mapping class looks like:
public class LocationLogMap : ClassMap<LocationLog>
{
ImportType<GisSharpBlog.NetTopologySuite.Geometries.Point>();
Id(x => x.Id);
Map(x => x.TimeStamp).Generated.Insert();
Map(x => x.Location);
}
In order to use the MsSql2008GeographyDialect with Fluent Nhibernate, I have created my own configuration class:
public class Sql2008Configuration
: PersistenceConfiguration<Sql2008Configuration, MsSqlConnectionStringBuilder>
{
public Sql2008Configuration()
{
Driver<SqlClientDriver>();
}
public static Sql2008Configuration MsSql2008
{
get { return new Sql2008Configuration().Dialect<MsSql2008GeographyDialect>(); }
}
}
so I have configuration code like:
var configuration = Fluently.Configure()
.Database(Sql2008Configuration.MsSql2008.ConnectionString(c => c.Is(connectionString)))
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<LocationLog>()
);
All of this to setup the fact that I am getting the following error when trying to persist the LocationLog type to the database:
A .NET Framework error occurred during
execution of user-defined routine or
aggregate "geography":
System.ArgumentException: 24204: The
spatial reference identifier (SRID) is
not valid. The specified SRID must
match one of the supported SRIDs
displayed in the
sys.spatial_reference_systems catalog
view. System.ArgumentException: at
Microsoft.SqlServer.Types.SqlGeography.set_Srid(Int32
value) at
Microsoft.SqlServer.Types.SqlGeography.Read(BinaryReader
r) at
SqlGeography::.DeserializeValidate(IntPtr
, Int32 , CClrLobContext* )
I have read the following articles about how to configure and use the Nhibernate Spatial libraries:
http://nhibernate.info/doc/spatial/configuration-and-mapping.html
http://nhibernate.info/doc/spatial/sample-usage.html
but neither seem to help. Anybody that has experience configuring Nhibernate to use the Spatial Geography types who could provide any insights would be greatly appreciated.
I am in the same boat, and thanks to your start I got it working (inserting and reading spatial data). For anyone else who is interested, firstly the GisSharpBlog.NetTopologySuite.Geometries.Point class is in NetTopologySuite.dll which is part of the nHibernate.Spatial download.
Secondly, as per James point, make sure you set the SRID to 4326.
And lastly, the map needs to look like this:
Map(a => a.Location).CustomType(typeof(NHibernate.Spatial.Type.GeometryType));
I am using Geography, but I read somewhere that using GeometryType may work and it does for me (I inserted some points and verified it in the database). I also read that its best to write SQL Query's for Geography so that you can use the special SQL 2008 Spatial methods (as opposed to using Criteria).
Steve is right, you need to explicitly set a SRID for your geometry type. Looking at the NHibernate.Spatial source (which you can checkout using SVN or whatever), doing a search for SRID comes up with this buried in the code as a comment hint:
<class name="MyGeoTableA">
<property name="MyGeoColumn">
<type name="NHibernate.Spatial.Type.GeometryType, NHibernate.Spatial">
<param name="srid">1234</param>
</type>
</property>
</class>
It looks like you need to set a parameter named SRID to whatever number you need (look it up in a SRID table). Obviously this is old-school XML configuration but fluent will have a method somewhere to add key/value string parameters. Give that a try.
Edit
After a bit more research, I found that trying to set a srid attribute on a column failed NHibernate's XML mapping validation, it throws an XmlSchemaValidationException. Instead, I found that geometry types in NetNopologySuite have a SRID attribute on the object themselves, and setting this makes things work. e.g.
LocationLog log = new LocationLog { Location = new Point() };
log.Location.SRID = 4326;
Session.Save(log);
There must be a better way to do this though (configure it instead of setting it all the time) but I haven't worked that out yet. If you look inside MsSql2008GeometryType class, it has a protected method called SetDefaultSRID(IGeometry) - it must be there for a reason!
Not really an answer but questions ;-)
Are you setting a SRID on the GisSharpBlog.NetTopologySuite.Geometries.Point object?
The default (since the point is a geometry) is 0 and will give you a SQL error when trying to persist the LocationLog.Location property as a geography. 0 is not a valid SRID for sql geography fields. You will need to specify one from the sys.spatial_reference_systems view.
Have you tried without Fluent NHibernate?
To eliminate as many components from the problem.
You can create your own factory with a default SRID.
For example, you can create a facade for factories such as this:
public static class Default
{
private const int DefaultSrid = 4326;
public static readonly IGeometryFactory Factory;
static Default()
{
Factory = new GeometryFactory(new PrecisionModel(), DefaultSrid);
}
}
and use it like this:
var point = Default.Factory.CreatePoint(new Coordinate(10, 10));
instead of using the "new" keyword.
You can also use the Default.Factory as a factory method in your IoC framework to create new geometries without the Default facade.
I know this hardly be useful but in any case.
After implementing all lain had said do use in your HQL queries SetParameter third IType parameter. Meaning in
Hero hero = openSession.Get<Hero>(3);
openSession.CreateQuery(
"from Hero h where NHSP.Distance(h.Location,:thislocation)<1000"
).SetParameter("thislocation", hero.Location, new CustomType(typeof(MsSql2008GeographyType), null) ).SetResultTransformer(new DistinctRootEntityResultTransformer())
.List();
new CustomType(typeof(MsSql2008GeographyType), null) must be passed or you get your all too famililar "System.ArgumentException: 24204".
Just spent whole night figuring that one out.

Categories