EF stored procedure with dynamic number of columns in result set - sql-server

At first I should note that I'm new in ASP.NET (however, I have some experience with C#) and Entity Framework. I work on a school project and there is quite complicated database containing energy consumption data. Those I need (I import, E export, reactive power C and L) are stored in one column as binary compressed so to get them out I have to use a stored procedure which calls some methods in custom assembly to decompress the column and restore the data.
The stored procedure has 4 arguments:
#identify int,
#startTime datetime,
#endTime datetime,
#args nvarchar(60) -- "Selector"
The selector is rather special, it's an argument where you specify what you want in the result set, e.g. 'i' for Import column only, 'i,e' for Import and Export. They designed it like this because it's faster if you need just one column rather then all columns (because of the way the compressed data are stored).
So, I've created an ADO.NET Entity Model called EnergyConsumptionDBModel, imported the stored procedure and created complex type EnergyConsumptionResult for the stored procedure return type as follows:
public partial class EnergyConsumptionResult
{
public System.DateTime Time { get; set; }
public double I { get; set; }
public double E { get; set; }
public double L { get; set; }
public double C { get; set; }
}
The column Time is always present in the result set, but the rest depends on the #args argument of the stored procedure. For example if I pick 'i,e,c,l' as an argument, it will return columns Time, I, E, C, L and everything is just fine but if I pick for example 'i' it returns Time, I which gives me an exception:
The data reader is incompatible with the specified 'EnergyConsumptionDBModel.EnergyConsumptionResult'.
A member of the type, 'E', does not have a corresponding column in the data reader with the same name.
So the question is, is there some simple way to solve this? Some kind of dynamic result mapping on complex type or is it much more simple to tell my colleague who designed the stored procedure to make it return all columns whatever is in #args but leave the un-used columns empty which is a solution my project leader may not like. Thanks for any help.

