pulling a non-trivial object from a MySQL database - c#

I have a C# .NET 3.5 project using a MySQL database.
I have an object Task which I would like to be able to create by pulling it from a series of database tables.
public class Task
{
public Task()
{
Values = new List<string>();
OtherValues = new List<string>();
Requirement = string.Empty;
Minimum = 1;
Children = new List<Foo>();
}
public IList<string> Values { get; set; }
public IList<string> OtherValues { get; set; }
public string Requirement { get; set; }
public int Minimum { get; set; }
public int Maximum { get; set; }
public IList<Foo> Children { get; set; }
}
I'd like to be able to get the tasks from a TaskList which would lazily read elements of the task as they were accessed by an enumerator.
public class TaskList : IEnumerable<Task>
{
/* ... */
public IEnumerator<Task> GetEnumerator()
{
string query = #"SELECT my_task.*, `Order` FROM my_task ORDER BY `Order` DESC";
using (MySqlConnection connection = new MySqlConnection(connection_string_))
using (MySqlCommand command = connection.CreateCommand())
{
command.CommandText = query;
connection.Open();
using (MySqlDataReader reader = command.ExecuteReader())
{
yeild /* ??? */
}
}
}
}
How is this done?

You can serialize it to XML and store it as a string. Add the following function to Task:
public XElement Serialize()
{
return new XElement("Task",
new XElement("Values",from val in Values select new XElement("Item",val)),
new XElement("OtherValues",from val in OtherValues select new XElement("Item",val)),
new XElement("Requirement",Requirement),
new XElement("Minimum",Minimum),
new XElement("Maximum",Maximum)
);
}
You will need to put using System.Linq; and using System.Xml.Linq; in the top of the .cs file.
I didn't write the code to serialize Children because I don't know what the data type Foo looks like, but you should serialize it in a similar manner. After you've done that, you can easily write the XML to the database, and read it back(write a constructor that parses the Xml into a Task object)
EDIT(addition):
Here is an example to a constructors that receives XML(or parse a string as XML):
public Task(string xmlSourceAsString):
this(XElement.Parse(xmlSourceAsString))
{
}
public Task(XElement xmlSource)
{
Values=(from itm in xmlSource.Element("Values").Elements("Item") select itm.Value).ToList();
OtherValues=(from itm in xmlSource.Element("OtherValues").Elements("Item") select itm.Value).ToList();
Requirement=xmlSource.Element("Requirement").Value;
Minimum=int.Parse(xmlSource.Element("Minimum").Value);
Maximum=int.Parse(xmlSource.Element("Maximum").Value);
}
EDIT(explanation):
You can't store your object as is in the database "as is", because it refers to other objects. For example - the list Values doesn't sit in the same place in memory as the rest of the object, befause it's a ref type - it refers to another object that sits in a different place in the memory. In matter of fact, the only parts of your object that are stored in the same place as the main object are the Minimum and Maximum, which are ref types, so if you could somehow store the object as is(laziest solution possible, if it worked), you would get your Minimum and Maximum fields right, but all other fields will point to the memory addresses where those objects where placed when you stored the Task object, which are now most likely invalid pointers(and I say "most likely" because it is also possible(though rare) that they will point to legitimate objects, maybe event of the same type - but they still won't have your data.
If you want the object with all it's data stored in a database(or in a file. or passed to a proccess that runs on another computer via network) you have to serialize it. Performance-wise the best way is to serialize it to binary(C# have some tools for that, but it's still more complex than XML).
Xml also have the adventage of being easily readable from most modern programming languages and database engines. MySQL has some functions to read and write XML, so you can update the object in the database and access it's fields from MySQL queries.
Conclusion
You asked for a solution that is easy(lazy), efficient, and sql-compatible(access to the object's fields from MySQL queries). I say you can only have two of your three requirements, but you can choose which two:
If you want something easy and efficient, even at the price of loosing compatibility, serialize your objects to binary. True, it's not as easy as XML, but .NET has some tools to help you with that.
If you want something efficient and compatible, and willing to do some work for that, you can put your object in MySQL the way databases are meant to be used - use separate tables for the lists that refers to the objects via OIDs, etc. This will require some work, but after you add the tables and code the MySQL functions and the C# functions that handle everything, you should be able to store, retrieve, and access your objects with ease.
If you want something easy and compatible, and you can afford loosing some efficiency, use my solution and serialize your objects to XML. This is the laziest solution - unless someone knows a library that can automatically serialize any object, LINQ to XML is the easiest way to do it, and requires much less code than any other solution.

Related

Dapper multilevel nesting

I am trying to use Dapper in my project to speed up data loading (currently using EF6)
Here is my SQL
String SQL = #"select vwArtikli_Grid_V2.ArtikalID
,vwArtikli_Grid_V2.ArtikalNaziv
,Artikli_TagLista.ArtikalTagListaID
,Artikli_TagLista.ArtikalTagID
,Artikli_Stanje.ArtikalStanjeID
,Artikli_Stanje.ObjekatID
,Artikli_Stanje.Stanje
,Artikli_Tagovi.GrupaID
,Artikli_Tagovi.ArtikalTagGrupaID
,Artikli_Tagovi.ArtikalTagNaziv
,Artikli_Tagovi.ArtikalTagPrint
,Artikli_Tagovi.ArtikalTagSlika
,Artikli_Tagovi.ArtikalTagID
,vwArtikli_Grid_V2.ArtikalID
from Artikli_Tagovi
inner join Artikli_TagLista on Artikli_Tagovi.ArtikalTagID = Artikli_TagLista.ArtikalTagID
right outer join vwArtikli_Grid_V2 on Artikli_TagLista.ArtikalID = vwArtikli_Grid_V2.ArtikalID
left outer join Artikli_Stanje on vwArtikli_Grid_V2.ArtikalID = Artikli_Stanje.ArtikalID;
I am using my Entity Framework Entities as POCOs and they are
VwArtikliGridV2, Artikli_TagLista, Artikli_Tagovi, Artikli_Stanje
VwArtikliGridV2 has two properties
public virtual ICollection<Artikli_TagLista> Artikli_TagLista { get; set; }
public virtual ICollection<Artikli_Stanje> Artikli_Stanje { get; set; }
and Artikli_TagLista has
public virtual Artikli_Tagovi Artikli_Tagovi { get; set; }
What is the easiest way to execute the query and map my data to the POCOs or Entities ?
I tried
Dapper.Mapper
var Artikli = cn.Query<VwArtikliGridV2, Artikli_TagLista, Artikli_Stanje, Artikli_Tagovi> (SQL);
but it didnot work
I also tried Slapper.AutoMapper
List<dynamic> ArtikliUM = cn.Query<dynamic>(SQL).ToList();
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(Artikli_Tagovi), new List<string> { "ArtikalTagID" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(Artikli_TagLista), new List<string> { "ArtikalTagListaID" });
Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(Artikli_Stanje), new List<string> { "ArtikalStanjeID" });
Artikli = (Slapper.AutoMapper.MapDynamic<VwArtikliGridV2>(ArtikliUM) as IEnumerable<VwArtikliGridV2>).ToList();
But it also did not work.
I can map VwArtikliGridV2 but i cant map any of the nested objects. They are always null.
What can I try ?
Dapper maps things flat. At first this feels like a big pain because EF nests things so easily. But once you get over the initial pain you realize how simple, predictable, and performance-oriented it is. Maybe an extra 15 min. of query-crafting for untouchable performance.
I answered a similar question here return a list of data via stored proc to dapper.
You should be able to return multiple datasets (one for the parent item, one for the underlying tags, etc.) and combine them in your app tier.
As BlackjacketMack said in his answer, I think multiple data sets are the way to go.
You might want to look in the Dapper documentation at the features Multi Mapping to split single rows into multiple objects, and QueryMultiple to read multiple result sets from a single query.
Obviously, it would mean modifying your query to return multiple result sets, but could achieve what you are looking for.
Examples can be found here: https://github.com/StackExchange/Dapper/blob/master/Dapper.Tests/MultiMapTests.cs
https://github.com/StackExchange/Dapper/blob/master/Dapper.Tests/QueryMultipleTests.cs
In particular, you might want to take a look at method public void TestMultiMapThreeTypesWithGridReader() in MultiMapTests.cs. I found that very useful to help me understand a similar problem of parent with child collections, where the child contains different object types returned in a single row.

