I am using OrmLite for MySql (from nuget) and have some objects that I'm persisting that will result in the contents being serialized and blobbed. What I'm finding is that the schema for these fields is defaulting to varchar(255), which works for small blobs only. Even relatively small lists are too large for 255 characters.
What is the best approach to ensure the blob tables are sized correctly when using OrmLite?
Example:
public class Foo : IHasId<long>
{
[AutoIncrement]
public long Id { get; set; }
public Dictionary<string, string> TestSize { get; set; }
}
The approach I'm taking now is to annotate with [StringLength(6000)] for each blobbed field. While this works, I'm not sure if there is a better way to ensure enough space.
Below is a full unit test that illustrates the sizing issue:
using NUnit.Framework;
using ServiceStack.DataAnnotations;
using ServiceStack.DesignPatterns.Model;
using ServiceStack.OrmLite;
using ServiceStack.OrmLite.MySql;
using System;
using System.Collections.Generic;
using System.Configuration;
namespace OrmLiteTestNamespace
{
[TestFixture]
public class BlobItemTest
{
[Test]
public void TableFieldSizeTest()
{
var dbFactory = new OrmLiteConnectionFactory(
ConfigurationManager.AppSettings["mysqlTestConn"],
MySqlDialectProvider.Instance);
using (var db = dbFactory.OpenDbConnection())
{
db.CreateTableIfNotExists<Foo>();
var foo1 = new Foo()
{
TestSize = new Dictionary<string, string>()
};
// fill the dictionary with 300 things
for (var i = 0; i < 300; i++)
{
foo1.TestSize.Add(i.ToString(), Guid.NewGuid().ToString());
}
// throws MySql exception "Data too long for column 'TestSize' at row 1"
db.Insert(foo1);
}
}
}
public class Foo : IHasId<long>
{
[AutoIncrement]
public long Id { get; set; }
public Dictionary<string, string> TestSize { get; set; }
}
}
The Varchar datatype is a variable sized datatype whose space is only determined by the the contents of the field, not the size of the column definition. E.g. In MySQL it only takes up the size of the contents + 2 bytes for the length (up to 65535 length).
Related
I like to keep my data models clean (and not dependent on any Servicestack DLLs) by defining any attributes just in the database layer. However since upgrading to ver 5.0, my application fails to correctly recognise attributes set in c# using AddAttributes().
The code below shows a minimal reproducable example.
using ServiceStack;
using ServiceStack.DataAnnotations;
using ServiceStack.OrmLite;
namespace OrmliteAttributeTest
{
class Program
{
static void Main(string[] args)
{
var type = typeof(DataItem2);
type.AddAttributes(new AliasAttribute("DataItem2Table"));
var prop = type.GetProperty(nameof(DataItem2.ItemKey));
if (prop != null)
prop.AddAttributes(new PrimaryKeyAttribute());
prop = type.GetProperty(nameof(DataItem2.ItemDescription));
if (prop != null)
prop.AddAttributes(new StringLengthAttribute(100));
SqlServerDialect.Provider.GetStringConverter().UseUnicode = true;
var connectionString = #"Data Source=localhost\sqlexpress; Initial Catalog=OrmLiteTest; Integrated Security=True;";
var connectionFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider);
using (var db = connectionFactory.OpenDbConnection())
{
db.CreateTableIfNotExists<DataItem>();
db.CreateTableIfNotExists<DataItem2>();
}
}
}
[Alias("DataItemTable")]
public class DataItem
{
[PrimaryKey]
public int ItemKey { get; set; }
[StringLength(100)]
public string ItemDescription { get; set; }
}
public class DataItem2
{
public int ItemKey { get; set; }
public string ItemDescription { get; set; }
}
}
The table for DataItem is created correctly using the attributes as specified. The table for DataItem2 fails to use any of the attibubes defined in the code.
The issue is that the static constructor of JsConfig.InitStatics() needs to be initialized once on Startup which reinitializes the static configuration (and dynamic attributes added) in ServiceStack Serializers.
This is implicitly called in ServiceStack libraries like OrmLiteConnectionFactory which because it hasn't been called before will reinitialize that ServiceStack.Text static configuration. To avoid resetting the dynamic attributes you can initialize the OrmLiteConnectionFactory before adding the attributes:
var connectionFactory = new OrmLiteConnectionFactory(connStr, SqlServerDialect.Provider);
var type = typeof(DataItem2);
type.AddAttributes(new AliasAttribute("DataItem2Table"));
var prop = type.GetProperty(nameof(DataItem2.ItemKey));
if (prop != null)
prop.AddAttributes(new PrimaryKeyAttribute());
prop = type.GetProperty(nameof(DataItem2.ItemDescription));
if (prop != null)
prop.AddAttributes(new StringLengthAttribute(100));
SqlServerDialect.Provider.GetStringConverter().UseUnicode = true;
Or if preferred you can explicitly call InitStatics() before adding any attributes, e.g:
JsConfig.InitStatics();
var type = typeof(DataItem2);
type.AddAttributes(new AliasAttribute("DataItem2Table"));
//...
What is the best way to handle the queries[search] and sorts[Driver1] parameters in the following querystring in asp.net?
?queries[search]=greenock&id=20&sorts[Driver1]=1
I tried using this model but only id was bound:
public class ICRSRequestModel
{
public int ID { get; set; }
public ICollection<string> Sorts { get; set; }
public ICollection<string> Queries { get; set; }
}
I don't have the option of changing the requesting application unfortunately, and the string contained inside [] could be any unknown value.
If the property is
public ICollection<string> Queries { get; set; }
Then the query string would need to be
?queries[0]=greenock
you would need to change the property to
public Dictionary<string, string> Queries { get; set; }
so that the query string could be
?queries[search]=greenock
The key will be "search" and the value will be "greenock"
Note this will only work for a single queries value. ?queries[search]=greenock?queries[anotherKey]=anotherValue will not work
Sorry, not got 50 rep points yet else I would have commented with this probably useless comment, but here goes..
I'm sure there's a better way using MVC bindings but if all else fails it might be worth taking the QueryString from the Server.Request object and splitting the string up to extract the information you want. You can get them in to a keyvaluepair collection using the code I had lying around in a project, I'm sure it can be manipulated for your needs.
Dictionary<string, string> dictionary = new Dictionary<string, string>();
foreach (string part in queryString.Split(new char[] { '&' }))
{
if (!string.IsNullOrEmpty(part))
{
string[] strArray = part.Split(new char[] { '=' });
if (strArray.Length == 2)
{
dictionary[strArray[0]] = strArray[1];
}
else
{
dictionary[part] = null;
}
}
}
I have a solution that i need to call a console app from asp.net and need to pass variables. one variable is a generic list of a certain class.
I have tried passing it but I got error that i cannot convert a generic list to a string which is correct.
I am not sure if there is another way to pass this.
I know webservice can solve this issue. But it there any other options?
Is this possible to do or only string are possible to pass
Here is the generic list sample.
List<person> personList = new List<person>();
person p = new person();
p.name = "test";
p.age = 12;
p.birthdate = 01/01/2014
personList.add(p)
Thanks.
Ok, Console application accepts only strings. This is defined in the Main method as
static void Main(string[] args)
Since you have a complex object list it'll be bit hard to pass this information to the console application (but not impossible). There are several options for you.
Pass your values as comma separated values as a string as long as this string is not too long.
Web Services or a Web API as you suggested.
Serialize your object to an XML file and then deserialize in your console application.
Write and read from a persistent data store
UPDATE
Sample Code for Option 3 (Write to an XML file)
I wrote this sample code out of curiosity. Hope this helps to solve your issue.
ASP.Net Website
I have a button in my web page (Default.aspx) and in it's click event it writes the Person collection/ List to an XML file. Here's the code behind.
using System;
using System.IO;
using System.Xml.Serialization;
namespace WriteToConsole
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnWriteToConsole_Click(object sender, EventArgs e)
{
PersonCollection personList = new PersonCollection();
// Person 1
Person p = new Person();
p.Name = "test 1";
p.Age = 12;
p.BirthDate = DateTime.Parse("01/01/2014");
personList.Add(p);
// Person 2
Person p2 = new Person();
p2.Name = "test 2";
p2.Age = 25;
p2.BirthDate = DateTime.Parse("01/01/2014");
personList.Add(p2);
XmlSerializer serializer = new XmlSerializer(personList.GetType());
StreamWriter file = new StreamWriter(#"D:\temp\PersonCollection.xml");
serializer.Serialize(file, personList);
file.Close();
}
}
}
And, the Person.cs looks like this.
using System;
using System.Collections.Generic;
namespace WriteToConsole
{
[Serializable]
[System.Xml.Serialization.XmlRoot("PersonCollection")]
public class PersonCollection : List<Person> {
}
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime BirthDate { get; set; }
public Person()
{
this.Name = string.Empty;
this.Age = 0;
this.BirthDate = DateTime.MinValue;
}
}
}
Console Application
Then read the XML file in your console application and display the data in personList on the console.
using System;
using System.IO;
using System.Xml.Serialization;
namespace ReadInConsole
{
class Program
{
static void Main(string[] args)
{
XmlSerializer deserializer = new XmlSerializer(typeof(PersonCollection));
TextReader textReader = new StreamReader(#"D:\temp\PersonCollection.xml");
PersonCollection personList = new PersonCollection();
personList = (PersonCollection)deserializer.Deserialize(textReader);
textReader.Close();
if (personList != null && personList.Count > 0)
{
foreach (Person p in personList)
{
Console.WriteLine("Person name: {0}, Age: {1} and DOB: {2}", p.Name, p.Age, p.BirthDate.ToShortDateString());
}
Console.ReadLine();
}
}
}
}
In your console application you should have the same Person class as a modal (This is same as the Person class in your Web Application. Only the namespace is different).
using System;
using System.Collections.Generic;
namespace ReadInConsole
{
[Serializable]
[System.Xml.Serialization.XmlRoot("PersonCollection")]
public class PersonCollection : List<Person>
{
}
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime BirthDate { get; set; }
public Person()
{
this.Name = string.Empty;
this.Age = 0;
this.BirthDate = DateTime.MinValue;
}
}
}
Hope you understand the code.
I have a Sharp Architecture based app using Fluent NHibernate with Automapping. I have the following Enum:
public enum Topics
{
AdditionSubtraction = 1,
MultiplicationDivision = 2,
DecimalsFractions = 3
}
and the following Class:
public class Strategy : BaseEntity
{
public virtual string Name { get; set; }
public virtual Topics Topic { get; set; }
public virtual IList Items { get; set; }
}
If I create an instance of the class thusly:
Strategy s = new Strategy { Name = "Test", Topic = Topics.AdditionSubtraction };
it Saves correctly (thanks to this mapping convention:
public class EnumConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Apply(FluentNHibernate.Conventions.Instances.IPropertyInstance instance)
{
instance.CustomType(instance.Property.PropertyType);
}
public void Accept(FluentNHibernate.Conventions.AcceptanceCriteria.IAcceptanceCriteria criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum);
}
}
However, upon retrieval (when SQLite is my db) I get an error regarding an attempt to convert Int64 to Topics.
This works fine in SQL Server.
Any ideas for a workaround?
Thanks.
Actually, it is possible to map enums to INT, but in SQLite, INT will come back as an Int64, and, since you can't specify that your enum is castable to long, you will get this error. I am using NHibernate, so my workaround was to create a custom AliasToBean class that handles converting the enum fields to Int32:
public class AliasToBeanWithEnums<T> : IResultTransformer where T : new()
{
#region IResultTransformer Members
public IList TransformList(IList collection)
{
return collection;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
var t = new T();
Type type = typeof (T);
for (int i = 0; i < aliases.Length; i++)
{
string alias = aliases[i];
PropertyInfo prop = type.GetProperty(alias);
if (prop.PropertyType.IsEnum && tuple[i] is Int64)
{
prop.SetValue(t, Convert.ToInt32(tuple[i]), null);
continue;
}
prop.SetValue(t, tuple[i], null);
}
return t;
}
#endregion
}
Usage:
public IList<ItemDto> GetItemSummaries()
{
ISession session = NHibernateSession.Current;
IQuery query = session.GetNamedQuery("GetItemSummaries")
.SetResultTransformer(new AliasToBeanWithEnums<ItemDto>());
return query.List<ItemDto>();
}
By default, emums are automapped to strings for SQLite (don't know what happens with SQL Server).
Less efficient storage wise, obviously, but that might be a non-issue unless you have really huge data sets.
i'm creating an html.helper for a 3rd party javascript grid component. i'm passing my gridextension my viewmodel.
in my viewmodel class i've got custom attributes on my properties describing how each column is displayed.
in my gridextension, i want to then serialize my class of T. in my CreateSerializedRow method i'd like to be able to do something like row. <- and get intellisense for my class. but how to i get intellisense for the members of class T without an explicit cast?
public class GridData<T>
{
#region Fields
private List<Dictionary<string, object[]>> _attributes;
private static IList<T> _dataSource;
#endregion
#region Properties
public string Align { get; set; }
public string Header { get; set; }
public string JsonData { get; set; }
public string Sorting { get; set; }
public string Width { get; set; }
#endregion
#region Public Methods
public void Serialize(IList<T> dataSource, List<Dictionary<string, object[]>> attributes)
{
_dataSource = dataSource;
_attributes = attributes;
JsonData = _dataSource.Count == 0 ? string.Empty : BuildJson();
}
#endregion
#region Private Methods
private static string BuildJson()
{
var sbJson = new StringBuilder();
var listCount = _dataSource.Count;
sbJson.Append("{page: 1, total:" + listCount + ", rows: [");
for (var i = 0; i < listCount; i++)
{
var serialized = CreateSerializedRow(i);
sbJson.Append(serialized);
if (i < listCount - 1)
sbJson.Append(",");
}
sbJson.Append("]}");
return sbJson.ToString();
}
private static string CreateSerializedRow(int index)
{
var row = _dataSource[index];
var sb = new StringBuilder();
//sb.Append("{id:'" + Id + "',data:[");
//sb.Append(String.Format("'{0}',", GroupName.RemoveSpecialChars()));
//sb.Append(String.Format("'{0}',", Description));
//sb.Append(String.Format("'{0}',", CreatedBy));
//sb.Append(String.Format("'{0}',", CreatedDate.ToShortDateString()));
//sb.Append(String.Format("'{0}',", EmailSubject.RemoveSpecialChars()));
//sb.Append(String.Format("'{0}',", EmailBody));
//sb.Append(String.Format("'{0}',", UpdatedBy));
//sb.Append(String.Format("'{0}'", UpdatedDate.ToShortDateString()));
//sb.Append("]}");
return sb.ToString();
}
#endregion
}
The best you can do is use Generic constraints, which specify that T must be castable to a specific type. See http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx#csharp_generics_topic4 for more information.
The syntax is more or less the following:
public class MyClass<T> where T : ISomethingOrOther
{
}
At least this way, you can limit T to an interface type and code against that abstraction with Intellisense support.
With what you're trying to do you'd probably have to use reflection to cycle through the properties and output the values. You might be better off making GridData an abstract class and override a method which outputs the row of data for each data class.
Or create a generic interface which has a Serialize() method that outputs a string of the objects values in the expected format. Have each model class implement this interface and then have the GridData class constrained to this interface. Assuming these are ViewModel classes, it should be a reasonable design.
If T is always a known interface or class you can do:
public class GridData<T>
where T:MyClass
{
...
}