Mapping TimeSpan in SQLite and Dapper - c#

I'm attempting to use Dapper to interface to an existing database format that has a table with a duration encoded as ticks in a BIGINT column. How do I tell Dapper to map my POCO's TimeSpan-typed property to ticks when inserting into and reading from the database?
I've tried to set the type map for TimeSpan to DbType.Int64:
SqlMapper.AddTypeMap(typeof(TimeSpan), DbType.Int64);
And I've also created an ITypeHandler, but the SetValue method is never called:
public class TimeSpanToTicksHandler : SqlMapper.TypeHandler<TimeSpan>
{
public override TimeSpan Parse(object value)
{
return new TimeSpan((long)value);
}
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
{
parameter.Value = value.Ticks;
}
}
Here's my POCO:
public class Task
{
public TimeSpan Duration { get; set; }
// etc.
}
When executing a simple insert statement like this:
string sql = "INSERT INTO Tasks (Duration) values (#Duration);";
And passing the POCO as the object to insert:
Task task = new Task { Duration = TimeSpan.FromSeconds(20) };
connection.Execute(sql, task);
I get this exception:
System.InvalidCastException : Unable to cast object of type 'System.TimeSpan' to type 'System.IConvertible'.
at System.Convert.ToInt64(Object value, IFormatProvider provider)
at System.Data.SQLite.SQLiteStatement.BindParameter(Int32 index, SQLiteParameter param)
at System.Data.SQLite.SQLiteStatement.BindParameters()
at System.Data.SQLite.SQLiteCommand.BuildNextCommand()
at System.Data.SQLite.SQLiteCommand.GetStatement(Int32 index)
at System.Data.SQLite.SQLiteDataReader.NextResult()
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery()
at Dapper.SqlMapper.ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action`2 paramReader) in SqlMapper.cs: line 3310
at Dapper.SqlMapper.ExecuteImpl(IDbConnection cnn, ref CommandDefinition command) in SqlMapper.cs: line 1310
at Dapper.SqlMapper.Execute(IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Nullable`1 commandTimeout, Nullable`1 commandType) in SqlMapper.cs: line 1185
If I leave the TimeSpan type mapping as-is (it defaults to DbType.Time), it writes the string version of the TimeSpan, i.e. `00:00:20.000", which isn't helpful as it does not match the format of the other data in the column.

Could you do the following instead?
public class Task
{
public TimeSpan Duration { get; set; }
public long Ticks
{
get { return Duration.Ticks; }
set { Duration = new TimeSpan(value); }
}
// etc.
}
string sql = "INSERT INTO Tasks (Duration) values (#Ticks);";

