How to specify EF relationships that require SQL cast? - sql-server

I have two legacy SQL tables:
Contact
Id(uniqueidentifier, not null)
Foo
GlobalId(nvarchar(50, null)
EntityName(nvarchar(100, null)
The Foo.GlobalId column stores the ID from other tables in the DB, so to get at relevant data, we'd join tables like this:
select * from Foo
inner join Contact on Foo.EnitityName = 'contact'
and cast(Foo.Globalid as unqiqueidentifier) = Contact.Id
How do I express this relationship or cast the nvarchar to uniqueidentifier (or vice versa) using FluentAPI, or failing that, how can I perform the SQL cast() using LINQ?
public class Contact
{
public Guid Id {get;set;}
public virtual ICollection<Foo> Foos {get;set;}
}
public class ContactMap : EntityTypeConfiguration<Contact>
{
HasKey(t => t.Id);
HasMany(c => c.Foos)
.WithOptional(foo => foo.Contact)
/* ??? */;
}
public class Foo
{
public string GlobalId {get;set;}
public Contact Contact {get;set}
}
public class FooMap : EntityTypeConfiguration<Foo>
{
Property(t => t.PsmsGlobalId).HasMaxLength(50);
Property(t => t.PsmsEntityName).HasMaxLength(100);
}

Entity Framework currently does not support SQL type casts. Creating a SQL view which performed the cast worked well for me.

Related

SQL to Entity Framework Using "In"

I've got an ASP.NET MVC application using EF in C#. I can write and run my query correctly in SQL Server. In EF, I'm not sure how to accomplish the same thing.
Tables A, B and C. C references B which References A.
The query looks like this:
Select *
From C
Where C.bID in (Select B.bID
From B
Where B.aID = '<unique Key In A>')
The sub-query returns multiple B keys. Which then pass it through and look up the ID's in C. In short I'm looking up all data in C related to a key in A.
I just don't know how to put that into EF language. Or if it's even possible. The IN operator is what's throwing me on the conversion.
Example:
var exampleList = _context.C
.Where(l => l.bId in (_context.B
.Where(p => p.aId = keyInA)));
"in" doesn't work here. Obviously. After I wrote this post I made sure of it.
Note: A:B has a 1:Many relation. B:C has a 1 to many relation. All IDs and keys are GUIDs
Set up the navigation property between C & B, and between A & B. This is pretty much the whole point of using Entity Framework rather than just an alternative to ADO and SQL queries.
C contains a BId, so set up a B navigation property:
public class C
{
public int CId { get; set; }
[ForeignKey("B")]
public int BId { get; set; }
public virtual B B { get; set; }
}
B contains an AId, so similar:
public class B
{
public int BId { get; set; }
[ForeignKey("A")]
public int AId { get; set; }
public virtual A A { get; set; }
}
Now, to write your query:
var cs = context.Cs.Where(x => x.B.AId == id);
or
var cs = context.Cs.Where(x => x.B.A.AId == id); // can filter on other "A" fields this way.
I mean, to avoid the use of navigation properties, you may as well just write Sprocs and use ADO. It is possible in cases where you have unrelated tables or absolutely need to avoid a navigation property. Use a Join between context.Cs and context.Bs:
var cs = context.Cs.Join(context.Bs, c => c.BId, b => b.BId, (c, b) => new { C = c, AId = b.AId })
.Where(x => x.AId == aid)
.Select(x => x.C);
IMO seriously ugly working with joins in EF, but sometimes necessary. If you can use a navigation property I'd highly recommend it over using something like that regularly.

Table Annotations Invalid object name

I have a ADMINCIDENTS class with, schema "ADM" and Table "Incidents":
[Table("ADM.INCIDENTS")]
public class ADMINCIDENTS
{
[Key]
public int INCNUM { get; set; }
}
When I query my database, the sql code generated is:
SELECT DISTINCT [inc].[INCNUM]
FROM [ADM.INCIDENTS] AS [inc]
Sql says that [ADM.INCIDENTS] is an invalid object.
How do I annotate my table so that the sql query generated adds the schema in the front in brackets with the table in brackets after, like this:
SELECT DISTINCT [inc].[INCNUM]
FROM [ADM].[INCIDENTS] AS [inc]
Try this
[Table("INCIDENTS", Schema="ADM")]
public class ADMINCIDENTS
{
[Key]
public int INCNUM { get; set; }
}
Take a look at this: http://www.entityframeworktutorial.net/code-first/table-dataannotations-attribute-in-code-first.aspx

Simple inner join result with Dapper?

I can't seem to find documentation or examples for my problem (been searching a while now). I think my problem is pretty straightforward, so here goes.
I have two tables. My primary table is called Persons and the secondary table is PersonEntries. For each person in Person table, i can have 0 or more entries in the PersonEntries table. Like this.
Table: Person
Id
Name
Table: PersonEntry
PersonId
CheckinTime
CheckoutTime
I have two objects like this
public class Person {
public string Name;
public List<PersonEntry> PersonEntries;
}
public class PersonEntry {
public DateTime CheckinTime;
public DateTime CheckoutTime;
}
If i was to get it from the database into my c# classes how would i do it? I can map a single table into my c# class and do it for each table, but then i'm left to match what entries maps to what person.
I've seen several examples of mapping ONE PersonEntry to ONE Person, the problem here is that i have a zero-to-many relation. My Person have a LIST of PersonEntry items.
You can do something like this (see https://www.tritac.com/blog/dappernet-by-example):
public class Shop {
public int? Id {get;set;}
public string Name {get;set;}
public string Url {get;set;}
public IList<Account> Accounts {get;set;}
}
public class Account {
public int? Id {get;set;}
public string Name {get;set;}
public string Address {get;set;}
public string Country {get;set;}
public int ShopId {get;set;}
}
var lookup = new Dictionary<int, Shop>()
conn.Query<Shop, Account, Shop>(#"
SELECT s.*, a.*
FROM Shop s
INNER JOIN Account a ON s.ShopId = a.ShopId
", (s, a) => {
Shop shop;
if (!lookup.TryGetValue(s.Id, out shop)) {
lookup.Add(s.Id, shop = s);
}
if (shop.Accounts == null)
shop.Accounts = new List<Account>();
shop.Accounts.Add(a);
return shop;
}
).AsQueryable();
var resultList = lookup.Values;

JPA2 CriteriaBuilder: Using LOB property for greaterThan comparison

My application is using SQLServer and JPA2 in the backend. App makes use of a timestamp column (in the SQLServer sense, which is equivalent to row version see here) per entity to keep track of freshly modified entities. NB SQLServer stores this column as binary(8).
Each entity has a respective timestamp property, mapped as #Lob, which is the way to go for binary columns:
#Lob
#Column(columnDefinition="timestamp", insertable=false, updatable=false)
public byte[] getTimestamp() {
...
The server sends incremental updates to mobile clients along with the latest database timestamp. The mobile client will then pass the old timestamp back to the server on the next refresh request so that the server knows to return only fresh data. Here's what a typical query (in JPQL) looks like:
select v from Visit v where v.timestamp > :oldTimestamp
Please note that I'm using a byte array as a query parameter and it works fine when implemented in JPQL this way.
My problems begin when trying to do the same using the Criteria API:
private void getFreshVisits(byte[] oldVersion) {
EntityManager em = getEntityManager();
CriteriaQuery<Visit> cq = cb.createQuery(Visit.class);
Root<Visit> root = cq.from(Visit.class);
Predicate tsPred = cb.gt(root.get("timestamp").as(byte[].class), oldVersion); // compiler error
cq.where(tsPred);
...
}
The above will result in compiler error as it requires that the gt method used strictly with Number. One could instead use the greaterThan method which simply requires the params to be Comparable and that would result in yet another compiler error.
So to sum it up, my question is: how can I use the criteria api to add a greaterThan predicate for a byte[] property? Any help will be greatly appreciated.
PS. As to why I'm not using a regular DateTime last_modified column: because of concurrency and the way synchronization is implemented, this approach could result in lost updates. Microsoft's Sync Framework documentation recommends the former approach as well.
I know this was asked a couple of years back but just in case anyone else stumbles upon this.. In order to use a SQLServer rowver column within JPA you need to do a couple of things..
Create a type that will wrap the rowver/timestamp:
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.xml.bind.annotation.XmlTransient;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Arrays;
/**
* A RowVersion object
*/
public class RowVersion implements Serializable, Comparable<RowVersion> {
#XmlTransient
#JsonIgnore
private byte[] rowver;
public RowVersion() {
}
public RowVersion(byte[] internal) {
this.rowver = internal;
}
#XmlTransient
#JsonIgnore
public byte[] getRowver() {
return rowver;
}
public void setRowver(byte[] rowver) {
this.rowver = rowver;
}
#Override
public int compareTo(RowVersion o) {
return new BigInteger(1, rowver).compareTo(new BigInteger(1, o.getRowver()));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RowVersion that = (RowVersion) o;
return Arrays.equals(rowver, that.rowver);
}
#Override
public int hashCode() {
return Arrays.hashCode(rowver);
}
}
The key here is that it implement Comparable if you want to use it in calculations (which you definitely do)..
Next create a AttributeConverter that will move from a byte[] to the class you just made:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
* JPA converter for the RowVersion type
*/
#Converter
public class RowVersionTypeConverter implements AttributeConverter<RowVersion, byte[]> {
#Override
public byte[] convertToDatabaseColumn(RowVersion attribute) {
return attribute != null ? attribute.getRowver() : null;
}
#Override
public RowVersion convertToEntityAttribute(byte[] dbData) {
return new RowVersion(dbData);
}
}
Now let's apply this RowVersion attribute/type to a real world scenario. Let's say you wanted to find all Programs that have changed on or before some point in time.
One straightforward way to solve this would be to use a DateTime field in the object and timestamp column within db. Then you would use 'where lastUpdatedDate <= :date'.
Suppose that you don't have that timestamp column or there's no guarantee that it will be updated properly when changes are made; or let's say your shop loves SQLServer and wants to use rowver instead.
What to do? There are two issues to solve.. one how to generate a rowver and two is how to use the generated rowver to find Programs.
Since the database generates the rowver, you can either ask the db for the 'current max rowver' (a custom sql server thing) or you can simply save an object that has a RowVersion attribute and then use that object's generated RowVersion as the boundary for the query to find the Programs changed after that time. The latter solution is more portable is what the solution is below.
The SyncPoint class snippet below is the object that is used as a 'point in time' kind of deal. So once a SyncPoint is saved, the RowVersion attached to it is the db version at the time it was saved.
Here is the SyncPoint snippet. Notice the annotation to specify the custom converter (don't forget to make the column insertable = false, updateable = false):
/**
* A sample super class that uses RowVersion
*/
#MappedSuperclass
public abstract class SyncPoint {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// type is rowver for SQLServer, blob(8) for postgresql and h2
#Column(name = "current_database_version", insertable = false, updatable = false)
#Convert(converter = RowVersionTypeConverter.class)
private RowVersion currentDatabaseVersion;
#Column(name = "created_date_utc", columnDefinition = "timestamp", nullable = false)
private DateTime createdDate;
...
Also (for this example) here is the Program object we want to find:
#Entity
#Table(name = "program_table")
public class Program {
#Id
private Integer id;
private boolean active;
// type is rowver for SQLServer, blob(8) for postgresql and h2
#Column(name = "rowver", insertable = false, updatable = false)
#Convert(converter = RowVersionTypeConverter.class)
private RowVersion currentDatabaseVersion;
#Column(name = "last_chng_dt")
private DateTime lastUpdatedDate;
...
Now you can use these fields within your JPA criteria queries just like anything else.. here is a snippet that we used inside a spring-data Specifications class:
/**
* Find Programs changed after a synchronization point
*
* #param filter that has the changedAfter sync point
* #return a specification or null
*/
public Specification<Program> changedBeforeOrEqualTo(final ProgramSearchFilter filter) {
return new Specification<Program>() {
#Override
public Predicate toPredicate(Root<Program> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
if (filter != null && filter.changedAfter() != null) {
// load the SyncPoint from the db to get the rowver column populated
SyncPoint fromDb = synchronizationPersistence.reload(filter.changedBeforeOrEqualTo());
if (fromDb != null) {
// real sync point made by database
if (fromDb.getCurrentDatabaseVersion() != null) {
// use binary version
return cb.lessThanOrEqualTo(root.get(Program_.currentDatabaseVersion),
fromDb.getCurrentDatabaseVersion());
} else if (fromDb.getCreatedDate() != null) {
// use timestamp instead of binary version cause db doesn't make one
return cb.lessThanOrEqualTo(root.get(Program_.lastUpdatedDate),
fromDb.getCreatedDate());
}
}
}
return null;
}
};
}
The specification above works with both the binary current database version or a timestamp.. this way I could test my stuff and all the upstream code on a database other than SQLServer.
That's it really: a) type to wrap the byte[] b) JPA converter c) use attribute in query.

Map null column as 0 in a legacy database (JPA)

Using Play! framework and it's JPASupport class I have run into a problem with a legacy database.
I have the following class:
#Entity
#Table(name="product_catalog")
public class ProductCatalog extends JPASupport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer product_catalog;
#OneToOne
#JoinColumn(name="upper_catalog")
public ProductCatalog upper_catalog;
public String name;
}
Some product catalogs don't have an upper catalog, and this is referenced as 0 in a legacy database. If I supply the upper_catalog as NULL, then expectedly JPA inserts a NULL value to that database column.
How could I force the null values to be 0 when writing to the database and the other way around when reading from the database?
I don't see any easy way of achieving what you want with JPA directly (and there are great chance that even if you find a way that works with basic operation like save or load, that it will not work with more complex use case, like complex criteria / hql, none standard fetching mode, etc)
So i would do that :
#Entity
#Table(name="product_catalog")
public class ProductCatalog extends JPASupport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer product_catalog;
#Column(name="upper_catalog")
public Long upper_catalog_id;
public String name;
public ProductCatalog getUpperCatalog() {
if (upper_catalog_id == 0)
return null;
return ProductCatalog.findById(upper_catalog_id);
}
public void setUpperCatalog(ProductCatalog pc) {
if (pc == null) {
upper_catalog_id = 0;
}
else {
if (pc.id == null) {
// option 1. a bit like a cascade
pc.save();
// option 2. if you consider passing a transient entity is not valid
throw new RuntimeException("transient entity " + pc.toString());
}
upper_catalog_id = pc.id;
}
}
}
I see two options:
Use a primitive data type as Id (i.e. int instead of Integer)
If you are using Hibernate as JPA provider, use a CustomType to do the conversion

Resources