So I've been dealing with a home brew DB framework that has some seriously flaws, the justification for use being that not using an ORM will save on the number of queries executed.
If I'm selecting all possibile records from the top level of a joinable object hierarchy, how many separate calls to the DB will be made when using an ORM (such as Hibernate)?
I feel like calling bullshit on this, as joinable entities should be brought down in one query , right? Am I missing something here?
note: lazy initialization doesn't matter in this scenario as all records will be used.
Hibernate will almost always retrieve object hierarchies using a single query; I don't recall seeing it do otherwise. It's easy to test, anyway. With this very simple mapping:
#Entity
public static class Person {
#Id
public String name;
}
#Entity
public static class Student extends Person {
public float averageGrade;
}
#Entity
public static class Teacher extends Person {
public float salary;
}
Then Hibernate gives me the following results for a very simple browse query (sessionFactory.openSession().createCriteria(Person.class).list();).
With #Inheritance(strategy = InheritanceType.SINGLE_TABLE) on the parent:
select this_.name as name0_0_, this_.averageGrade as averageG3_0_0_,
this_.salary as salary0_0_, this_.DTYPE as DTYPE0_0_ from HibernateTest$Person this_
With #Inheritance(strategy = InheritanceType.JOINED) on the parent:
select this_.name as name0_0_, this_1_.averageGrade as averageG1_1_0_,
this_2_.salary as salary2_0_, case when this_1_.name is not null then 1
when this_2_.name is not null then 2 when this_.name is not null then 0
end as clazz_0_ from HibernateTest$Person this_ left outer
join HibernateTest$Student this_1_ on this_.name=this_1_.name left outer join
HibernateTest$Teacher this_2_ on this_.name=this_2_.name
With #Inheritance(strategy = InheritanceType.JOINED) on the parent:
select this_.name as name0_0_, this_.averageGrade as averageG1_1_0_,
this_.salary as salary2_0_, this_.clazz_ as clazz_0_ from
( select null as averageGrade, name, null as salary, 0 as clazz_
from HibernateTest$Person union select averageGrade, name, null as salary,
1 as clazz_ from HibernateTest$Student union select null as averageGrade,
name, salary, 2 as clazz_ from HibernateTest$Teacher ) this_
As you can see, each is one query, with JOINs or UNIONs as appropriate depending on the mapping type.
Bobah is right,
You should give hibernate a try in order to see how many request will be sent to the database, however, in hibernate you can also specify and tune specific request by using HQL.
In addition with hibernate tools, you could also use P6spy driver, so you'll be able to see all the request that hibernate send to your database, with the value for each filter of the request.
Related
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
I am trying to import SQL Server's CONTAINS() function in my Entity Framework model so that I can use it in my LINQ queries.
I have added this to my EDM:
<Function Name="FullTextSearch" ReturnType="Edm.Boolean">
<Parameter Name="Filter" Type="Edm.String" />
<DefiningExpression>
CONTAINS(*, Filter)
</DefiningExpression>
</Function>
Add created my method stub:
[EdmFunction("MyModelNamespace", "FullTextSearch")]
public static bool FullTextSearch(string filter)
{
throw new NotSupportedException("This function is only for L2E query.");
}
I try to call the function like this:
from product in Products
where MyModel.FullTextSearch("FORMSOF(INFLECTIONAL, robe)")
select product
The following exception is raised:
The query syntax is not valid. Near term '*'
I realize that the function I defined is not directly linked to the entity set being queried so that could also be a problem.
Is there any way to pull this off?
The function you have defined above uses Entity SQL, not Transact SQL, so I think the first step is to figure out whether CONTAINS(*,'text') can be expressed in Entity SQL.
Entity SQL doesn't support the * operator as described here: http://msdn.microsoft.com/en-us/library/bb738573.aspx and if I try
entities.CreateQuery<TABLE_NAME>("select value t from TABLE_NAME as t where CONTAINS(*, 'text')");
I get the same error you got above. If I try to explicitly pass the column it works:
entities.CreateQuery<TABLE_NAME>("select value t from TABLE_NAME as t where CONTAINS(t.COLUMN_NAME, 'text')");
But when I look at the SQL it translated it to a LIKE expression.
ADO.NET:Execute Reader "SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[TABLE_NAME] AS [Extent1]
WHERE (CASE WHEN ([Extent1].[COLUMN_NAME] LIKE '%text%') THEN cast(1 as bit) WHEN ( NOT ([Extent1].[COLUMN_NAME] LIKE '%text%')) THEN cast(0 as bit) END) = 1
) AS [GroupBy1]"
If you cannot express the query using Entity SQL you'll have to use a Stored Procedure or other mechanism to use Transact SQL directly.
This is way beyond me but could you try
from product in Products where MyModel.FullTextSearch(product, "FORMSOF(INFLECTIONAL, robe)") select product
My reasoning is that in SQL Server it is expecting two parameters.
I inserted a little function into my code, in a class which inherits from the Context class, which points to my SQL function supporting Full Text searching, my solution is a little more closed ended to yours (not allowing the specification of the type of text search), it returns an IEnumerable, essentially a list of primary keys matching the searching criteria, something like this;
public class myContext : DataContext
{
protected class series_identity
{
public int seriesID;
series_identity() { }
};
[Function(Name = "dbo.fnSeriesFreeTextSearchInflectional", IsComposable = true)]
protected IQueryable<series_identity> SynopsisSearch([Parameter(DbType = "NVarChar")] string value)
{
return this.CreateMethodCallQuery<series_identity>(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), value);
}
public IEnumerable<int> Search(string value)
{
var a = from t1 in SynopsisSearch(value)
select t1.seriesID;
return a;
}
};
usage is something like;
myContext context = new myContext();
IEnumerable<int> series_identities = (from t1 in context.Search("some term")
select t1).Distinct();
I have one query that does a count/group by where I don't need a parameter (there is no where clause).
What is the syntax to run a parameterless query with dapper?
var _results = _conn.Query<strongType>("Select Count(columnA) as aCount, ColumnB, ColumnC from mytable group by ColumnB, ColumnC");
does not work.
I've tried it a few different ways but I still keep getting "ArgumentNullException was unhandled by user code".
Tried to figure it out myself, searched all over and I'm giving up. Thanks in advance.
Edit: Below is the line of code from SqlMapper.cs that throws the error. It's line 1334
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null));
The error details: Value cannot be null. Parameter name: con
Mapping a single result back works just fine:
var a = cnn.Query<int>("select 1").Single()
// a is 1
You may face trouble if somehow your query returns no results, for example:
select count(Id) from
(
select top 0 1 as Id, 2 as Title
) as X
group by Title
return 0 results, so doing a Single on an empty result set is not going to work.
try
var _results = _conn.Query("Select columnB, Count(columnA) C from mytable group by columnB");
int ColumnB = ((int)_results[0].ColumnB);
int C = ((int)_results[0].C);
Value cannot be null. Parameter name: con
This is error is thrown by several dynamic ORMs including Dappper, PetaPoco and Massive but it's usually the same problem: Make sure you're using the [Ignore] attribute on the properties you don't want to include. This includes properties inherited from base classes. The error is useless but that's what it means.
This error can occur because a property you're trying to set in your return object is get-only. Dapper, of course, requires being able to set all properties. You might consider having a separate database DTO object that then gets converted to your properly immutable domain object after reading from the database.
Change this:
public string MyProperty { get; }
to this:
public string MyProperty { get; set; }
I am not much strong in SQL, so looking for some help.
First I am looking for suggestion for the best way to implement this logic in SQL and then some sample code to implement.
My portal is going to connect Students and Training Providers.
Students: Select what courses (multiple) they want, type of delivery (online, class room), Industry(domain) to which the course to be targeted more, Location Preference.
Training Providers: Select what courses offering (so one record for each course), offering locations, type of delivery for each course, industries (multiple) it is targeting.
When student login:
I would like to create SP which in turn create view to store the matched records of the Training Providers data which matches that student needs of that StudentID, CourseID passed to SP
I have created the following sp ( but not included create view part as I am not sure how to do this)
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[sp_TPsMatched2StuCourse]
-- Add the parameters for the stored procedure here
#StuID int,
#CourseID int
AS
BEGIN
Select TP.MemID,TP.PastExp,SN.DeliveryType,SN.LocPref,SN.Industry,SC.CourseID from
tbl_TrainingProvider as TP , tbl_StuCourses as SC, tbl_StuNeeds SN
where SN.CourseID = #CourseID and SN.StuID = #StuID and
SN.DeliveryType in (TP.DeliveryMode) and
SN.LocPref IN (TP.LocOffering) and
SN.Industry IN (TP.Industries)
END
--- exec sp_ELsMatched2EntProp 1, 1
Why I need to put the data is as follows:
Assume the data is stored in that dynamic view and that would be bind to datagrid. Student then select interested TPs. Then only contact details would be shared to each other and this cannot be reveresed. So I would put this interested data in another table later. Every time data changes, hence the matches. Student can change some of his/her needs or new TPs join etc so view to be temparory.
when I executed this using above command, I am not getting data though it matches few records. What is wrong I am doing.
Any help would be greatly appreciated.
You are not getting expected results because you filter out too many records in WHERE( I'm talking about this part : SN.DeliveryType in (TP.DeliveryMode) and
SN.LocPref IN (TP.LocOffering) and SN.Industry IN (TP.Industries)). I'd recommend to use JOIN ... ON instead of specifying all tables in FROM and join condition in WHERE. I'm not sure what you want exactly, but I believe you are looking for
FROM tbl_StuNeeds SN
LEFT JOIN tbl_TrainingProvider as TP ON (TP.DeliveryMode = SN.DeliveryType AND
SN.LocPref = TP.LocOffering AND TP.Industries = SN.Industry)
WHERE SN.CourseID = #CourseID and SN.StuID = #StuID
Also, there is no join conditions in your code for tbl_StuCourses as SC which results in cross-join.
Finally, why do you need a stored procedure at all? From what I see in your example, a table-valued function will work better:
CREATE FUNCTION [dbo].getTPsMatched2StuCourse(#StuID INT,#CourseID INT)
RETURNS TABLE AS
RETURN
Select .... ;
I know there have been a lot of questions about Entity Framework doing cross database queries on the same server posted to stackoverflow. Mostly the answer seems to be 'no', and this link from way back in 2008 is referenced. However, Entity Framework is changing all the time and with CTP5 out, I'm wondering if the answer is still the same - that you can't do it, or you can do it if you manually edit the edmx file, or you have to use views. This feature alone is the reason I'm still tied to Linq-to-SQL, as we have multiple SQL Server 2008 databases on the same server and need to query across them. Polluting our databases with hundreds of select * views is not an option, and with code-first development I don't have an edmx file to edit. I was playing with the pubs database to see if I could get somewhere, but I'm stuck. Any suggestions?
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
namespace DbSchema {
public class Employee {
[Key]
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public short JobID { get; set; }
public Job Job { get; set; }
}
public class Job {
[Key]
public short ID { get; set; }
public string Description { get; set; }
}
public class PubsRepository : DbContext {
public DbSet<Employee> Employee { get; set; }
public DbSet<Job> Job { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
// employee
var eeMap = modelBuilder.Entity<Employee>();
eeMap.ToTable("employee", "dbo"); // <-- how do I reference another database?
eeMap.Property(e => e.ID).HasColumnName("emp_id");
eeMap.Property(e => e.FirstName).HasColumnName("fname");
eeMap.Property(e => e.LastName).HasColumnName("lname");
eeMap.Property(e => e.JobID).HasColumnName("job_id");
// job
var jobMap = modelBuilder.Entity<Job>();
jobMap.Property(j => j.ID).HasColumnName("job_id");
jobMap.Property(j => j.Description).HasColumnName("job_desc");
}
public List<Employee> GetManagers() {
var qry = this.Employee.Where(x => x.Job.Description.Contains("manager"));
Debug.WriteLine(qry.ToString());
return qry.ToList(); // <-- error here when referencing another database!
}
}
}
I think that the answer is still no, but there are ways around it.
The reason why it is no, it that EF uses a DBContext, and a context has a connection string, and a connection string goes to a database.
Here are 2 ways around it:
use 2 different contexts one against each database, this will mean bringing data to the client and merging it on the client.
use linked tables on the database, pulling data through views, so that EF sees it as coming from a single database.
In your code it looks like you are using 2 dbcontexts
There are two ways to do it.
One is, of course, to create a view in one of the databases which does the cross database query, then access the veiw from your model as you would any other view.
The other was it to create the same cross database query view within the model itself by creating a DefiningQuery. This is most similar to how you would do it with SQLClient. In SQLClient, you'd create the view in T-SQL as the text of a SQLCommand, then execute the command to create a data reader or data table. Here you use the same T-SQL to create a DefiningQuery, then link it up with an Entity that you create manually. It's a bit of work, but it does exactly what you'd want it to.
Here's a link on using DefiningQuerys: http://msdn.microsoft.com/en-us/library/cc982038.aspx.
If you happen to have the book "Programming Entity Framework" by Lerman from O'Reilly, there a good example in chapter 16.
So you have to jump through a few hoops to do what you used to do directly with SQLClient, BUT you get the modeled Entity.
The answer is still the same. If you want to execute cross database query you have to fall back to SQL and use SqlQuery on context.Database.
Warning! using DefiningQuerys can be VERY SLOW!
Here's an example:
If this is the defining query that you create an Entity against:
Select
C.CustomerID,
C.FirstName,
C.LastName,
G.SalesCatetory
From
CustomerDatabase.dbo.Customers C
Inner Join MarketingDatabase.dbo.CustomerCategories G on G.CustomerID = C.CustomerID
Then when you do a select against the Entity by CustomerID, the SQL trace looks something like this:
Select
[Extent1].[CustomerID] as [CustomerID],
[Extent1].[FirstName] as [FirstName],
[Extent1].[LastName] as [LastName],
[Extent1].[SalesCatetory] as [SalesCatetory]
From (
Select
C.CustomerID,
C.FirstName,
C.LastName,
G.SalesCatetory
From
CustomerDatabase.dbo.Customers C
Inner Join MarketingDatabase.dbo.CustomerCategories G on G.CustomerID = C.CustomerID
) as [Extent1]
Where '123456' = [Extent1].[CustomerID]
SQL Server may run this query very slowly. I had one case, a little more complicated than the above example, where I tried the DefiningQuery text directly in a SQl Server Management Console query window by adding a where clause for the value I wanted to select for. It run in less than a second. Then I captured the SQL Trace from selecting for the same value from the Entity created for this DefiningQuery and ran the SQL Trace query in a SQL Server query window - it took 13 seconds!
So I guess that only real way to do cross database queries is to create a veiw on the server.