I don't think it is possible to automatically map resultsets with variable number of columns in EF. You specify the mapping at design time and EF relies on that. What you could do would be returning all columns but set the columns for which you don't return data to null. You may try using executing the stored procedure directly and use Translate method on ObjectContext (http://msdn.microsoft.com/en-us/library/dd466384.aspx) for materialization but I think this method will also expect columns in your reader that correspond to property names.

Related

EF 6 - Stored Procedure Duplicate Result Column

I'm in the process of migrating from a legacy system. The database cannot be modified - including adding/modifying stored procedures.
I've added a stored procedure to an EDMX model successfully, it generated the following code:
public virtual ObjectResult<sp_GetUserInfoByUID_Result> sp_GetUserInfoByUID(Nullable<System.Guid> sessionID, Nullable<System.Guid> userUID)
{
var sessionIDParameter = sessionID.HasValue ?
new ObjectParameter("SessionID", sessionID) :
new ObjectParameter("SessionID", typeof(System.Guid));
var userUIDParameter = userUID.HasValue ?
new ObjectParameter("userUID", userUID) :
new ObjectParameter("userUID", typeof(System.Guid));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<sp_GetUserInfoByUID_Result>("sp_GetUserInfoByUID", sessionIDParameter, userUIDParameter);
}
However, I get the following runtime error:
The data reader is incompatible with the specified 'MyApp.Repository.sp_GetUserInfoByUID_Result'. A member of the type, 'useraccount_uid1', does not have a corresponding column in the data reader with the same name.
So it looks like EF generated two mappings: useraccount_uid and useraccount_uid1. This is because the stored procedure returns a table with two columns named useraccount_uid.
Is there a way to get round this in the EF model?
Turns out the solution was really simple, I'd just overlooked how EF modeled stored procedures. When you add a stored procedure to the Model, by default it actually adds a couple of references.
A mapping from the model to the basic function in the DB - you cannot edit these mappings.
A "Function Import" - this is the part which maps result sets to code models.
So all I had to was look for the Function Imports folder in the EDMX Model Browser. In here the stored procedure was listed. If you right-click on the function you'll see the "Function Import Mapping" option. This will open the Mappings Detail window. Here I could simply correct the column naming.

Adding a row to a table from the properties of a class

I have a class that represents the table of a db-row. Its properties are the columns of the table. I add a new row to the table with the following code:
Public Sub AddRow(oTestRow As TestRow)
Dim sql As String
With oTestRow
sql = String.Format("INSERT INTO TestTable " &
"(ArtNr, ArtName, ArtName2, IsVal, CLenght) " &
"Values ('{0}', '{1}', '{2}', {3}, {4})",
.ArtNr, .ArtName, .ArtName2, .IsVal, .CLenght)
End With
Using oConn As New OleDbConnection(m_ConnString)
oConn.Open()
Using oInsertCmd As New OleDbCommand(sql, oConn)
oInsertCmd.ExecuteNonQuery()
End Using
End Using
End Sub
That is just an example, but my classes have around 30-40 properties and this brings a very large and complex sql string.
Creating, editing or maintaining these sql strings for many classes could generate errors.
I am wondering if any compact way or method exists in order to add the whole object's istance (the properties of course) to the table "TestTable" without writing such a large sql string.
I created the TestRow in the way that its properties are exactly the columns of the table "TestTable" (with the same name). But I did not found in the ADO.NET anything that could be used.
If changing DB system is an option, you may wanna take a look at some document based no sql solution like MongoDB, CouchDB or especially for .Net RavenDB, db4o or Eloquera.
Here is a list of some of them.
for starters anything with inline queries is a bad practice (unless the need demands for e.g. you have tables defined in the db, and dont have access to the db to deploy procedures)
you have few options - for e.g. instead of handwriting the classes , use Entitiy framework a better alternative to Linq2Sql
if you want to stick with the tags in this question I would design this making the most of OO concepts. (this is a rough sketch, but I hope this helps)
public class dbObject
protected <type> ID --- This is important. if this has value, commit will assume update, otherwise an update will be performed
public property DBTableName // set the table name
public property CommitStoredprocedure // the procedure on the database that can do commit work
public property SelectStoredProcedure // the procedure used to retrieve the i
public dbObject construcor (connection string or dbcontext etc)
set dbConnection here
end constructor
public method commit
reflect on this.properties available and prepare your commit string.
if you are using storedproc ensure that you prepare named parameters and that the stored proc is defined with the same property names as your class property names. also ensure that storedproc will update if there is an ID value or insert and return a ID when the id value is not available
Create ADO.net command and execute. (this is said easy here but you need to perfect this method)
End method
end class
public class employee inherits dbObject
// employee properties here
public string name;
end employee
public class another inherits dbObject
//another properties
public bool isValid;
end department
usage:
employee e = new employee;
e.name = "John Smith";
e.commit();
console.WriteLine(e.id); // will be the id set by the commit method from the db
If you make baseclass correct (well tested) here, this is automated and you shouldnt see errors.
you will need to extend the base class to Retrieve records from the db based on an id (if you want to instantiate objects from db)

store strings of arbitrary length in Postgresql

I have a Spring application which uses JPA (Hibernate) initially created with Spring Roo. I need to store Strings with arbitrary length, so for that reason I've annotated the field with #Lob:
public class MyEntity{
#NotNull
#Size(min = 2)
#Lob
private String message;
...
}
The application works ok in localhost but I've deployed it to an external server and it a problem with encoding has appeared. For that reason I'd like to check if the data stored in the PostgreSQL database is ok or not. The application creates/updates the tables automatically. And for that field (message) it has created a column of type:
text NOT NULL
The problem is that after storing data if I browse the table or just do a SELECT of that column I can't see the text but numbers. Those numbers seems to be identifiers to "somewhere" where that information is stored.
Can anyone tell me exactly what are these identifiers and if there is any way of being able to see the stored data in a #Lob columm from a pgAdmin or a select clause?
Is there any better way to store Strings of arbitrary length in JPA?
Thanks.
I would recommend skipping the '#Lob' annotation and use columnDefinition like this:
#Column(columnDefinition="TEXT")
see if that helps viewing the data while browsing the database itself.
Use the #LOB definition, it is correct. The table is storing an OID to the catalogs -> postegreSQL-> tables -> pg_largeobject table.
The binary data is stored here efficiently and JPA will correctly get the data out and store it for you with this as an implementation detail.
Old question, but here is what I found when I encountered this:
http://www.solewing.org/blog/2015/08/hibernate-postgresql-and-lob-string/
Relevant parts below.
#Entity
#Table(name = "note")
#Access(AccessType.FIELD)
class NoteEntity {
#Id
private Long id;
#Lob
#Column(name = "note_text")
private String noteText;
public NoteEntity() { }
public NoteEntity(String noteText) { this.noteText = noteText }
}
The Hibernate PostgreSQL9Dialect stores #Lob String attribute values by explicitly creating a large object instance, and then storing the UID of the object in the column associated with attribute.
Obviously, the text of our notes isn’t really in the column. So where is it? The answer is that Hibernate explicitly created a large object for each note, and stored the UID of the object in the column. If we use some PostgreSQL large object functions, we can retrieve the text itself.
Use this to query:
SELECT id,
convert_from(loread(
lo_open(note_text::int, x'40000'::int), x'40000'::int), 'UTF-8')
AS note_text
FROM note

Select from table using XML column

I am creating a task-scheduler on SQL Server 2008.
I have a table that I use to store tasks. Each task is a task name (e.g. ImportFile) and arguments. I store arguments in XML column, since different tasks have different signatures.
Table is as follows:
Id:integer(PK) | operation:nvarchar | Arguments:xml
Before queuing a task, I often need to verify that given task hasn't been scheduled yet. The lookup is done based on both operation and args.
Question: Using Linq-to-Sql how can I check if given operation+args is present in the queue already?
I am looking for something like:
var isTaskScheduled = db.Tasks.Any(t =>
t.Opearation == task.Operation &&
t.Arguments == task.ArgumentsAsXElement);
(which doesn't work because SQL Server can't compare XML type)
Any alternative implementation suggestions?
You might want to surface e.g. a string property that encapsultes your Arguments, or maybe it would be sufficient to have e.g. the length and a CRC of your Arguments as extra properties on your class:
public partial class Task
{
public int ArgumentLength
{ .... }
public int ArgumentCRC
{ .... }
}
That way, if you can compare length (of your XML) and the CRC and they match, you can be pretty sure and safe to assume the two XML's are identical. Your check would then be something like:
var isTaskScheduled =
db.Tasks.Any(t => t.Operation == task.Operation &&
t.ArgumentLength == task.ArgumentLength &&
t.ArgumentCRC == task.ArgumentCRC);
or something like that.
This may be a stretch, but you could use a "Hashcode" when saving the data to the database, then query on the hashcode value at a later date / time.
This assumes that you have a class that represents your task entity and that you have overridden the GetHashCode method of said class.
Now, when you go to query the database to see if the task is in the scheduled queue, you simply query on the hashcode, thus avoiding the need to do any xml poking at query time.
var t1 = new Task{Operation="Run", Arguments="someXElement.value"};
var t2 = new Task{Operation="Run", Arguments="someXElement.value"};
in the code above t1 == t2 because you are overriding GetHashCode and computing the hash for Operation+Arguments.Value. if you store the hashcode in the db, then you can easily tell if you have an object in the DB that equals the hash code that you are checking for.
This may be similar to what marc_s was talking about.
You can write a class which implements IComparable:
public class XMLArgument : IComparable
{
public XMLArgument(string argument)
{
}
public int CompareTo(object obj)
{
...
}
}
var isTaskScheduled = db.Tasks.Any(t =>
t.Opearation == task.Operation &&
(new XMLArgument(t.Arguments)).CompareTo(new XMLArgument(task.ArgumentsAsXElement)) == 0);

SqlBulkCopy unable to Parse "0", "1" bool values into BIT on database table

I am using my custom CSVDataReader : IDataReader {} to insert Bulk values in a Database table.
Every datatype but the Bit (from "1"/"0") is parsed perfectly. I am getting the following error
" value of type String from the data source cannot be converted to type bit" while parsing 0 or 1 as bool
If I change these values to "true"/"false". It is taken without any problem.
I can't alter the CSV file. Currently I replace that specific column from "0"/"1" to "false"/"True" during Iteration. But this is not an elegant solution.
Please help !
Thanks
Panks
I guess you're sending "1" and "0" rather then 1 and 0
FYI, SQL Server will accept true and false for bit
I had a similar issue where an IEnumerable of anonymous types was failing to populate my database table. It turned out that the order of the fields in my DataTable did not match the order of the columns in the database table. I do not at this point understand why the order of the columns should matter, since the column names could be used to match them correctly.
In any case, my column mismatch was a side effect of using the FastMember NuGet package to quickly populate the DataTable, which sorts the columns alphabetically under the hood! The solution was to replace my anonymous type with a small declared class that used the Ordinal attribute on its properties:
public class Row
{
[Ordinal(0)] public string ColumnA { get; set; }
[Ordinal(1)] public string SecondColumn { get; set; }
[Ordinal(2)] public string ColumnC { get; set; }
}
You can read more about this ordering issue here, where user dyatchenko created a pull request to support custom property ordering. This was eventually merged back into master.

Resources