spring data jdbc use #query comments, How to match the parameters registered for entity - spring-el

#Modifying
#Query(value = "update orders o set o.status =#{#order.status} ")
Integer updateOrder(#Param("order") Order order);
#Modifying
#Query(value = "update orders o set o.status = #{#order.status}")
Integer updateOrder(#Param("order") Order order);
I want to get the parameters of the order by means of order entity then update.

This is part of SpEL support which wasn't implemented when the question was asked originally.
Support for it was added recently and will become available with 3.0.0-RC.
See https://github.com/spring-projects/spring-data-relational/issues/619.

Related

SQL CLR Trigger - get source table

I am creating a DB synchronization engine using SQL CLR Triggers in Microsoft SQL Server 2012. These triggers do not call a stored procedure or function (and thereby have access to the INSERTED and DELETED pseudo-tables but do not have access to the ##procid).
Differences here, for reference.
This "sync engine" uses mapping tables to determine what the table and field maps are for this sync job. In order to determine the target table and fields (from my mapping table) I need to get the source table name from the trigger itself. I have come across many answers on Stack Overflow and other sites that say that this isn't possible. But, I've found one website that provides a clue:
Potential Solution:
using (SqlConnection lConnection = new SqlConnection(#"context connection=true")) {
SqlCommand cmd = new SqlCommand("SELECT object_name(resource_associated_entity_id) FROM sys.dm_tran_locks WHERE request_session_id = ##spid and resource_type = 'OBJECT'", lConnection);
cmd.CommandType = CommandType.Text;
var obj = cmd.ExecuteScalar();
}
This does in fact return the correct table name.
Question:
My question is, how reliable is this potential solution? Is the ##spid actually limited to this single trigger execution? Or is it possible that other simultaneous triggers will overlap within this process id? Will it stand up to multiple executions of the same and/or different triggers within the database?
From these sites, it seems the process Id is in fact limited to the open connection, which doesn't overlap: here, here, and here.
Will this be a safe method to get my source table?
Why?
As I've noticed similar questions, but all without a valid answer for my specific situation (except that one). Most of the comments on those sites ask "Why?", and in order to preempt that, here is why:
This synchronization engine operates on a single DB and can push changes to target tables, transforming the data with user-defined transformations, automatic source-to-target type casting and parsing and can even use the CSharpCodeProvider to execute methods also stored in those mapping tables for transforming data. It is already built, quite robust and has good performance metrics for what we are doing. I'm now trying to build it out to allow for 1:n table changes (including extension tables requiring the same Id as the 'master' table) and am trying to "genericise" the code. Previously each trigger had a "target table" definition hard coded in it and I was using my mapping tables to determine the source. Now I'd like to get the source table and use my mapping tables to determine all the target tables. This is used in a medium-load environment and pushes changes to a "Change Order Book" which a separate server process picks up to finish the CRUD operation.
Edit
As mentioned in the comments, the query listed above is quite "iffy". It will often (after a SQL Server restart, for example) return system objects like syscolpars or sysidxstats. But, it seems that in the dm_tran_locks table there's always an associated resource_type of 'RID' (Row ID) with the same object_name. My current query which works reliably so far is the following (will update if this changes or doesn't work under high load testing):
select t1.ObjectName FROM (
SELECT object_name(resource_associated_entity_id) as ObjectName
FROM sys.dm_tran_locks WHERE resource_type = 'OBJECT' and request_session_id = ##spid
) t1 inner join (
SELECT OBJECT_NAME(partitions.OBJECT_ID) as ObjectName
FROM sys.dm_tran_locks
INNER JOIN sys.partitions ON partitions.hobt_id = dm_tran_locks.resource_associated_entity_id
WHERE resource_type = 'RID'
) t2 on t1.ObjectName = t2.ObjectName
If this is always the case, I'll have to find that out during testing.
How reliable is this potential solution?
While I do not have time to set up a test case to show it not working, I find this approach (even taking into account the query in the Edit section) "iffy" (i.e. not guaranteed to always be reliable).
The main concerns are:
cascading (whether recursive or not) Trigger executions
User (i.e. Explicit / Implicit) transactions
Sub-processes (i.e. EXEC and sp_executesql)
These scenarios allow for multiple objects to be locked, all at the same time.
Is the ##SPID actually limited to this single trigger execution? Or is it possible that other simultaneous triggers will overlap within this process id?
and (from a comment on the question):
I think I can join my query up with the sys.partitions and get a dm_trans_lock that has a type of 'RID' with an object name that will match up to the one in my original query.
And here is why it shouldn't be entirely reliable: the Session ID (i.e. ##SPID) is constant for all of the requests on that Connection). So all sub-processes (i.e. EXEC calls, sp_executesql, Triggers, etc) will all be on the same ##SPID / session_id. So, between sub-processes and User Transactions, you can very easily get locks on multiple resources, all on the same Session ID.
The reason I say "resources" instead of "OBJECT" or even "RID" is that locks can occur on: rows, pages, keys, tables, schemas, stored procedures, the database itself, etc. More than one thing can be considered an "OBJECT", and it is possible that you will have page locks instead of row locks.
Will it stand up to multiple executions of the same and/or different triggers within the database?
As long as these executions occur in different Sessions, then they are a non-issue.
ALL THAT BEING SAID, I can see where simple testing would show that your current method is reliable. However, it should also be easy enough to add more detailed tests that include an explicit transaction that first does some DML on another table, or have a trigger on one table do some DML on one of these tables, etc.
Unfortunately, there is no built-in mechanism that provides the same functionality that ##PROCID does for T-SQL Triggers. I have come up with a scheme that should allow for getting the parent table for a SQLCLR Trigger (that takes into account these various issues), but haven't had a chance to test it out. It requires using a T-SQL trigger, set as the "first" trigger, to set info that can be discovered by the SQLCLR Trigger.
A simpler form can be constructed using CONTEXT_INFO, if you are not already using it for something else (and if you don't already have a "first" Trigger set). In this approach you would still create a T-SQL Trigger, and then set it as the "first" Trigger using sp_settriggerorder. In this Trigger you SET CONTEXT_INFO to the table name that is the parent of ##PROCID. You can then read CONTEXT_INFO() on a Context Connection in a SQLCLR Trigger. If there are multiple levels of Triggers then the value of CONTEXT INFO will get overwritten, so reading that value must be the first thing you do in each SQLCLR Trigger.
This is an old thread, but it is an FAQ and I think I have a better solution. Essentially it uses the schema of the inserted or deleted table to find the base table by doing a hash of the column names and comparing the hash with the hashes of tables with a CLR trigger on them.
Code snippet below - at some point I will probably put the whole solution on Git (it sends a message to Azure Service Bus when the trigger fires).
private const string colqry = "select top 1 * from inserted union all select top 1 * from deleted";
private const string hashqry = "WITH cols as ( "+
"select top 100000 c.object_id, column_id, c.[name] "+
"from sys.columns c "+
"JOIN sys.objects ot on (c.object_id= ot.parent_object_id and ot.type= 'TA') " +
"order by c.object_id, column_id ) "+
"SELECT s.[name] + '.' + o.[name] as 'TableName', CONVERT(NCHAR(32), HASHBYTES('MD5',STRING_AGG(CONVERT(NCHAR(32), HASHBYTES('MD5', cols.[name]), 2), '|')),2) as 'MD5Hash' " +
"FROM cols "+
"JOIN sys.objects o on (cols.object_id= o.object_id) "+
"JOIN sys.schemas s on (o.schema_id= s.schema_id) "+
"WHERE o.is_ms_shipped = 0 "+
"GROUP BY s.[name], o.[name]";
public static void trgSendSBMsg()
{
string table = "";
SqlCommand cmd;
SqlDataReader rdr;
SqlTriggerContext trigContxt = SqlContext.TriggerContext;
SqlPipe p = SqlContext.Pipe;
using (SqlConnection con = new SqlConnection("context connection=true"))
{
try
{
con.Open();
string tblhash = "";
using (cmd = new SqlCommand(colqry, con))
{
using (rdr = cmd.ExecuteReader(CommandBehavior.SingleResult))
{
if (rdr.Read())
{
MD5 hash = MD5.Create();
StringBuilder hashstr = new StringBuilder(250);
for (int i=0; i < rdr.FieldCount; i++)
{
if (i > 0) hashstr.Append("|");
hashstr.Append(GetMD5Hash(hash, rdr.GetName(i)));
}
tblhash = GetMD5Hash(hash, hashstr.ToString().ToUpper()).ToUpper();
}
rdr.Close();
}
}
using (cmd = new SqlCommand(hashqry, con))
{
using (rdr = cmd.ExecuteReader(CommandBehavior.SingleResult))
{
while (rdr.Read())
{
string hash = rdr.GetString(1).ToUpper();
if (hash == tblhash)
{
table = rdr.GetString(0);
break;
}
}
rdr.Close();
}
}
if (table.Length == 0)
{
p.Send("Error: Unable to find table that CLR trigger is on. Message not sent!");
return;
}
….
HTH

How to get Option Set Description field in MSCRM

I've tried to find where Option Set descriptions are stored in CRM's database. After research on the internet I've found that Option Set data is stored in the StringMap SQL table but this table doesn't contain description Field I want.
Does anyone know where Option Set descriptions are stored stored in CRM's SQL database? Below is a screenshot highlighting the field value I'm looking for:
Try this:
SELECT Label FROM [LocalizedLabelView] llv
join [AttributePicklistValueView] apvv on llv.ObjectId = apvv.AttributePicklistValueId
join [OptionSetView] osw on apvv.OptionSetId = osw.OptionSetId
join [AttributeView] aw on osw.OptionSetId = aw.OptionSetId
where aw.Name = 'fieldname' and llv.ObjectColumnName = 'Description'
This works for both global and non-global option sets, you just have to put as fieldname the name of the attribute on the entity (not a name of global option set). Of course to handle only global option sets you will not need the last join, simply do osw.Name = 'globaloptionsetname'
This seems to work:
SELECT DISTINCT l.Label
FROM MetadataSchema.LocalizedLabel l
LEFT JOIN MetadataSchema.AttributePicklistValue ap ON l.ObjectId = ap.AttributePicklistValueId
LEFT JOIN MetadataSchema.OptionSet os ON os.OptionSetId = ap.OptionSetId
WHERE l.ObjectColumnName = 'Description' AND os.Name = '<OPTIONSET_NAME>' AND ap.Value = <OPTIONSET_VALUE>
There are two parameters in the above script that you need to modify:
<OPTIONSET_NAME> must be replaced with the schema name of your optionset and prefixed with the entity's schema name. For example, if your optionset is called new_businessTypes and it's on the account entity, then <OPTIONSET_NAME> would be replaced with 'account_new_businesstypes'.
<OPTIONSET_VALUE> must be replaced with the integer value of the option you're looking for. In your example screenshot, that value is 2.

Dapper with Access, update statement partially not working

I have a product class and tried to evaluate Dapper with Access database.. Select, Delete and Insert operations are working fine, but I have a problem with update operation. It is working in one way only code below)
When I tried to change the Description based on ProductNumber it works (updateStatement2) and Description get updated, but when I tried to change the ProductNumber based on Description (updateStatement1) it doesn't work and ProductNumber doesn't get updated. It bit strange to me. Is it a bug or am I missing anything?. My database is just a basic one and no primary keys set. I have attached a screenshot below
(For more information see my code below)
public class Products
{
public string ProductNumber { get; set; }
public string Description { get; set; }
}
static void Main(string[] args)
{
using (var con = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb"))
{
Products product2 = new Products();
product2.ProductNumber = "P2";
product2.Description = "TestProduct2Changed";
var updateStatement2 = #"Update Products Set Description = #Description Where ProductNumber = #ProductNumber";
int outp2 = con.Execute(updateStatement2, product2);
Products product1 = new Products();
product1.ProductNumber = "P3Changed";
product1.Description = "TestProduct3";
var updateStatement1 = #"Update Products Set ProductNumber = #ProductNumber Where Description = #Description";
int outp1 = con.Execute(updateStatement1, product1);
}
}
I am using Dapper version 1.50.2. This is my database screenshot
It looks like ADO Access commands require the parameters to be present in the same order as they appear in the SQL query.
In your original code, for the query that works, the parameters appear in the query string in alphabetical order -
Update Products Set Description = #Description Where ProductNumber = #ProductNumber
This works because the properties are taken from "product2" in alphabetical order. This may not be by design, it might just be the order in which reflection lists them.
In your query that fails, the parameters appear in reverse alphabetical order -
Update Products Set ProductNumber = #ProductNumber Where Description = #Description
.. and this fails because the parameter values get mis-assigned within Access.
You should be able confirm this by changing the order of the parameters in your dynamic parameter alternative. I tried using dynamic parameters and it worked when the parameters were in the same order as which they appeared in the SQL query but failed if they weren't. The database I'm using isn't quite the same as yours but the following should illustrate what I'm talking about:
// Doesn't work (parameter order is incorrect)
con.Execute(
"Update People Set PersonName = #PersonName Where Notes = #Notes",
new { Notes = "NotesChanged", PersonName = "New Name" }
);
// DOES work (parameter order is correct)
con.Execute(
"Update People Set PersonName = #PersonName Where Notes = #Notes",
new { PersonName = "New Name", Notes = "NotesChanged" }
);
While trying to find more information about this, I came across this answer that unfortunately seems to confirm the issue: https://stackoverflow.com/a/11424444/3813189
I guess that it might be possible for the custom SQL generator that you've mentioned in one of your other questions to do some magic to parse the query and retrieve the parameters in the order in which they must appear and to then ensure that they are provided in the correct order.. if someone is maintaining an Access connector for DapperExtensions then it might be worth raising an issue. Because, at the moment, I think that you are correct and that it is an issue with the library.

JPA retrieve part of object

In sql you can easily select a few columns out of the whole table. How do you achieve such a thing in JPA ?
And that's not actually my main question. It's more of an design one.
Let's say i have a parent object with some information fields and a collection of child objects field with a one to many connection.
#Entity
#Table(name = "code")
public class CodeList extends Code {
/** The code list's values. */
#OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "codeListId")
private List<CodeValue> codeValues;
/** The code list's display type. */
#Column(length = 255, name = "DISPLAY_TYPE")
private String displayType;
....
I am trying to implement a rest service using JPA (Hibernate) and jax-rs but what if i want my service to be able to retrieve just the information of the CodeList ( the displayType ) or just the collection of codeValues without any extra performance overhaul (nulling out the objects) or retrieval from the database of extra data that i dont need ?
By default #OneToMany relationship is LAZY .
simple value :
SELECT c.displayType FROM CodeList c WHERE c.id=:id
relationship
SELECT v FROM CodeList c LEFT JOIN c.codeValues v WHERE c.id=:id
Use JPQL to determine which values you want to retrieve.
select e.displayType from CodeList e
OR
select e from CodeList d left join fetch d.codeValues e

Entity Framework - how to get database column datatype from metadata

Is there a way to get the database column DataType length information given a table's EntityType?
Example SQL (SQL Server) that you can run to see precisely what information I am looking for:
select
sys.tables.name as 'Table Name',
sys.columns.name as 'Column Name',
sys.systypes.name as 'DataType',
sys.columns.max_length as 'Max Length',
sys.columns.precision as 'Precision'
from
sys.columns, sys.systypes, sys.tables
where
sys.columns.system_type_id = sys.systypes.xtype
and sys.systypes.name <> 'sysname'
and sys.tables.type = 'U'
and sys.tables.name <> 'sysdiagrams'
and sys.columns.object_id=sys.tables.object_id
order by
sys.tables.name, sys.columns.column_id;
The last 3 columns contain the data that I would like to have access to because I'm generating some documentation. One example reason for the documentation is: Entity Framework will throw an Exception by default if a string is set on a property that can't support it's length. A developer without access to the database metadata has a challenge with the discoverability of length requirements in this case.
Thanks,
Aaron
Unfortunately no.
Even if that information is correctly captured in the SSDL (i.e. the Storage Schema Definition language) there is no public API in EF to go from C-Space (conceptual model) property to S-Space (storage model) column.
If your model is simple you can perhaps infer that information, using the EF metadata workspace and some simple heuristics, but once things get even a little complicated, those heuristics will break down.
Your only option at that point is to write code to interpret MSL (mapping or CS-Space) files, and use that in conjunction with the MetadataWorkspace to go from C-Space to S-Space.
EDIT: as pointed out by KristoferA you often have the attribute on the C-Space property, so you can go to directly to that. Unfortunately that is not always the case, and often it gets out of sync with the database.
I'm pretty sure that Julie Lerman's book covers how to get maxlength, at least a tool to validate against it, by making changes in the POCO creation. Chapter 13, starts around page 356. Example 13-12 covers it, it starts with
string MaxLengthValidation(EdmProperty prop)...
it's copyrighted material so I won't cut/paste it, but I hope you can buy a copy of her book and get the info.
Yes, this is possible: (EF6.1)
<Extension>
Public Function GetColumns(Of TEntity)(Db As IObjectContextAdapter) As List(Of DataColumn)
Dim oMetadata As MetadataWorkspace
Dim oObjects As ObjectItemCollection
Dim oContext As ObjectContext
Dim oColumn As DataColumn
Dim oQuery As Func(Of EdmProperty, Boolean)
Dim oType As EntityType
GetColumns = New List(Of DataColumn)
oContext = Db.ObjectContext
oMetadata = oContext.MetadataWorkspace
oObjects = oMetadata.GetItemCollection(DataSpace.OSpace)
oType = oMetadata.GetItems(Of EntityType)(DataSpace.OSpace).
Single(Function(EntityType As EntityType) oObjects.GetClrType(EntityType) Is GetType(TEntity))
oQuery = Function(EdmProperty As EdmProperty) EdmProperty.DeclaringType.Name = oType.Name
oType.Properties.ToList.ForEach(Sub(Column As EdmProperty)
oColumn = New DataColumn With
{
.AutoIncrement = Column.IsStoreGeneratedIdentity,
.AllowDBNull = Column.Nullable,
.ColumnName = Column.Name,
.DataType = Column.PrimitiveType.ClrEquivalentType,
.Caption = Column.Name
}
If oColumn.DataType Is GetType(String) Then
oColumn.MaxLength = Column.MaxLength.GetValueOrDefault
Else
oColumn.MaxLength = -1
End If
GetColumns.Add(oColumn)
End Sub)
End Function

Resources