Solutions for LinqToDB:
MappingSchema.SetDataType(typeof(TimeSpan), DataType.NText);
Or:
MappingSchema.SetDataType(typeof(TimeSpan), DataType.Int64);
Example:
public class Program
{
private const string ConnectionString = "Data Source=:memory:;Version=3;New=True;";
public static void Main()
{
var dataProvider = new SQLiteDataProvider();
var connection = dataProvider.CreateConnection(ConnectionString);
connection.Open();
var dataConnection = new DataConnection(dataProvider, connection);
dataConnection.MappingSchema.SetDataType(typeof(TimeSpan), DataType.Int64);
dataConnection.CreateTable<Category>();
dataConnection.GetTable<Category>()
.DataContextInfo
.DataContext
.Insert(new Category
{
Id = 2,
Time = new TimeSpan(10, 0, 0)
});
foreach (var category in dataConnection.GetTable<Category>())
{
Console.WriteLine($#"Id: {category.Id}, Time: {category.Time}");
}
}
private class Category
{
public int Id { get; set; }
public TimeSpan Time { get; set; }
}
}

Related

How to use InsertMany properly Mongodb C#

Hi I wont to pass some synthetic data to my database with the method InsertMany i have write the flowing code:
My Main:
static void Main(string[] args)
{
MongoCRUD db = new MongoCRUD("testClass");
List<GlobalUrbanPoint> syntheticData = CreateSunfeticData(20);
db.InsertMultipleRecords<GlobalUrbanPoint>("geo3", syntheticData);
}
My model class:
public class GlobalUrbanPoint
{
[BsonId]
public ObjectId Id{ get; set; }
public string NAME { get; set; }
}
The function for the synthetic data:
public static List<GlobalUrbanPoint> CreateSunfeticData(int NumberOfDocumet)
{
List<GlobalUrbanPoint> SyntheticList = new List<GlobalUrbanPoint>();
var SyntheticObject = new GlobalUrbanPoint();
for (var i = 1; i < NumberOfDocumet; i++)
{
SyntheticObject.NAME = (i+1).ToString();
SyntheticList.Add(SyntheticObject);
}
return SyntheticList;
}
And for my operation i use MongoCRUD
public class MongoCRUD
{
private IMongoDatabase db;
public MongoCRUD(string database)
{
var client = new MongoClient();
db = client.GetDatabase(database);
}
public void InsertRecord<T>(string table, T record)
{
var collection = db.GetCollection<T>(table);
collection.InsertOne(record);
}
public void InsertMultipleRecords<T>(string table, List<T> records)
{
var collection = db.GetCollection<T>(table);
collection.InsertMany(records);
}
}
When i run the code i get an error E11000 duplicate key error collection. I check the definition of InsertMany and it takes for arguments IEnumerable<TDocument> documents. It is an easy way to convert List<T> to IEnumerable<TDocument>?
What i need to change my synthetic function or my InsertMultipleRecords funtion. Any sugestion?
Thank you for your time.
After some digging i found that the object that i create in synthetic function has the same _id. For that i needed to change the creation of the object, inside of for loop. And my problem it was not in InsertMultipleRecords.
public static List<GlobalUrbanPoint> CreateSunfeticData(int NumberOfDocumet)
{
List<GlobalUrbanPoint> SyntheticList = new List<GlobalUrbanPoint>();
for (var i = 1; i < NumberOfDocumet; i++)
{
var SyntheticObject = new GlobalUrbanPoint();
SyntheticObject.NAME = (i+1).ToString();
SyntheticList.Add(SyntheticObject);
}
return SyntheticList;
}

Pass properties from array to a new class

i am working with a .net application where i have a web service that returns values in array form and now this array values i want to pass to a class and also as a reference to a private object. But since i am fresh new in programming i do not know how where an with what logic to start.
This is the private obj i created and i want to pass those references where CT is the array type and clsIn is the info that comes from another class but i have no idea how to pass neither of them.
private object TotInfo(clsIn In, CT ct)
{
TotInfo objFromCD = new TotInfo();
return objFromCD;
}
And here is the new class i have created that where i want to pass all the values from clsIn and CT:
public class TotInfo
{
// Object properties
private string LAST_OFFER;
private string LAST_OFFER_DATE;
private string CLOSING_REASON;
private string _NO;
private string _STATUS;
#region "GET/SET Property"
public string NO
{
get { return _NO; }
set { _NO = value; }
}
public string LAST_OFFER
{
get { return _LAST_OFFER; }
set { _LAST_OFFER = value; }
}
public string LAST_OFFER_DATE
{
get { return _LAST_OFFER_DATE; }
set { _LAST_OFFER_DATE = value; }
}
public string CLOSING_REASON
{
get { return _CLOSING_REASON; }
set { _CLOSING_REASON = value; }
}
public string STATUS
{
get { return _STATUS; }
set { _STATUS = value; }
}
#endregion
#region "Costruttori"
public CardsTotInfo() { }
public CardsTotInfo(string No, string lastOffer, string lastOfferDate, string closingReason, string status)
{
this.NO = No;
this.LAST_OFFER = lastOffer.ToUpper();
this.LAST_OFFER_DATE = lastOfferDate.ToUpper();
this.CLOSING_REASON = closingReason.ToUpper();
this.STATUS = status.ToUpper();
}
}
I have passed, or better say i think i have passed in the correct way the values of clsIn but i do not know how to pass the properties of the array type CT[].
I really need help.
Thank you in advance.
If CT is an object array and the data you get from the web service always comes in the same order, for instance using an arbitrary example:
object[] CT = { 1, DateTime.Now, "foo", true }
If you know that each property data inside the array will always be at the same index (you will always have a int in index 0 representing an Id, and a DateTime on index 1 representing the last offer day and so on)
I would say you need to set each property "manually":
private object TotInfo(clsIn In, CT ct)
{
TotInfo objFromCD = new TotInfo();
//get data from DB
//set the data from the array into the class properties
objFromCD.Id = (int)ct[0];
objFromCD.LastOfferDate = (DateTime)ct[1];
objFromCD.ClosingReason = (string)ct[2];
objFromCD.Available = (bool)ct[3];
return objFromCD;
}

How to return a List in c#

Below I have a method I am trying to create. The overall objective of the method is to create a list, open a database in SQL, and convert that information to a string. The problem though I am having is returning all the data. I understand for a method to work you need a return statement of some sort, however when I try to return the list, it keeps telling me
Cannot implicitly convert type 'System.Collections.Generic.List(Namespace).MyHome' to '(namespace).MyHome
So my question to you all is, can I return the list, or will I have to create another variable to return, or am I trying to return the wrong thing entirely?
Below is the method I am working on. For explanation, this method is using a class I created that holds the credentials for the data I am trying to get.
private static MyHome GetUserDataFromMyHome(string username)
{
List<MyHome> myHomeInformation = new List<MyHome>();
using (SqlConnection connection = new SqlConnection(Properties.Settings.Default.MyHomeConnectionString))
{
SqlCommand command = connection.CreateCommand();
command.CommandText = #"SELECT USER_NAME, EMAIL, FIRST_NAME, LAST_NAME, TRAVELER_UID FROM DATA_BASE";
connection.Open();
SqlDataReader reader = sqlError.ExecuteReader();
while (reader.Read())
{
MyHome userInformation = new MyHome();
foreach (MyHome item in myHomeInformation)
{
userInformation.myHomeUserName = Utilities.FromDBValue<string>(reader["USER_NAME"]);
userInformation.myHomeEmail = Utilities.FromDBValue<string>(reader["EMAIL"]);
userInformation.myHomeFirstName = Utilities.FromDBValue<string>(reader["FIRST_NAME"]);
userInformation.myHomeLastName = Utilities.FromDBValue<string>(reader["LAST_NAME"]);
userInformation.myHomeTravelerUID = Utilities.FromDBValue<string>(reader["TRAVELER_UID"]);
myHomeInformation.Add(userInformation);
}
}
}
return myHomeInformation;
}
Here is the class that holds the credentials:
class MyHome : IEnumerable<MyHome>
{
public string myHomeUserName { get; set; }
public string myHomeEmail { get; set; }
public string myHomeFirstName { get; set; }
public string myHomeLastName { get; set; }
public string myHomeTravelerUID { get; set; }
}
Replace
private static MyHome GetUserDataFromMyHome(string username)
with
private static List<MyHome> GetUserDataFromMyHome(string username)
the return type should be a list of objects and not just a object
A List<MyHome> is not a MyHome. If you want your function to return a List<MyHome>, declare it as such.
You have to declare the return type of the function to be a List of MyHome.
private static List<MyHome> GetUserDataFromMyHome(string username)
Otherwise whatever is calling this is expecting to get one instance of MyHome.

can we declare an object of another class as the data member

My code is like this...
class Transaction
{
public class Date
{
public int day, month, year;
}
Date d;
double amount;
long acc_no;
string action;
}
how can we access the Date d.
how can we access the Date d
Same way you access any class-level member of any object.
From within any instance of the class Transaction, you would access it as a class-level member:
this.d
From outside an instance of Transaction you can't access d (nor should you) because it's a private member. (C# members are private by default unless declared otherwise.)
From within an instance of Date there's no guarantee that you're in the context of an instance of Transaction so there's no direct access to that member. (Just because it's a nested class doesn't guarantee that it will always be used in that structure.) Not that it would really matter anyway since d is an instance of Date so if you're in Date then it would only need to access itself anyway.
Like mentioned in the comment, this is generally not a good idea, but you can access it. You can access it from anywhere in class Transaction, but not outside it, unless you use a method or property.
class Transaction
{
public class Date
{
public int day, month, year;
}
Date d;
double amount;
long acc_no;
string action;
public Date GetDate()
{
return d; // Access Date d by using a method
}
}
namespace banking
{
public class Transaction
{
public class Date
{
public int day, month, year;
}
public Date date = new Date();
public double amount;
public long acc_no;
public string action;
}
class Program
{
static void Main(string[] args)
{
List<Transaction> transaction = new List<Transaction>();
StreamReader sr = new StreamReader("transaction.csv");
string data = sr.ReadLine();
while (data != null)
{
string[] dataarray = data.Split(',');
string[] date_split = dataarray[0].Split('-');
Transaction tran_obj = new Transaction();
tran_obj.date.day = int.Parse(date_split[0]);
tran_obj.date.month = int.Parse(date_split[1]);
tran_obj.date.year = int.Parse(date_split[2]);
tran_obj.acc_no = long.Parse(dataarray[1]);
tran_obj.amount = double.Parse(dataarray[2]);
tran_obj.action = dataarray[3];
transaction.Add(tran_obj);
data = sr.ReadLine();
}
Console.WriteLine("Please enter the account number for which you are looking for");
long new_acc_no = long.Parse(Console.ReadLine());
foreach (Transaction t in transaction)
{
if (t.acc_no == new_acc_no)
{
Console.WriteLine(t.amount);
Console.WriteLine(t.date);
Console.WriteLine(t.action);
}
}
string s = Console.ReadLine();
}
string s = Console.ReadLine();
}
}
the problem with this code is that in output console it is not showing the date
i have stored the date like this 12-02-1994

Is there a better way to map Objects from a database query to an object?

I would like to know if there is a better way to solve this problem that I am overlooking. (I'm looking for a second opinion)
I want to create a generic and easy way to bind objects to database reader queries using "Oracle.DataAccess.Client".
In order to do this I initially wanted to create an object which inherited from OracleCommand; however, OracleCommand is a sealed object.
To deal with this I decided to create an extension method which attempts to map objects to generic columns in the database for each row.
EDIT : In my scenario, I know what the database will look like; however, I will not know where the database is before run time. i.e. The database may have been transferred ahead of time and the end user will specify the credentials for the database at run time.
Here is the implementation:
public static T[] Bind<T>(this OracleCommand oe, Binding binding, CommandBehavior Behavior = CommandBehavior.Default)
{
List<T> ret = new List<T>();
using (var reader = oe.ExecuteReader(Behavior))
{
while (reader.Read())
{
T unknownObj = (T)Activator.CreateInstance(typeof(T));
for (int i = 0; i < binding.GetBindCount(); i++)
{
var propinfo = unknownObj.GetType().GetProperties().ToList();
var prop = propinfo.Find((p) => p.Name == binding.GetBindValue(i, true));
prop.SetValue(unknownObj, reader[binding.GetBindValue(i, false)]);
}
ret.Add(unknownObj);
}
}
return ret.ToArray();
}
}
public class Binding
{
List<BindingMap> _map = new List<BindingMap>();
public void AddBind(String VariableName, String ColumnName)
{
_map.Add(new BindingMap(VariableName, ColumnName));
}
public String GetBindValue(int index, bool IsVariable = true)
{
var a = _map.ToArray();
return (IsVariable) ? a[index].Variable : a[index].Column;
}
public int GetBindCount()
{
return _map.Count;
}
}
public class BindingMap
{
public String Column;
public String Variable;
public BindingMap(String v, String c)
{
Variable = v;
Column = c;
}
}
Is there a better way to do this that I've overlooked, or is this a sound?
The way it would be used in real code is like this :
static void Main()
{
Binding b = new Binding();
b.AddBind("CreatedBy", "Create_by");
using (var Conn = new OracleConnection())
{
Conn.ConnectionString = od.Options.GetConnectionString();
using (var Command = new OracleCommand())
{
Command.Connection = Conn;
Command.CommandText = "Select * From Accounts";
Conn.Open();
var a = Command.Bind<Account>(b);
foreach (Account e in a)
{
Console.WriteLine(e.CreatedBy);
}
}
}
Console.Read();
}
public class Account
{
public String CreatedBy
{
get;
set;
}
}
As a slightly better way, you could designate the bound property like Telerik does: with a Linq expression. Here is the usage. Instead of :
AddBind("CreatedBy", "Created_by");
You would write
AddBind( x => x.CreatedBy, "Created_by");
You get a slightly stronger typing opportunity. The signature of AddBind would be:
public void AddBind<T>(Expression<Func<Account, T>> variable, string columnName) {
// ...
}
But I would not go into the way of generic functions. I'd rather overload a non-generic function :
public void AddBind(Expression<Func<Account, double>> variable, string columnName) {
// Add binding for double
}
public void AddBind(Expression<Func<Account, DateTime>> variable, string columnName) {
// Add binding for DateTime
}
// ...
The type of binding would then be selected according to the type of your mapped object. This prevents you from misnaming your properties, so you keep the possibility of performing name changes in the Account class without breaking your bindings.
The column name has still to be a string, sorry.
Of course, the way then to generalize is to make your BindingMap generic. (Taking your business class as a type parameter)
class BindingMap<BusinessClass> {
// ....
public void AddBind(Expression<Func<BusinessClass, double>> variable, string columnName) {
// Add binding for double
}
public void AddBind(Expression<Func<BusinessClass, DateTime>> variable, string columnName) {
// Add binding for DateTime
}
// ...
};
I leave as an exercice to you the problem of digging the property descriptor out of the expression :)

Categories

Resources