Can't cast Int to Decimal with EF Core?

I am trying to solve an issue I have with pulling large ints (22+ digits) into ASP.Net with Entity Framework Core from a MySQL database.
EF Core does not support BigInteger and the suggestions I received where to use decimal instead. However, when using decimal types on my entities, I always receive the following exception when trying to select from the DB:
System.InvalidCastException: Unable to cast object of type
'System.Int32' to type 'System.Decimal'
In the Database the columns are INT(25) and in my models the type is decimal, here is an example model:
[Table("alliance_reputation_rankings")]
public class AllianceReputationRank
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[Column("date")]
public DateTime Date { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[Column("world")]
public int World { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[Column("alliance")]
public string Alliance { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[Column("rank")]
public int Rank { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[Column("reputation")]
public decimal Reputation { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[Key]
[Column("entry_id")]
public int EntryId { get; set; }
}
I cannot select the Reputation property using EF Core. Even if I try and use (decimal) before the property to cast it:
Rough example of a select:
_context.AllianceReputationRankings
.Where(p => p.Date == rank.Date && p.Alliance== rank.Alliance && p.World == rank.World)
.Select(pl => new AllianceReputationRank
{
Date = pl.Date,
World = pl.World,
Alliance = pl.Alliance,
Reputation = (decimal)pl.Reputation
}
).FirstOrDefault();
How can I use decimals in my models to bring in large ints from the database? If I cannot use decimals, how can I use large numbers with EF Core?
Entity Framework expects a very tight type-constraint between the database and model, it really doesn't like to see a numeric column with a decimal property. I've outlined several options here, each with it's own benefits and drawbacks. Feel free to use whichever one is the best for you.
Since you're using MySQL, the first option I'm outlining is that you could alter the column type from INT(25) to DECIMAL(25, 0). Then you should be able to use decimal in Entity Framework for that as much as you want.
If you can't do that, then, sadly, you're in a very tight corner. Entity Framework Core just isn't the right tool for this job, it's not mature enough. In comments you clarified that you are using this numeric column for math in the DB, which means string and VARCHAR(25) are out of the playing book, unless you can take that math out of the DB.
This solution relies on the assumption that this entire model is read-only. If it is (and you don't need to update the database from Entity Framework Core) then you can build a VIEW in MySQL that casts the INT(25) column to a VARCHAR(25), and do something like the following:
[NotMapped]
public BigInteger ReputationValue { get; set; }
public string Reputation
{
get
{
return ReputationValue.ToString();
}
set
{
ReputationValue = BigInteger.Parse(value);
}
}
The problem is that you can't really update the database through a VIEW, so if you wanted to update these records (anything to do with this entire model, basically) you would need to write manual SQL, or build a stored procedure. This is just a limitation of Entity Framework Core that can't really be gotten around easily.
Lastly, the final option is to use a stored procedure / method for reading and writing. You could then pass a WHERE clause to it (if you want to take the challenge of building a C#-style conversion, go for it, otherwise just add a WHERE sqlConditionCode ... string that you pass to the method to filter things by.
Then build a second stored procedure / method to do the updates. You could call dbContext.Update(model) which would pass everything to the stored procedure to do the update, and dbContext.Get("WHERE EntryId = #EntryId", new List<SqlParamter> { new SqlParamter("#EntryId", ...) }) I'm sure you get the idea by this point.
Something like:
public IEnumerable<AllianceReputationRank> GetAllianceReputationRanks(string whereClause, IEnumerable<MySqlParameter> parameters)
{
// Get a connection string from somewhere
var connectionString = string.Empty;
using (var connection = new MySqlConnection(connectionString))
using (var command = new MySqlCommand("SELECT * FROM alliance_reputation_rankings " + (whereClause ?? string.Empty), connection))
{
connection.Open();
foreach (var parameter in parameters)
{
command.Parameters.Add(parameter);
}
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
// Build a list or use `yield return`, if building a list instance here
var result = new List<AllianceReputationRank>();
while (reader.Read())
{
// Build a model of `AllianceReputationRank` here
var model = new AllianceReputationRank();
// Use reflection or just add each property manually
model.Date = reader.GetDate("date");
// ...
// Make sure you read `reputation` as a string, then `BigInteger.Parse` it to your model
model.Reputation = BigInteger.Parse(reader.GetString("reputation"));
// Either add the model to the list or `yield return model`
result.Add(model);
}
// If you built a list then `return` it
return result;
}
}
}
}
Building the opposite method is, well, just the opposite. The ?? string.Empty might be superfluous, I don't have an IDE in front of me to check if string + null will throw an exception, but you can remove it if you don't like it. (Better safe than sorry here, in my opinion.) I really hope all my types and usage is correct, if not, I apologize for any modifications needed other than adding the connection string and additional properties.
Honestly, the DECIMAL(25, 0) option should work for you, if that doesn't then the stored procedure / method option should. Both should keep your math in the DB and hopefully not break anything else while also fixing the Entity Framework Core issues at the same time. Is it less than ideal? Absolutely, and I wish it weren't what was necessary. But, unfortunately, Entity Framework Core is very new and still requires a lot of updates just to add simple functionality that Entity Framework not-Core has. (Like the lack of a .Find method in Entity Framework Core, for example.)
I wish we had better news, but without the ability to build our own mappable-types (I.e. build our own BigInteger that is supported by Entity Framework Core) there just isn't a lot to be done with this problem, we're stuck to nasty work-arounds to make it do what we need to.
What I did that seemed to work is to multiply by 1m:
context.AllianceReputationRankings
.Where(p => p.Date == rank.Date && p.Alliance== rank.Alliance && p.World == rank.World)
.Select(pl => new AllianceReputationRank
{
Date = pl.Date,
World = pl.World,
Alliance = pl.Alliance,
Reputation = pl.Reputation * 1m
}
).FirstOrDefault();
This seems to allow decimal operations while not taking performance to the floor.

c# Linq to Sql dynamic Data Context assignment

`Hi,
Can somebody please give me a pointer on this? I have 8 servers each with 8 databases which look identical exept server/database name. We are talking thousands of tables.
I create my data contexts with sqlmetal.exe
After creating my data contexts, I import them into the application and then I run comparison scripts over the databases to compare results.
My problem is dynamically switching between data contexts.
Datacontext.DAL.DUK1 duk1sdi = new Datacontext.DAL.DUK1(connectionString);
Datacontext.DAL.DUK3 duk3sdi = new Datacontext.DAL.DUK3(connectionString);
string fromOne = runQuery(duk1sdi);
string fromThree = runQuery(duk3sdi);
public static string runQuery(DataContext duk)
{
var query =
from result in duk.TableA
select result.Total;
string returnString = query;
return returnString;
}
I have no problem with the query running when the duk is predefined, however how do I define and pass the datacontext to the function?
The error I get is:
Error 1 'System.Data.Linq.DataContext' does not contain a definition
for 'TableA' and no extension method 'TableA' accepting a first
argument of type 'System.Data.Linq.DataContext' could be found (are
you missing a using directive or an assembly reference?)
You could use the GetTable<T> method, where T is the type of the table, e.g. TableA.
public static string runQuery(DataContext duk) {
var table = duk.GetTable<TableA>();
var query = from result in table select result.Total;
...
}
However, all types of TableA will need to be the same type, strictly (I'm pretty sure).
Otherwise you would need to literally branch the logic for the handling of each context. Since you can extend your DataContext instances (in general, maybe not in your specific case) then you could have them share an interface that exposes a collection property of TableA, but you would need a higher level context wrapper to pass around then - unless you pass around the collection by altering the method signature.
You can use interfaces. Check this answer, but be sure to script the interfaces using a .tt file with the amount of tables you have.
Edit:
If you have generated contexts which you want to use interchangeably in a reusable method, you have the problem that the generated TableA classes are not reusable, since they are different types (even though the names may match, but that doesn't make them equal). Therefore you need to abstract the actual types, and one way to do this, is to use interfaces. You build your reusable method around an interface which abstracts the specific context-type and table-type. The downside is that you have to implement the interfaces on the generated contexts and tabletypes. This though is something you can solve using a .tt script.
Pseudo code:
// Define interface for table
public interface ITableA {
// ... properties
}
// Define interface for context
public interface IMyContext {
IQueryable<ITableA> TableA { get; }
}
// Extend TableA from DUK1
public partial class TableA: ITableA {
}
// Extend DUK1
public partial class Datacontext.DAL.DUK1: IMyContext {
IQueryable<ITableA> IMyContext.TableA {
get { return TableA; }
}
}
// Same for DUK3 and TableA FROM DUK3
// Finally, your code
Datacontext.DAL.DUK1 duk1sdi = new Datacontext.DAL.DUK1(connectionString);
Datacontext.DAL.DUK3 duk3sdi = new Datacontext.DAL.DUK3(connectionString);
string fromOne = runQuery(duk1sdi);
string fromThree = runQuery(duk3sdi);
public static string runQuery(IMyContext duk) {
// Note: method accepts interface, not specific context type
var query = from result in duk.TableA
select result.Total;
string returnString = query;
return returnString;
}
If your schema is identical between databases, why script the dbml for all of them? Just create one context with it's associated classes and dynamically switch out the connection string when instantiating the context.
var duk1sdi = new Datacontext.DAL.DUK1(connectionString1);
var duk3sdi = new Datacontext.DAL.DUK1(connectionString2);
Thanks, guys, I think I found the simplist solution for me based a bit of both your answers and by RTFM (Programming Microsoft Linq in Microsoft .NET Framework 4 by Paulo Pialorsi and Marco Russo)
In this way I don't have to use the large DBML files. It is a shame because I'm going to have to create hundreds of tables in this way, but I can now switch between connection strings on the fly.
First I create the table structure. (outside the program code block)
[Table(Name = "TableA")]
public class TableA
{
[Column] public int result;
}
Then I define the table for use:
Table<TableA> TableA = dc.GetTable<TableA>();
And then I can query from it:
var query =
from result in TableA
select TableA.result;

Temp Table like thing in c#

I have a scenario where I need to use tabular data which is unlikely to change but which helps implement business logic:
Table
Parent Child Relationship Allowed
A B C True
B A C False
Each row represent a business rule. Based on this table I need to populate a dropdown control with the contents of the "Relationship" column. Can I have some sort of data structure within C# to store this tabular data, or do I need to use a a database table?
Unless you need to persist this data across sessions, you don't need to store it in a database table. From the perspective of Object Oriented design, why not make an object, and therefore class, that represents the structure you need. Something like this:
public class Relationship
{
public string Name { get; set; }
public string Parent { get; set; }
public string Child { get; set; }
public bool Allowed { get; set; }
}
Note of course that I'm using strings where you might want to use further objects of their own 'type'. Additionally, keep in mind access, and what should and shouldn't be allowed to access these properties... This is example is intentionally simple at this point!
I would agree that a full blown database may be overkill if this is all the data you need to store, so you could theoretically hardcode the data and structure in C# if you really wanted to, but not a good idea in almost all cases - even if it is unlikely to change.
At a minimum store the data in a little XML / config file so that IF it does change, you do not need to recompile the application.
Use DataTable: http://msdn.microsoft.com/en-us/library/System.Data.DataTable.aspx
You can create your Table and fill it like this:
// Definition
DataTable table = new DataTable();
DataColumn column = new DataColumn("Parent", typeof(string));
table.Columns.Add(column);
// Data
DataRow row = table.NewRow();
row["Parent"] = "A";
table.Rows.Add(row);

How to use a c# method in a sql stored procedure

I wanted to use linq as so:
MyDBEntities context = new MyDBEntities();
context.MyTable.Where(i => MyMethod(i.column, valueToTest).ToList();
with
public bool MyMethod(Object a, Object b)
but apparently using such a method with isn't possible
so I was hopping I could use the methode in a stored procedure I would be able to call with linq
do you think is it possible ?
Generally it is possible to create C# function and use it in SQL Server (2005 and newer) but it is not so simple - you must use SQL CLR which means separate project for your function, special references, special types, etc. At last you must deploy the assembly to SQL server to be able to use the function in SQL. General documentation also covering how to create custom function:
Creating SQL Server Objects in Managed Code
Once you have your function on SQL server you can use it within stored procedure and you can use it within query. I'm actually not sure if you can import these functions into Linq-to-sql or EF model and use them in Linq-to-sql or Linq-to-entities queries.
Take a look here for a complete sample:
Calling custom methods in LINQ-to-SQL
I hope I understand you correctly.
Let's say that MyTable is a database table that contains the columns Name, and Address
Here's how you would get a value back whether the results contain the specified value you passed.
public void SomeMethod()
{
MyTable table= new MyTable();
bool b= MyMethod(table.Name, "Fred");
if(b)
//Do something
else
//Do something else
}
public bool MyMethod(MyTable a, object value)
{
using(var context= new MyDBEntities())
{
return context.MyTable.Where(i => a == value).Any();
}
}
This is what the database table 'MyTable' looks like behind the scenes.(the data context generated this)
public class MyTable
{
public string Name { get; set; }
public string Address { get; set; }
}
So you can see in the first method I pass table.Name to MyMethod, that's only possible because MyTable has a pubic property called Name. Also notice that we are using type Object for the value, as the parameter could an int, a string, a date time, who knows.
Note: This is untested code, but should get off to right track if I understand you correctly.

Categories

Resources