I use FluentMigrator in my project.
And now I Add new column in table and how I can update data in this column by SQL query?
public override void Up()
{
Alter.Table("Images").AddColumn("Item_id").AsInt32().Nullable();
//do something like "Update Images img set img.Item_id=(Select i.Id
//from Items i where i.Image=img.Id)"
}
public override void Down()
{
Delete.Column("Item_id").FromTable("Images");
}
You can either use Insert.IntoTable:
Insert.IntoTable("Users").Row(new { Username = "CURRENT_USER" });
or Execute.WithConnectionto insert data using FluentMigrator
Hi I believe You can make it simpler for Your case, but I had a more complex issue. I added a new column of varbinary type, where I needed to keep a complex object and I needed to prepare it in c# code. So I couldn't just execute UPDATE combined with SELECT command.
I solved that by adding a static class where I execute SQL SELECT command, then I use retrieved data during migration to prepare UPDATE commands.
The important clue is to use ConnectionString which is accessible directly from FluentMigrator.Migration class and execute own SQL commands to get data, then process it in C# and then simply use Update.Table method of FluentMigrator.
Here is my code (simplified):
DatabaseHelper class:
/// <summary>
/// Helper class for executing SQL on a Database defined by given ConnectionString
/// </summary>
public static class DatabaseHelper
{
/// <summary>
/// Run SQL SELECT on a Database defined by given ConnectionString
/// </summary>
/// <param name="connectionString">Connection string</param>
/// <param name="sql">Full SQL SELECT command to be executed</param>
/// <returns>List of rows containing all columns as string[] array</returns>
public static string[][] SelectList(string connectionString, string sql)
{
using (SqlConnection sqlConnection = new SqlConnection($"{connectionString}"))
{
try
{
sqlConnection.Open();
SqlCommand command = new SqlCommand(sql, sqlConnection);
SqlDataReader reader = command.ExecuteReader();
if (!reader.HasRows)
return null;
List<string[]> rowsList = new List<string[]>();
//save all rows (including all columns) from the response to the list
while (reader.Read())
{
//every row has n columns
var row = new string[reader.FieldCount];
//fill every column
for (int i = 0; i < reader.FieldCount; i++)
{
row[i] = reader[i].ToString();
}
//add row to the list
rowsList.Add(row);
}
reader.Close();
sqlConnection.Close();
return rowsList.ToArray();
}
catch (Exception e)
{
if (sqlConnection.State == ConnectionState.Open)
sqlConnection.Close();
Console.WriteLine(e);
throw;
}
}
}
}
Migration class:
public class Migration123 : Migration
{
public override void Up()
{
Create.Column("NEW_COLUMN").OnTable("TABLE_NAME").AsCustom("NVARCHAR(255)").Nullable()
//migrate data from OLD_COLUMN to NEW_COLUMN
MigrateData();
...
}
private void MigrateData()
{
string[][] rowsArray = DatabaseHelper.SelectList(ConnectionString,"SELECT [ID],[OLD_COLUMN] FROM [TABLE_NAME]");
//if nothing to migrate then exit
if (rowsArray == null)
return;
foreach (var row in rowsArray)
{
//prepare a value which will be inserted into a new column, basing on old columns value
var someNewValueForNewColumn = row[1] + " (modified)";
//insert a value into a new column
Update.Table("TABLE_NAME").Set(new
{
NEW_COLUMN = someNewValueForNewColumn
}).Where(new
{
ID = row[0]
});
}
}
public override void Down()
{
...
}
}
Hope it helps somebody!
public class <MigrationClassName> : Migration
{
private readonly ScriptResourceManager _sourceManager;
public <MigrationClassName>(
ScriptResourceManager sourceManager)
{
_sourceManager = sourceManager;
}
public override void Up()
{
MigrateData();
}
private void MigrateData()
{
var script = _sourceManager
.Read("<FileName>.sql");
Execute.Sql(script);
}
}
using System.IO;
public class ScriptResourceManager
{
public string Read(string name)
{
var assembly = typeof(ScriptResourceManager).Assembly;
var resourcesBasePath = typeof(ScriptResourceManager).Namespace;
var resourcePath = $"{resourcesBasePath}.{name}";
using var stream = assembly.GetManifestResourceStream(resourcePath);
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}
SQL FILE
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;
Related
I have created a small ETL program with a single method that only takes a connection string for the source database and a connection string for a target database.
Basically, it has 5 steps:
Step 1. Select data from the source.
Step 2. Create a temporary table on the target.
Step 3. Load data from the source into the temporary on the target.
Step 4. Merge the data from temporary table into the actual target table.
Step 5. Drop the temporary table
This works great for a single transformation that needs to take place, but I have about 20 different ETL "jobs" that need to take place.
So instead of copying and pasting the same method verbatim 19 different times, I would like to define a base class that defines this single method one time, and then call this single method from each child class, with its own select, create, merge and drop statements.
Is this possible?
Base Class:
public class ETLBase
{
private static string Select;
private static string CreateTemp;
private static string Merge;
private static string CleanUp;
private static string DestinationTable;
public static void ExecuteJob(string sourceConnectionString, string destinationConnectionString)
{
using (OracleConnection sourceConnection = new OracleConnection(sourceConnectionString))
{
sourceConnection.Open();
OracleCommand selectCommand = new OracleCommand(Select, sourceConnection);
OracleDataReader reader = selectCommand.ExecuteReader();
using (SqlConnection destinationConnection = new SqlConnection(destinationConnectionString))
{
destinationConnection.Open();
SqlCommand createTempCommand = new SqlCommand(CreateTemp, destinationConnection);
createTempCommand.ExecuteNonQuery();
SqlCommand mergeCommand = new SqlCommand(Merge, destinationConnection);
SqlCommand dropCommand = new SqlCommand(CleanUp, destinationConnection);
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection))
{
bulkCopy.DestinationTableName = DestinationTable;
try
{
bulkCopy.WriteToServer(reader);
mergeCommand.ExecuteNonQuery();
dropCommand.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
reader.Close();
}
}
}
}
}
}
Child Class:
public class ChildETL: ETLBase
{
private static string Select = #"Select THIS DataStatement";
private static string CreateTemp = #"CREATE TABLE Statement";
private static string Merge = #"Merge Table statement";
private static string CleanUp = "DROP TABLE Statement";
private static string DestinationTable = "##TempTable";
}
And then execute it something like this, but where each child class uses its own defined fields, so it uses it's own SQL statements.
public class Program
{
public static void Main(string[] args)
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
string schoolDBConnection = config["SchoolConnection"];
string courseDBConnection = config["CourseConnection"];
string teacherDBConnection = config["Connection"];
ChildETLA.ExecuteJob(schoolDBConnection, courseDBConnection);
ChildETLB.ExecuteJob(teacherDBConnection, courseDBConnection);
//...and so on for each child ETL class
}
}
First of all, you need to run "ExecuteJob()" polymorphically, which means behaviour of "ExeceuteJob" will be different based on source and destination connection string. You can't achieve polymorphism for static function or properties. First of all, you need to refactor it to instance method by taking out the "Static" keywords. Also, the behaviour of the base class is deciding by child classes so that nobody should be able to create an object of base class to make it an abstract class. Look at your code you have two behaviours one for school and another one for the teacher. So you have to create two different child classes which inherit the abstract base class. It is the responsibility of object creation to compose object with source and destination connection string so pass it to the constructor and set it while creating the object itself. Please find the refactored code,
public abstract class ETLBase
{
private readonly string sourceConnectionString;
private readonly string destinationConnectionString;
protected virtual string Select { get; set; } = #"Select THIS DataStatement";
protected virtual string CreateTemp { get; set; } = #"CREATE TABLE Statement";
protected virtual string Merge { get; set; } = #"Merge Table statement";
protected virtual string CleanUp { get; set; } = "DROP TABLE Statement";
protected virtual string DestinationTable { get; set; } = "##TempTable";
protected ETLBase(string sourceConnectionString, string destinationConnectionString)
{
this.sourceConnectionString = sourceConnectionString;
this.destinationConnectionString = destinationConnectionString;
}
public void ExecuteJob()
{
using (OracleConnection sourceConnection = new OracleConnection(sourceConnectionString))
{
sourceConnection.Open();
OracleCommand selectCommand = new OracleCommand(Select, sourceConnection);
OracleDataReader reader = selectCommand.ExecuteReader();
using (SqlConnection destinationConnection = new SqlConnection(destinationConnectionString))
{
destinationConnection.Open();
SqlCommand createTempCommand = new SqlCommand(CreateTemp, destinationConnection);
createTempCommand.ExecuteNonQuery();
SqlCommand mergeCommand = new SqlCommand(Merge, destinationConnection);
SqlCommand dropCommand = new SqlCommand(CleanUp, destinationConnection);
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection))
{
bulkCopy.DestinationTableName = DestinationTable;
try
{
bulkCopy.WriteToServer(reader);
mergeCommand.ExecuteNonQuery();
dropCommand.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
reader.Close();
}
}
}
}
}
And the child classes,
public class ChildETLSchool : ETLBase
{
public ChildETLSchool(string sourceConnectionString, string destinationConnectionString)
: base(sourceConnectionString, destinationConnectionString)
{
//Change values of below lines only if you want to override the values
//Select = #"Select THIS DataStatement";
//CreateTemp = #"CREATE TABLE Statement";
//Merge = #"Merge Table statement";
//CleanUp = "DROP TABLE Statement";
//DestinationTable = "##TempTable";
}
}
public class ChildETLTeacher : ETLBase
{
public ChildETLTeacher(string sourceConnectionString, string destinationConnectionString)
: base(sourceConnectionString, destinationConnectionString)
{
//Change values of below lines only if you want to override the values
//Select = #"Select THIS DataStatement";
//CreateTemp = #"CREATE TABLE Statement";
//Merge = #"Merge Table statement";
//CleanUp = "DROP TABLE Statement";
//DestinationTable = "##TempTable";
}
}
And the object creation in main function,
static void Main(string[] args)
{
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
string schoolDBConnection = config["SchoolConnection"];
string courseDBConnection = config["CourseConnection"];
string teacherDBConnection = config["Connection"];
var childETLSchool = new ChildETLSchool(schoolDBConnection, courseDBConnection);
var childETLTeacher = new ChildETLTeacher(teacherDBConnection, courseDBConnection);
childETLSchool.ExecuteJob();
childETLTeacher.ExecuteJob();
}
Small brief of what i need and what i currently have
I connect to a database and get my data from it and i get ( Name , LongNumber) and basically i have an event (action) that fires when the event occurs (this action gives me a LongNumber aswell).
I need to compare the LongNumbers between the one i got from the event (action) and the one from my database, and if they are similar i take the name and use it.
for example Hello, Alex ( alex is taken from the database)
Issue
I get Index out of range, and using my logic all texts will change what i'm trying to achieve is to change the text to the name of the person that got the Long number the same as the longNumber from the event
Code: Gettings Data from the database
using UnityEngine;
using System.Collections;
using MySql.Data.MySqlClient;
using System;
using System.Linq;
using System.Collections.Generic;
public class MySqlTestScript : MonoBehaviour
{
private static MySqlTestScript _instnace;
public static MySqlTestScript sharedInstance()
{
return _instnace;
}
string row = "";
public string host = "*****";
public string database = "*******";
public string usrename= "*******";
public string password = "******";
public List<Data> userData = new List<Data>();
Data data;
void Awake()
{
_instnace = this;
}
// Use this for initialization
public void Start()
{
GetDataFromDatabase();
}
public string GetDataFromDatabase()
{
string myConnectionString = "Server="+host+";Database="+database+";Uid="+usrename+ ";Pwd="+password+";";
MySqlConnection connection = new MySqlConnection(myConnectionString);
MySqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM Users";
MySqlDataReader Reader;
try
{
connection.Open();
Reader = command.ExecuteReader();
while (Reader.Read())
{
for (int i = 0; i < Reader.FieldCount; i++)
{
//rfid_tags.Add (Reader.GetString("UserName"));
//rfid_tags.Add(Reader.GetString("RFID_Tag"));
data = new Data(Reader.GetString("UserName"), Reader.GetString("RFID_Tag"));
userData.Add(data);
// ws.DomainUrl = reader.GetString("DomainUrl");
// rfid_tags.Add(Reader.GetValue(i).ToString() + ",");
// row += Reader.GetValue(i).ToString() + ", ";
}
Debug.Log(row);
}
}
catch (Exception x)
{
Debug.Log(x.Message);
return x.Message;
}
connection.Close();
return row;
}
}
public class Data {
public string username { get; set; }
public string rfid { get; set; }
public Data(string _name, string _rfid)
{
username = _name;
rfid = _rfid;
}
public void SetUserName(string _userName) { username = _userName; }
public void SetRFID(string _rfid) { rfid = _rfid; }
}
Code for Comparing the values from the event(action) and showing the text
void OnTagsReported(ImpinjReader sender, TagReport report)
{
Debug.Log("OnTagsReported");
// This event handler is called asynchronously
// when tag reports are available.
// Loop through each tag in the report
// and print the data.
foreach (Tag tag in report)
{
Debug.Log(tag.Epc);
// Debug.Log(MySqlTestScript.sharedInstance().rfid_tags[0]);
Debug.Log("STEP ONE");
for (int i = 0; i < MySqlTestScript.sharedInstance().userData.Count; i++)
{
Debug.Log("STEP TWO");
if (tag.Epc.ToString().Trim() == MySqlTestScript.sharedInstance().userData[i].rfid)
{
Debug.Log("STEP THREE");
// TODO References the Name
Loom.QueueOnMainThread(() => {
namesTxt[i].text = MySqlTestScript.sharedInstance().userData[i].username;
});
}
}
As you can see in the Event script it's so unflexible and it gives index out of range if my database has less than 6 rows. I need to make it more friendly and generic
Any help would be appreciated and i hope my question is clear Thank you :)!
This should make more sense:
Database:
using UnityEngine;
using System.Collections;
using MySql.Data.MySqlClient;
using System;
using System.Linq;
using System.Collections.Generic;
public class MySqlTestScript : MonoBehaviour
{
private static MySqlTestScript _instnace;
public static MySqlTestScript sharedInstance()
{
return _instnace;
}
string row = "";
public string host = "*****";
public string database = "*******";
public string usrename= "*******";
public string password = "******";
public List<AppUser> users = new List<AppUser>();
void Awake()
{
_instnace = this;
}
// Use this for initialization
public void Start()
{
GetDataFromDatabase();
}
public string GetDataFromDatabase()
{
string myConnectionString = "Server=" + host + ";Database=" + database + ";Uid=" + usrename + ";Pwd=" + password + ";";
MySqlConnection connection = new MySqlConnection(myConnectionString);
MySqlCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM users";
MySqlDataReader Reader;
try
{
connection.Open();
Reader = command.ExecuteReader();
while (Reader.Read())
{
users.Add(new AppUser() {
username = Reader.GetString("UserName").Trim(),
rfid = Reader.GetString("longNumbers ").Trim()
});
}
}
catch (Exception x)
{
Debug.Log(x.Message);
return x.Message;
}
connection.Close();
return row;
}
}
Data object:
public Class AppUser
{
public string rfid { get; set; }
public string username { get; set; }
}
Event/data comparison:
void OnTagsReported(ImpinjReader sender, TagReport report)
{
Debug.Log("OnTagsReported");
// This event handler is called asynchronously
// when tag reports are available.
// Loop through each tag in the report
// and print the data.
foreach (Tag tag in report)
{
Debug.Log(tag.Epc);
List<AppUser> appUsers = MySqlTestScript.sharedInstance().users;
int numUsers = appUsers.Count;
Debug.Log(numUsers);
for (int i = 0; i < numUsers; i++)
{
if (tag.Epc.ToString().Trim() == appUsers[i].rfid)
{
// TODO References the Name
Loom.QueueOnMainThread(() => {
if (i < namesTxt.Count) namesTxt[i].Text = appUsers[i].username; //assumes textnames is a "List" of textboxes and is already populated with instances of text1, text2 text3 etc. The if is just to guard against there being more rows in the DB than textboxes
});
}
}
}
}
One way to get around this is to wrap each of your cases in another if statement to check if the database has that many rows
if (MySqlTestScript.sharedInstance().longNumbers.Length > 1) {
if (tag.Epc.ToString().Trim() == MySqlTestScript.sharedInstance().longNumbers[0].ToString()) {
if(MySqlTestScript.sharedInstance().userNames[0] != null)
txt1.text = "Hello "+ MySqlTestScript.sharedInstance().userNames[0];
}
}
}
else {
txt1.text = "";
}
This will avoid your index out of range exception. In each wrapping case you will need to increment the comparison to be 1, 2, 3, etc.
It is not the prettiest solution but will avoid the exception. Another option would be to wrap in a try-catch block. Again not the prettiest but would accomplish the task.
When writing some unit tests for our application, I stumbled upon some weird behaviour in EF6 (tested with 6.1 and 6.1.2): apparently it is impossible to repeatedly create and delete databases (same name/same connection string) within the same application context.
Test setup:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
class AMap : EntityTypeConfiguration<A>
{
public AMap()
{
HasKey(a => a.Id);
Property(a => a.Name).IsRequired().IsMaxLength().HasColumnName("Name");
Property(a => a.Id).HasColumnName("ID");
}
}
public class SomeContext : DbContext
{
public SomeContext(DbConnection connection, bool ownsConnection) : base(connection, ownsConnection)
{
}
public DbSet<A> As { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new AMap());
}
}
[TestFixture]
public class BasicTest
{
private readonly HashSet<string> m_databases = new HashSet<string>();
#region SetUp/TearDown
[TestFixtureSetUp]
public void SetUp()
{
System.Data.Entity.Database.SetInitializer(
new CreateDatabaseIfNotExists<SomeContext>());
}
[TestFixtureTearDown]
public void TearDown()
{
foreach (var database in m_databases)
{
if (!string.IsNullOrWhiteSpace(database))
DeleteDatabase(database);
}
}
#endregion
[Test]
public void RepeatedCreateDeleteSameName()
{
var dbName = Guid.NewGuid().ToString();
m_databases.Add(dbName);
for (int i = 0; i < 2; i++)
{
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
Console.WriteLine();
}
[Test]
public void RepeatedCreateDeleteDifferentName()
{
for (int i = 0; i < 2; i++)
{
var dbName = Guid.NewGuid().ToString();
if (m_databases.Add(dbName))
{
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
}
Console.WriteLine();
}
[Test]
public void RepeatedCreateDeleteReuseName()
{
var testDatabases = new HashSet<string>();
for (int i = 0; i < 3; i++)
{
var dbName = Guid.NewGuid().ToString();
if (m_databases.Add(dbName))
{
testDatabases.Add(dbName);
Assert.IsTrue(CreateDatabase(dbName), "failed to create database");
Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database");
}
}
var repeatName = testDatabases.OrderBy(n => n).FirstOrDefault();
Assert.IsTrue(CreateDatabase(repeatName), "failed to create database");
Assert.IsTrue(DeleteDatabase(repeatName), "failed to delete database");
Console.WriteLine();
}
#region Helpers
private static bool CreateDatabase(string databaseName)
{
Console.Write("creating database '" + databaseName + "'...");
using (var connection = CreateConnection(CreateConnectionString(databaseName)))
{
using (var context = new SomeContext(connection, false))
{
var a = context.As.ToList(); // CompatibleWithModel must not be the first call
var result = context.Database.CompatibleWithModel(false);
Console.WriteLine(result ? "DONE" : "FAIL");
return result;
}
}
}
private static bool DeleteDatabase(string databaseName)
{
using (var connection = CreateConnection(CreateConnectionString(databaseName)))
{
if (System.Data.Entity.Database.Exists(connection))
{
Console.Write("deleting database '" + databaseName + "'...");
var result = System.Data.Entity.Database.Delete(connection);
Console.WriteLine(result ? "DONE" : "FAIL");
return result;
}
return true;
}
}
private static DbConnection CreateConnection(string connectionString)
{
return new SqlConnection(connectionString);
}
private static string CreateConnectionString(string databaseName)
{
var builder = new SqlConnectionStringBuilder
{
DataSource = "server",
InitialCatalog = databaseName,
IntegratedSecurity = false,
MultipleActiveResultSets = false,
PersistSecurityInfo = true,
UserID = "username",
Password = "password"
};
return builder.ConnectionString;
}
#endregion
}
RepeatedCreateDeleteDifferentName completes successfully, the other two fail. According to this, you cannot create a database with the same name, already used once before. When trying to create the database for the second time, the test (and application) throws a SqlException, noting a failed login. Is this a bug in Entity Framework or is this behaviour intentional (with what explanation)?
I tested this on a Ms SqlServer 2012 and Express 2014, not yet on Oracle.
By the way: EF seems to have a problem with CompatibleWithModel being the very first call to the database.
Update:
Submitted an issue on the EF bug tracker (link)
Database initializers only run once per context per AppDomain. So if you delete the database at some arbitrary point they aren't going to automatically re-run and recreate the database. You can use DbContext.Database.Initialize(force: true) to force the initializer to run again.
A few days ago I wrote integration tests that included DB access through EF6. For this, I had to create and drop a LocalDB database on each test case, and it worked for me.
I didn't use EF6 database initializer feature, but rather executed a DROP/CREATE DATABASE script, with the help of this post - I copied the example here:
using (var conn = new SqlConnection(#"Data Source=(LocalDb)\v11.0;Initial Catalog=Master;Integrated Security=True"))
{
conn.Open();
var cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = string.Format(#"
IF EXISTS(SELECT * FROM sys.databases WHERE name='{0}')
BEGIN
ALTER DATABASE [{0}]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE
DROP DATABASE [{0}]
END
DECLARE #FILENAME AS VARCHAR(255)
SET #FILENAME = CONVERT(VARCHAR(255), SERVERPROPERTY('instancedefaultdatapath')) + '{0}';
EXEC ('CREATE DATABASE [{0}] ON PRIMARY
(NAME = [{0}],
FILENAME =''' + #FILENAME + ''',
SIZE = 25MB,
MAXSIZE = 50MB,
FILEGROWTH = 5MB )')",
databaseName);
cmd.ExecuteNonQuery();
}
The following code was responsible for creating database objects according to the model:
var script = objectContext.CreateDatabaseScript();
using ( var command = connection.CreateCommand() )
{
command.CommandType = CommandType.Text;
command.CommandText = script;
connection.Open();
command.ExecuteNonQuery();
}
There was no need to change database name between the tests.
I have a code a base that iterates through a list of records and does the follwing
'select * from Table to see if fields exist
then later in the interation
'select * from Table to retreive some data
then farther down in the interation
select field1, field2 from Table to get certain pieces of information
I need to do all of these functions for each record. Would the process speed up if I called the query once for each record and hold the data in a datatable and retreive what I need from there? Or is there another more efficient way to not have to make 3 db calls to the same table which will speed up the process?
You can cache query data to System.Data.DataTable. To simplify things I has written CMyDynaset class, which populates DataTable with data from DB. Here how to use it for example with MySQL:
using System;
using System.Data.Common;
using MySql.Data.MySqlClient;
namesapce MyProg
{
class Program
{
private const string strMyConnection = "Host=localhost;Database=mydb;User Id=myuser;Password=mypsw";
static void Main(string[] args)
{
using (MySqlConnection myConnection = new MySqlConnection(strMyConnection))
{
using (MyDb.CMyDynaset dyn = new MyDb.CMyDynaset(myConnection, MySqlClientFactory.Instance))
{
// populate dyn.Table (System.Data.DataTable)
dyn.Init("select * from Table");
dyn.Load();
// access fields
foreach (DataColumn column in dyn.Table.Columns)
{
// ...
}
// get data
long nCountAll = dyn.Table.Rows.Count; // rows count
foreach (DataRow row in dyn.Table.Rows)
{
Object val1 = row[1]; // acess by index
Object val2 = row["id"]; // acess by name
// ...
}
// update data
dyn.Table.Rows[0]["name"] = "ABC";
dyn.Update();
}
}
}
}
}
CMyDynaset class (CMyDynaset.cs):
// CMyDynaset.cs
using System;
using System.Data.Common;
namespace MyDb
{
/// <summary>
/// Summary description for CMyDynaset.
/// </summary>
public class CMyDynaset : IDisposable
{
public System.Data.DataTable Table = null;
// private
private DbConnection myConnection = null;
private DbProviderFactory myFactory = null;
private DbDataAdapter dataAdap = null;
private DbCommandBuilder cmdBld = null;
private bool bIsSchema = false;
public CMyDynaset(DbConnection conn, DbProviderFactory factory)
{
this.myConnection = conn;
this.myFactory = factory;
}
#region IDisposable Members
public void Dispose()
{
if (this.Table != null)
{
this.Table.Dispose();
this.Table = null;
}
if (this.cmdBld != null)
{
this.cmdBld.Dispose();
this.cmdBld = null;
}
if (this.dataAdap != null)
{
this.dataAdap.Dispose();
this.dataAdap = null;
}
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
#endregion
// Init
public void Init(string strSelect)
{
DbCommand cmdSel = this.myConnection.CreateCommand();
cmdSel.CommandText = strSelect;
this.dataAdap = this.myFactory.CreateDataAdapter();
this.dataAdap.SelectCommand = cmdSel;
this.cmdBld = this.myFactory.CreateCommandBuilder();
this.cmdBld.DataAdapter = this.dataAdap;
this.Table = new System.Data.DataTable();
// schema
this.bIsSchema = false;
}
public void AddParameter(string name, object value)
{
DbParameter param = this.dataAdap.SelectCommand.CreateParameter();
param.ParameterName = name;
param.Value = value;
this.dataAdap.SelectCommand.Parameters.Add(param);
}
public void AddParameter(DbParameter param)
{
this.dataAdap.SelectCommand.Parameters.Add(param);
}
// Open, Close
private void Open(ref bool bClose)
{
if (this.myConnection.State == System.Data.ConnectionState.Closed)
{
this.myConnection.Open();
bClose = true;
}
if (!this.bIsSchema)
{ // schema
this.dataAdap.FillSchema(this.Table, System.Data.SchemaType.Mapped);
this.bIsSchema = true;
}
}
private void Close(bool bClose)
{
if (bClose)
this.myConnection.Close();
}
// Load, Update
public void Load()
{
bool bClose = false;
try
{
this.Table.Clear();
this.Open(ref bClose);
this.dataAdap.Fill(this.Table);
}
catch (System.Exception ex)
{
throw ex;
}
finally
{
this.Close(bClose);
}
}
public void Update()
{
bool bClose = false;
try
{
this.Open(ref bClose);
this.dataAdap.Update(this.Table);
}
catch (System.Exception ex)
{
throw ex;
}
finally
{
this.Close(bClose);
}
}
}
}
According to MSDN documentation a LINQ query is not executed before iterated over in a foreach loop.
But when I try the following:
namespace MCSD487_AdoConnection
{
class Program
{
static void Main(string[] args)
{
DataSet dataSet = new DataSet();
dataSet.Locale = CultureInfo.InvariantCulture;
FillDataSet(dataSet);
DataTable folders = dataSet.Tables["Folder"];
IEnumerable<DataRow> folderQuery = folders.AsEnumerable();
IEnumerable<DataRow> aFolders = folderQuery.Where(f => f.Field<string>("Name")[0].ToString().ToLower() == "a");
// this is where I thought the SQL execution whould happen
foreach (DataRow row in aFolders)
{
Console.WriteLine("{0} was created on {1}", row.Field<string>("Name"), row.Field<DateTime>("DateTime"));
}
Console.ReadLine();
}
internal static void FillDataSet(DataSet dataSet)
{
try
{
string connectionString = ConfigurationManager.ConnectionStrings["conn"].ConnectionString;
SqlDataAdapter dataAdapter = new SqlDataAdapter("SELECT DateTime, Name FROM Folder", connectionString);
// Add table mappings.
dataAdapter.TableMappings.Add("Table", "Folder");
dataAdapter.Fill(dataSet);
// Fill the DataSet.
// This it where the actual SQL executes
dataAdapter.Fill(dataSet);
}
catch (SqlException ex)
{
Console.WriteLine("SQL exception occurred: " + ex.Message);
}
}
}
}
and I look at my SQL Server Profiler, I can see that the actual SQL call is performed when I call the dataAdapter.Fill(dataSet) in the FillDataSet method and not when iterating through rows.
My question is: How do I make LINQ perform the SQL execution on only the names starting with 'a' (without specifying that in the SQL commandText in the FillDataSet method)?
EDIT 2013-07-07 23:44:
I ended with the following solution, based on Evan Harpers answer:
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;
namespace MCSD487_AdoConnection
{
[Table(Name = "Folder")]
public class Folder
{
private int _Id;
[Column(IsPrimaryKey = true, Storage = "_Id")]
public int Id
{
get { return _Id; }
set { _Id = value; }
}
private DateTime _DateTime;
[Column(Storage = "_DateTime")]
public DateTime DateTime
{
get { return _DateTime; }
set { _DateTime = value; }
}
private string _Name;
[Column(Storage = "_Name")]
public string Name
{
get { return _Name; }
set { _Name = value; }
}
}
class Program
{
static void Main(string[] args)
{
DataContext db = new DataContext(new SqlConnection(#"Data Source=OLF\OLF;Initial Catalog=DirStructure;Integrated Security=True"));
Table<Folder> folders = db.GetTable<Folder>();
IQueryable<Folder> folderQuery = from folder in folders
where folder.Name[0].ToString().ToLower() == "a"
select folder;
foreach (Folder folder in folderQuery)
{
Console.WriteLine("{0} was created on {1}", folder.Name, folder.DateTime);
}
Console.ReadLine();
}
}
}
Thanks for guiding me in the right direction.
This is exactly the expected behavior. You are using ADO.NET to get the data into a DataTable, and then querying against the DataTable – not the underlying database – in line with ADO.NET's disconnected architecture.
If you want to see your LINQ queries turned into optimized database calls just-in-time, use something like LINQ to SQL.
With DataSet you can'T do it. The only way is to fill the DataSet/Table adapter with the limited results (the starts with stuff have to be written in SQL).