The below I can have a condition to execute view/table, which would load into AccountDataModel class.
dbcontext.AccountDataModel.FromSql($"select * from account where id=123").FirstOrDefault();
How can I retrieve without using a class model, if I want to retrieve just 1 or 2 columns
example: select name from account where id=123
Do I always need a class model?
ADO.NET works in EFCore =)
using Microsoft.EntityFrameworkCore;
using System.Data.Common;
using System.Data.SqlClient;
using System;
public void ExampleMethod(DbContext context)
{
SomeObjectResult result = null;
DbCommand cmd = context.Database.GetDbConnection().CreateCommand();
cmd.CommandText = "Select C.ID, C.CarModelID as ModelID, C.VIN, C.RegNumber,cast(C.CountValue as int) as Course,A.BrandID from A inner join C on A.ID = C.KeyID Where A.ID = #appID";
cmd.Parameters.Add(new SqlParameter("#appID", appointmentID));
if (cmd.Connection.State != ConnectionState.Open)
{
cmd.Connection.Open();
}
using (var reader = await cmd.ExecuteReaderAsync())
{
if (reader.Read())
{
result = new SomeObjectResult()
{
BrandID = (int)reader["BrandID"],
Course = (int)reader["Course"],
ID = (int)reader["ID"],
ModelID = (int?)reader["ModelID"],
RegNumber = (string)reader["RegNumber"],
VIN = (string)reader["VIN"]
};
}
}
}
Yes. like this :
var account = dbcontext.AccountDataModel.FromSql(#"select a.id, a.name from account a where a.id=123");
source : FromSql for non entity type
This queries the database
var name = dbcontext.GetDBConnection().Query("select name from account where id=123").FirstOrDefault();
Related
I am dealing with creating billings that uses a fairly massive amount of data (2+ million records overall), so I was forced to use some direct SQL to speed up the data loading as using pure EF was too slow.
Using this entity:
[Keyless]
public class BillingAggregate
{
public List<Customer> Customers { get; set; }
public List<Debt> Debts { get; set; }
public List<Payment> Payments { get; set; }
public List<Credit> Credits { get; set; }
}
Added to the DataContext as:
public DbSet<BillingAggregate> BillingAggregate { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BillingAggregate>().ToSqlQuery("EXEC [dto].[GetForMonthlyBilling]");
}
With my Stored Procedure as:
CREATE PROCEDURE [dbo].[GetForMonthlyBilling]
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #TempCustomerIDsForBilling([CustomerId] bigint)
INSERT INTO #TempCustomerIDsForBilling([CustomerId])
SELECT DISTINCT [CustomerId]
FROM [dbo].[Debt] debt
INNER JOIN [dbo].[DebtType] debtType ON debt.[DebtTypeId] = debtType.[DebtTypeId]
WHERE debtType.[IsCollectible] = 1
SELECT * FROM [dbo].[Customer] WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
SELECT * FROM [dbo].[Debt] WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
SELECT * FROM [dbo].[Payment] WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
SELECT *
FROM [dbo].[Credit] credit
INNER JOIN [dbo].[Debt] debt ON credit.[DebtId] = debt.[DebtId]
WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
DROP TABLE #TempCustomerIDsForBilling
END
GO
This seems to be all that the documentation that I was able to find requires me to do... however, when I do a standard query:
var billingAggregate = await dbContext.BillingAggregate.FirstOrDefaultAsync();
It immediately throws the error:
{"Sequence contains no elements"}
The immediacy of the error makes me think that the Stored Procedure fails to even run, as running it in SQL alone takes 40+ seconds... what am I missing?
For whomever this may help, as per Svyatoslav Danyliv's link, it seems this is currently not possible with EF Core (though, was possible with EF 6). As a workaround, I had to drop down to a lower lever API that EF Core uses. I created it as an Extension Method so that I never have to see this "garbage" again :)
public static class DbContextExtensions
{
public static async Task<List<BillingHelper>> GetBillingsAsync(this MyDataContext dbContext)
{
var customers = new List<Customer>();
var customerAddresses = new List<CustomerAddress>();
var debts = new List<Debt>();
var payments = new List<Payment>();
var credits = new List<Credit>();
using (var command = dbContext.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "[dbo].[GetForMonthlyBilling]";
command.CommandType = System.Data.CommandType.StoredProcedure;
if (command.Connection.State != System.Data.ConnectionState.Open)
command.Connection.Open();
using (var reader = await command.ExecuteReaderAsync())
{
if (reader != null && reader.HasRows)
{
var customerType = typeof(Customer);
var customerProperties = customerType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
var customerDictionary = customerProperties.ToDictionary(property => property.Name.ToUpper(), property => property);
while (await reader.ReadAsync())
{
var customer = new Customer();
for (int index = 0; index < reader.FieldCount; index++)
{
var currentColumnName = reader.GetName(index).ToUpper();
if (customerDictionary.ContainsKey(currentColumnName))
{
var property = customerDictionary[currentColumnName];
if ((property != null) && property.CanWrite)
{
var value = reader.GetValue(index);
property.SetValue(claimant, (value == DBNull.Value ? null : value), null);
}
}
}
customers.Add(claimant);
}
// This loads the next "SELECT", that is Result Set
await reader.NextResultAsync();
var customerAddressType = typeof(CustomerAddress);
// ... similar as above for Customer
await reader.NextResultAsync();
var debtType = typeof(Debt);
// ... similar as above for Customer
await reader.NextResultAsync();
// ...
}
}
}
// From here to the end of this method takes C# compiler a long time to do
// opportunity for optimization perhaps.
var billings = customers.Select(customer => new BillingHelper
{
CustomerId = customer.CustomerId,
Customer = customer,
Debts = debts.Where(x => x.CustomerId == customer.CustomerId).ToList(),
Payments = payments.Where(x => x.CustomerId == customer.CustomerId).ToList()
}).ToList();
foreach (var billing in billings)
{
var debtIds = billing.Debts.Select(x => x.DebtId);
billing.Credits = credits.Where(x => debtIds.Contains(x.DebtId)).ToList();
billing.Customer.Addresses = customerAddresses.Where(x => x.CustomerId == billing.CustomerId).ToList();
}
return billings;
}
}
This is as close as I've got...
public static class Helpers
{
public static bool TableExists(this MigrationBuilder builder, string tableName)
{
bool exists = builder.Sql($#"SELECT 1 FROM sys.tables AS T
INNER JOIN sys.schemas AS S ON T.schema_id = S.schema_id
WHERE S.Name = 'SchemaName' AND T.Name = '{tableName}'");
return exists;
}
}
But how does one get a result form the SQL call?
Here is one solution...
public override void Up()
{
if (!Exists("dbo.MyTable"))
{
... do something
}
}
private static bool Exists(string tableName)
{
using (var context = new DbContext(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
var count = context.Database.SqlQuery<int>("SELECT COUNT(OBJECT_ID(#p0, 'U'))", tableName);
return count.Any() && count.First() > 0;
}
}
This query runs immediately rather than being defered like other DbMigration commands are - but that's why it works. The result is known straight away so that other commands can be queued (or not queued) as required.
It's not a perfect solution, but you could use an IF in SQL:
builder.Sql(#"
IF (EXISTS(SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'MySchema'
AND TABLE_NAME = 'TableName'))
BEGIN
--DO SOMETHING
END
");
So we've got a whole lot of Postgres SQL queries stored in files and used from PHP. The task is to replace PHP with Java. We want to reuse as much of the queries "as is" to keep the migration path short. I can't get the Array parameters to work.
Here's a query example:
update user_devices
set some_date = now()
where some_id in (
select distinct some_id from user_devices
where user_id = any(:userIDs) and device_id = any(:deviceIDs)
and exists (select 1 from users where user_id = any(:userIDs) and customer_id = :customerID)
);
Note the "any" clauses, which cause the problem, because they expect an array type.
This is how we used them from PHP:
$this->allValues['userIDs'] = '{' . implode ( ",", $userIdNodes ) . '}';
$this->allValues['deviceIDs'] = '{' . implode ( ",", $deviceIdNodes ) . '}';
$this->allValues['customerID'] = customerID;
$this->db->runQuery ( $this->getQuery ( 'my_query' ), $this->allValues );
So as parameters the array types look like "{111,222}".
This is what I tried in Java:
Integer customerID = 1;
int[] userIDs = new int[]{111,222};
int[] deviceIDs= new int[]{333,444};
//List<Integer> userIDs = Arrays.asList(111,222);
//List<Integer> deviceIDs= Arrays.asList(333,444);
//java.sql.Array userIDs = toArray("integer", new int[]{111,222}));
//java.sql.Array deviceIDs= toArray("integer", new int[]{333,444}));
//java.sql.Array userIDs = toArray("integer", Arrays.asList(111,222)));
//java.sql.Array deviceIDs= toArray("integer", Arrays.asList(333,444)));
//String userIDs = "{111,222}";
//String deviceIDs= "{333,444}";
//String userIDs = "ARRAY[111,222]";
//String deviceIDs= "ARRAY[333,444]";
Query nativeQuery = em.createNativeQuery(queryString);
nativeQuery.setParameter("userIDs", userIDs);
nativeQuery.setParameter("deviceIDs", deviceIDs);
nativeQuery.setParameter("customerID", customerID);
//nativeQuery.setParameter(createParameter("userIDs",java.sql.Array.class), userIDs);
//nativeQuery.setParameter(createParameter("userIDs",java.sql.Array.class), deviceIDs);
//nativeQuery.setParameter(createParameter("customerID", Integer.class), customerID);
query.executeUpdate();
//[...]
private Array toArray(String typeName, Object... elements) {
Session session = em.unwrap(Session.class); // ATTENTION! This is Hibernate-specific!
final AtomicReference<Array> aRef = new AtomicReference<>();
session.doWork((c) -> {
aRef.set(c.createArrayOf(typeName, elements));
});
return aRef.get();
}
private <T> Parameter<T> createParameter(final String name, final Class<?> clazz) {
return new Parameter<T>() {
#Override
public String getName() {
return name;
}
#Override
public Integer getPosition() {
return null; // not used
}
#Override
public Class<T> getParameterType() {
return (Class<T>) clazz;
}
};
}
None of these will work I will get one of these exceptions:
When using the "toArray" method:
Caused by: org.hibernate.HibernateException: Could not determine a type for class: org.postgresql.jdbc4.Jdbc4Array
at org.hibernate.internal.AbstractQueryImpl.guessType(AbstractQueryImpl.java:550)
at org.hibernate.internal.AbstractQueryImpl.guessType(AbstractQueryImpl.java:534)
at org.hibernate.internal.AbstractQueryImpl.determineType(AbstractQueryImpl.java:519)
at org.hibernate.internal.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:487)
at org.hibernate.jpa.internal.QueryImpl$ParameterRegistrationImpl.bindValue(QueryImpl.java:247)
at org.hibernate.
Or when using int[] or Strings, I'll get:
Caused by: org.postgresql.util.PSQLException: ERROR: op ANY/ALL (array) requires array on right side
Position: 137
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2270)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1998)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255)
at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:570)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:420)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:366)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.postgresql.ds.jdbc23.AbstractJdbc23PooledConnection$StatementHandler.invoke(AbstractJdbc23PooledConnection.java:453)
at com.sun.proxy.$Proxy274.executeUpdate(Unknown Source)
at com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:125)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
jpa.spi.BaseQueryImpl.setParameter(BaseQueryImpl.java:582)
Using Wireshark I found this when both APIs are talking to the database:
Image: Comparison of database calls with Wireshark
select oid, typname from pg_type where oid in (0, 23, 1043) order by oid;
oid |typname
------+-------
23 |int4
1043 |varchar
Has anyone managed to use array-parameters with native queries using Hibernate as backend for the JPA EntityManager? If so: How?
Change your query from where user_id = any(:userIDs) to where user_id IN (:userIDs), and change the userIDs array to a collection e.g. List<Long>. You will have to additionally protect it empty lists, but it will work.
I was able to work around this problem by unwrapping the Hibernate session from the EntityManager and use a JDBC PreparedStatement, which eats the java.sql.Array parameters without any complaint.
The NamedParameterStatement used in the example below is described here (I've modified it to my needs). It delegates to a PreparedStatement.
The rest of the code goes a little something like this:
public int executeUpdate(...){
//....
Integer customerID = 1;
java.sql.Array userIDs = toArray("integer", new int[]{111,222}));
java.sql.Array deviceIDs= toArray("integer", new int[]{333,444}));
final AtomicInteger rowsModifiedRef = new AtomicInteger();
final Session session = em.unwrap(Session.class); // ATTENTION! This is Hibernate-specific!
session.doWork((c) -> {
try (final NamedParameterStatement statement = new NamedParameterStatement(c, queryString)) {
statement.setObject("deviceIDs", userIDs);
statement.setObject("userIDs", userIDs);
statement.setObject("customerID", userIDs);
rowsModifiedRef.set(statement.executeUpdate());
}
});
return rowsModifiedRef.get();
}
private Array toArray(String typeName, Object... elements) {
Session session = em.unwrap(Session.class); // ATTENTION! This is Hibernate-specific!
final AtomicReference<Array> aRef = new AtomicReference<>();
session.doWork((c) -> {
aRef.set(c.createArrayOf(typeName, elements));
});
return aRef.get();
}
(PostgreSQL 9.1, Telerik OpenAccess v2.0.50727, PgAdmin III).
I'm having difficulty calling a stored procedure from the (Telerik) Entity Framework. The exact error is:
NpgsqlException was unhandled by user code.
ERROR: 42703: column "cpatient" does not exist.
The Telerik templated call is:
public int SaveDx(string cpatient, Object o, Object n)
{
OAParameter parameterCpatient = new OAParameter();
parameterCpatient.ParameterName = "cpatient";
parameterCpatient.Size = -1;
if(cpatient != null)
{
parameterCpatient.Value = cpatient;
}
else
{
parameterCpatient.DbType = DbType.String;
parameterCpatient.Value = DBNull.Value;
}
OAParameter parameterO = new OAParameter();
parameterO.ParameterName = "o";
parameterO.Value = o;
OAParameter parameterN = new OAParameter();
parameterN.ParameterName = "n";
parameterN.Value = n;
int queryResult = this.ExecuteNonQuery("SELECT * FROM \"public\".\"g_savedx\"(cpatient, o, n)", CommandType.Text, parameterCpatient, parameterO, parameterN);
return queryResult;
}
Where the ExecuteNonQuery statement generates the error. The PostgreSQL stored procedure is:
FUNCTION g_savedx(cpatient character varying, o view_dx, n view_dx)
RETURNS void AS ...
The postgreSQL function has been tested to work correctly from pgAdmin.
So where is the column "cpatient" coming from?? What am I doing wrong?
TIA
I never could get the Telerik EntitiesModel ExecuteNonQuery to work under any conditions. Hence the suggested code of:
using (var cxt = new Nova.Data.Data())
{
cxt.SaveDx();
cxt.SaveChanges();
}
where cxt.SaveDx() is the domain model name for the postgresql g_savedx stored procedure, fails.
My eventual workaround for PostgreSQL is to use Npgsql directly as:
public void SaveDx(View_dx dx, bool alldx = false)
{
using (var cxt = new Nova.Data.Data())
{
string connstring = cxt.Connection.ConnectionString;
using (NpgsqlConnection conn = new NpgsqlConnection(connstring))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "g_savedx";
cmd.CommandType = CommandType.StoredProcedure;
NpgsqlCommandBuilder.DeriveParameters(cmd);
cmd.Parameters["groupid"].Value = ....
var rowsAffected = cmd.ExecuteNonQuery();
}
}
}
}
When doing it this way, only use the types defined in the NpgsqlDbType enumeration in the PostgreSQL procedure interface. (PostgreSQL can use composite types, Npgsql not so much).
It would sure be nice for Telerik ExecuteNonQuery to work.
i am new to JPA and i have a problem with it.
suppose that we have two tables which are related
by a ManytoOne association, which means that
table A stores a primary key of table B within it.
when these two tables are mapped to JPA entities
i have a problem for search on this situation.
i have used an existing code from richfaces demo, to handle filtering and sorting by using
JPA. this code is using input parameters to create criteria query.
this is the code:
private CriteriaQuery<T> createSelectCriteriaQuery() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
Root<T> root = criteriaQuery.from(entityClass);
if (arrangeableState != null) {
List<Order> orders = createOrders(criteriaBuilder, root);
if (!orders.isEmpty()) {
criteriaQuery.orderBy(orders);
}
Expression<Boolean> filterCriteria = createFilterCriteria(criteriaBuilder, root);
if (filterCriteria != null) {
criteriaQuery.where(filterCriteria);
}
}
return criteriaQuery;
}
protected Expression<Boolean> createFilterCriteriaForField(String propertyName, Object filterValue, Root<T> root, CriteriaBuilder criteriaBuilder) {
String stringFilterValue = (String) filterValue;
if (Strings.isNullOrEmpty(stringFilterValue)) {
return null;
}
stringFilterValue = stringFilterValue.toLowerCase(arrangeableState.getLocale());
Path<String> expression = root.get(propertyName);
Expression<Integer> locator = criteriaBuilder.locate(criteriaBuilder.lower(expression), stringFilterValue, 1);
return criteriaBuilder.gt(locator, 0);
}
private Expression<Boolean> createFilterCriteria(CriteriaBuilder criteriaBuilder, Root<T> root) {
Expression<Boolean> filterCriteria = null;
List<FilterField> filterFields = arrangeableState.getFilterFields();
if (filterFields != null && !filterFields.isEmpty()) {
FacesContext facesContext = FacesContext.getCurrentInstance();
for (FilterField filterField : filterFields) {
String propertyName = (String) filterField.getFilterExpression().getValue(facesContext.getELContext());
Object filterValue = filterField.getFilterValue();
Expression<Boolean> predicate = createFilterCriteriaForField(propertyName, filterValue, root, criteriaBuilder);
if (predicate == null) {
continue;
}
if (filterCriteria == null) {
filterCriteria = predicate.as(Boolean.class);
} else {
filterCriteria = criteriaBuilder.and(filterCriteria, predicate.as(Boolean.class));
}
}
}
return filterCriteria;
}
the code is okay, when i try to filter columns(not joined columns), but when i try to
query on joined column, the produced query is not correct and it throws exception.
so my question is that, how could i use JPA criteria query api, to filter rows by both
joined columns and non-joined coulmns.
thanks
I don't believe you can treat join columns like regular ones.
for example if you want to filter on id of B, you would have to create a join from A to B , then use B_.id to match values.
Shay