Grails 3: using findAll with join tables - sql

In my Grails webapp I have the following domain classes:
class Customer {
static hasMany = [
addresses: Address
]
static mapping = {
addresses (cascade: "all-delete-orphan", joinTable: [name: "bs_core_customer_addresses"])
}
}
class Address {
...
}
Now I want to implement the abillity to filter the 1:n relation like addresses for things like country (should be dynamic, that means the user can add different filters by itself).
What is the best way to accomplish this?
Filtering the collection from the Customer object? e.g. customer.addresses.findAll{...}
Direct query from the database? How can I add the restriction for the Customer<->Address relation. belongsTo at the Address domain class is no option because the Address object is used in several 1:n relations. e.g. Customer.findAll(...)
Any other option?

you should be able to get away with
static constraints = {
addresses(validator: checkAddress)
}
// This is a static method which is used for validation
// and can be used for when inserting a record to check how many
// existing addresses exist for the end user that has countryCode of US
// This is directly bound to all the object the user and will
// will not be searching the entire DB (A local find limited to user records)
static def checkAddress={val,obj,errors->
if (!obj?.addresses.findAll{it.countryCode=='US'}?.size() >= 2) {
return errors.rejectValue('adress','exceeds limit')
}
}
The above should be self explanatory, but having read through your post a few times now I think I have a better understanding of what you are trying to achieve and there are probably a few different ways of doing it. So let's explore some of them:
Using HQL query, you could change this to another method, I prefer HQL.
class Customer {
def userService
//UserAddress does not have setter transients not essential
static transients = ['userAddress','userService']
//This is a protected function that will return an address
// object given a countryCode
// Execute like this:
// Customer cm = Customer.get(customer.id as Long)
//Address usa = cm.getCountry('US')
protected Address getUserAddress(String countryCode) {
return userService.findAddress(countryCode, this.id)
}
}
Now the service but actually you don't need to execute in domain class unless there is some other need, for displaying etc you could always call this sort of service from within a controller call to render for display purposes
class UserSerice {
// This should return the entire address object back to the domain class
// that called it and will be quicker more efficient than findAll
Address findAddress(String countryCode, Long customerId) {
String query="""
select address from Address a
where a.id :id and countryCode = :code
"""
def inputParams=[code:countryCode, id:customerId]
return Address.executeQuery(query,inputParams,[readOnly:true,timeout:15])
}
Another approach could be a 3rd table that gets updated upon each address added that would give a quick lookup:
class Customer {
static hasMany = [
addresses: Address
//probably don't even need this
//, userCountries:UserCountries
]
}
Class UserCountries {
// register customer
Customer customer
String CountryCode
//maybe address object or Long addressId - depending on if how plain you wanted this to be
Address address
}
Then register the address id and countryCode to this domainclass each time you add a new address and I guess you would need to write some backward compatible code to add existing records to this table for it to work properly.
I left a comment and then removed it for you to expand further on what or how the filtering was taking place. since although you talk of countryCode there is no actual code to show how it all fits in.
I still think something as simple as this would work
//This would only be doing a find with all the bound objects of addresses bound to this customer. so a find within the hasMany relationship elements of this specific customer
protected def getCustomAddress(String countryCode) {
return addresses.findAll{it.code==countryCode}
}
Other far out ideas could be something like this
class Customer {
String _bindAddress
List bindAddress=[]
static transients = [ 'bindAddress' ]
static constraints = {
_bindAddress(nullable:true)
}
//you store a flat CSV as _bindAddress
//you need to work out your own logic to ammend to existing CSV each time address is added
// you will also update _bindAddress of this domainClass each time customer gets a hasMany address added
// so no need for setBindAddress
// void setBindAddress(String b) {
// bindAddress=b.split(',')
// }
//Inorder to set a list back to flat file
//pass in list object
void setBindAddress(List bindAddress) {
_bindAddress=bindAddress.join(',')
/for 1 element this captures better
//_bindAddress=bindAddress.tokenize(',').collect{it}
}
//This is now your object as a list that you can query for what you are querying.
List getBindAdress() {
return _bindAddress.split(',')
}
}
If your actual csv list contained a listing of 'COUNTRY_CODE-ADDRESS_ID' then you could query like this
def found = customer.bindAddress.find{it.startsWith('US-')}
Address usaAddress= Address.get(found.split('-')[1] as Long)
//Slightly longer explaining above:
def found = customer.bindAddress.find{it.startsWith('US-')}
def record = found.split('-')
String countryCode=record[0]
Long addressId=record[1] as Long
Address usaAddress= Address.get(addressId)

Related

Nhibernate mapping at run time

I am developing a site in which nhibernate is using. that is working fine for static mapping. but problem that i apply this application on existing database. so is there any way that mapping of classes took place at run time. i mean user provide tables and column names for mapping. Thanks
From your question I interpret you saying that the POCO classes exists, but you don't know the table or column names at build time.
So, if you already had this class:
public class MyGenericClass
{
public virtual long Id { get; set; }
public virtual string Title { get; set; }
}
You could bind it to a table and columns at runtime:
string tableName; // Set somewhere else by user input
string idColumnName; // Set somewhere else by user input
string titleColumnName; // Set somewhere else by user input
var configuration = new NHibernate.Cfg.Configuration();
configuration.Configure();
var mapper = new NHibernate.Mapping.ByCode.ModelMapper();
mapper.Class<MyGenericClass>(
classMapper =>
{
classMapper.Table(tableName);
classMapper.Id(
myGenericClass => myGenericClass.Id,
idMapper =>
{
idMapper.Column(idColumnName);
idMapper.Generator(Generators.Identity);
}
);
classMapper.Property(c => c.Title,
propertyMapper =>
{
propertyMapper.Column(titleColumnName);
}
);
}
);
ISessionFactory sessionFactory = configuration.BuildSessionFactory();
ISession session = sessionFactory.OpenSession();
////////////////////////////////////////////////////////////////////
// Now we can run an SQL query over this newly specified table
//
List<MyGenericClass> items = session.QueryOver<MyGenericClass>().List();
I don't think that could be possibly with NHibernate, but you could use a workaround.
You could use a view instead a table for the NHibernate mapping.
And in runtime, you could create that View or update it with the especified user mapping you need.
For example, you define a mapping in NHibernate to a view named ViewMapped with two columns Name and Mail.
And in the other hand, the user has a table with three columns Name, SecondName, EMail.
you can create a view on runtime with the following select:
(SELECT Name + ' ' + SecondName as Name, EMail as Mail FROM tableName) AS ViewMapped
I hope that helps you, or at least leads you to a solution.

Returning composite class with Neo4jClient

The Neop4jClient cypher wiki (https://github.com/Readify/Neo4jClient/wiki/cypher) contains an example of using lambda expressions to return multiple projections...
var query = client
.Cypher
.Start(new { root = client.RootNode })
.Match("root-[:HAS_BOOK]->book-[:PUBLISHED_BY]->publisher")
.Return((book, publisher) => new {
Book = book.As<Book>(),
Publisher = publisher.As<Publisher>(),
});
So the query will return details of both book nodes and publisher nodes. But I want to do something slightly different. I want to combine the contents of a single node type with a property of the matched path. Lets say I have Person nodes with a property 'name', and a class defined so,,,
public class descendant
{
public string name { get; set; }
public int depth { get; set; }
}
A cypher query like this will return what I want, which is all descendants of a given node with the depth of the relationship...
match p=(n:Person)<-[*]-(child:Person)
where n.name='George'
return distinct child.name as name, length(p) as depth
If I try a Neo4jClient query like this...
var query =
_graphClient.Cypher
.Match("p=(n:Person)<-[*]-(child:Person)")
.Where("n.name='George'")
.Return<descendant>("child.name, length(p)") ;
I get an error that the syntax is obsolete, but I can't figure out how should I project the cypher results onto my C# POCO. Any ideas anyone?
The query should look like this:
var query =
_graphClient.Cypher
.Match("p=(n:Person)<-[*]-(child:Person)")
.Where((Person n) => n.name == "George")
.Return((n,p) => new descendant
{
name = n.As<Person>().Name,
depth = p.Length()
});
The Return statement should have the 2 parameters you care about (in this case n and p) and project them via the lambda syntax (=>) to create a new descendant instance.
The main point this differs from the example, is that the example creates a new anonymous type, whereas you want to create a concrete type.
We then use the property initializer (code inside the { } braces) to set the name and depth, using the As<> and Length extension methods to get the values you want.
As a side note, I've also changed the Where clause to use parameters, you should always do this if you can, it will make your queries both faster and safer.

Can you use RequestFactory's .with() method with named queries?

I'm trying to make a call to a database using RequestFactory with Hibernate/JPA, and I want to retrieve a list of entities with embedded entities returned as well. I know that the .with() method works for methods like .find(), but it doesn't seem to work with custom queries.
The current way I'm doing it is as follows:
I used a named query in the entity class for the query. (Primary Entity is Name, embedded entity is a Suffix entity called nameSuffix)
#NamedQueries({ #NamedQuery(name = "Query.name", query = "select * from NameTable") })
Then in the service class, the .list() method, which is what I'd like to call with RequestFactory, is as follows.
public List<Name> list() {
return emp.get().createNamedQuery("Query.name").getResultList();
}
Finally, this is how I make the call in my client side code:
NameRequest context = requestFactory.createNameRequest();
context.list().with("nameSuffix").fire(new Receiver<List<NameProxy>>(){
public void onSuccess(List<NameProxy> response) {
String suff = response.get(0).getNameSuffix().getText();
}
});
In the above code, it says that getNameSuffix() returns null, which would imply that .with("nameSuffix") does not work with the .list() call like it does with the standard .find() method.
Is there a way to build a call that would return a list of entities and their embedded entities using .with(), or do I need to do it another way? If I need to do it another way, has anyone figured out a good way of doing it?
I think you misunderstood what the method with() is thought for, unless you had a method getNameSuffix which returns the NameSuffixentity. This is what the documentation says about it:
When querying the server, RequestFactory does not automatically populate relations in the object graph. To do this, use the with() method on a request and specify the related property name as a String
So, what you have to pass to the method is a list of the name of the child entities you want to retrieve. I hope this example could be helpful:
class A {
String getS(){return "s-a"}
B getB(){return new B();}
}
class B {
String getS(){return "s-b";}
C getC(){return new C();}
}
class C {
String getS(){return "s-c";}
}
context.getA().fire(new Receiver<A>(){
public void onSuccess(A response) {
// return 's-a'
response.getS();
// trhows a NPE
response.getB().getS();
}
});
context.getA().with("b").fire(new Receiver<A>(){
public void onSuccess(A response) {
// return 's-a'
response.getS();
// return 's-b'
response.getB().getS();
// trhows a NPE
response.getB().getC().getS();
}
});
context.getA().with("b.c").fire(new Receiver<A>(){
public void onSuccess(A response) {
// return 's-a'
response.getS();
// return 's-b'
response.getB().getS();
// return 's-c'
response.getB().getC().getS();
}
});

Fluent subclasses - only first and last records are being cast into the correct types

I have a very strange issue that I cannot explain. I have my base mapping with this
//This will automatically cast the row into the correct object type based on the value in AccountType
DiscriminateSubClassesOnColumn<string>("AccountType")
.Formula(String.Format("CASE AccountType WHEN {0} THEN '{1}' WHEN {2} THEN '{3}' ELSE '{4}' END",
(int)PaymentMethodType.CheckingAccount,
typeof(ACH).Name,
(int)PaymentMethodType.SavingsAccount,
typeof(ACH).Name,
typeof(CreditCard).Name));
I have looked in the logs, I have executed the sql that nhibernate is generating, and all records have the same data. There is not difference in them that would denote why this should not work.
The base class is PaymentMethodBase. I have 2 subclasses, CreditCard and ACH, which inherit from PaymentMethodBase.
Then, I have this extension
public static string PaymentMethodName(this PaymentMethodBase paymentMethod)
{
if (paymentMethod is ACH)
{
var ach = (ACH)paymentMethod;
return String.Format("{0} {1}", ach.BankName, String.Format("XXXX{0}", ach.AccountNumber.Substring(ach.AccountNumber.Length - 4)));
}
if (paymentMethod is CreditCard)
{
var creditCard = (CreditCard)paymentMethod;
return String.Format("{0} {1}", creditCard.Name, creditCard.CreditCardNumber);
}
return "Unknown Payment Method";
}
Which I call like this.
public SelectList PaymentMethodsSelectList
{
get
{
var methods = (from p in PaymentMethods
where p != null
select new
{
id = p.PaymentMethodId,
name = p.PaymentMethodName()
}).OrderBy(x => x.name);
var results = methods.ToList();
results.Insert(0, new { id = (int)NewPaymentMethods.ACH, name = "<New eCheck Account...>" });
results.Insert(0, new { id = (int)NewPaymentMethods.CreditCard, name = "<New Credit Card...>" });
return new SelectList(results, "id", "name");
}
}
This code is used by 2 models. The collection of payment methods are all coming from the same object - a customer object. The collection is mapped like this.
HasMany<PaymentMethodBase>(x => x.PaymentMethods)
.KeyColumn("CustomerId")
.Where(y => y.AccountType < 10)
.Inverse()
.Cascade.All();
So, I get the customer 2 different ways. One is that I get the customer through another object (main site). The other has the object being pulled directly by id (in an iframe). The direct by id method works every time. The other method, where I get the customer through another object, causes only the first and last payment method to cast correctly. If there are more than 2, they are left in the base class and appear in the middle of the list after the sort.
I have tried changing the parent object to just mapping the id and then getting the customer record by the ID. Failure. There is something else in the way that is causing this to happen, but only on that one model.
I suspect that the issue here is Fetching issue.
Since the extension method is running on the .Net side rather than in the "NHibernate level" it cannot run properly when the collection of payment methods is not available.
Perhaps in the direct by Id methos you have Fetching set up for the payments whereas in the indirect method the automatic fetching goes only to the Customer object but stops short before fetching the Payment methods.
Try to instruct NHibernate to pre-fetch the Payment methods for you in the indirect method.
Something like:
Session.Query<SomeObject>.Where(.....).Fetch(x => x.Customer).ThenFetch(c => c.PaymentMethods)
The problem here appeared to be the usage of extension methods within a linq statement to return a value based on the type. The issue is that it would work some places but not others, probably due to some fetching issue, as suggested by Variant.
I solved this problem by making a property in my base class like this
public virtual string DisplayName { get { return "Unknown"; } }
Then I overrode the property in my child class and added the logic that was in the extension method for that type.
public override string DisplayName { get { return String.Format("{0} {1}", Name, AccountMask); } }

Simple Delete in Entity Framework using WCF (many to many relationship)

I have a simple database model containing 3 Tables : Companies, Categories and CompanyCategories (which is a relation table with only 2 FK : CompanyID and CategoryID).
My edmx model it only shows Companies and Categories tables (CompanyCategories is somehow hidded since its a simple many to many relationship table).
In the WCF service, I have a GetDatabase() function that returns all the database objets wrapped in one big custom object :
[OperationContract]
public FullDatabase GetDatabase()
{
DBEntities context = new DBEntities ();
FullDatabase mydb = new FullDatabase();
mydb.Companies = context.Companies.ToList();
mydb.Categories = context.Categories.ToList();
return mydb;
}
[OperationContract]
public FullDatabase UpdateDatabase(FullDatabase db)
{
// Here is my problem when removing a category from a company on
// the client its been brought back in my db object
}
class FullDatabase()
{
List<Company> Companies;
List<Category> Categories;
}
On the client now, I use GetDatabaseAsync() to retrieve the database in a _FullDB variable. Now using that variable I tried the following :
// Adding a category like that Works well
Company c = _FullDB.Companies.First();
c.Categories.Add(_FullDB.Categories.First());
wcfServiceClientObject.UpdateDatabaseASync(_FullDB);
.....
// Removing a category, doesn't work though :
Company c = _FullDB.Companies.First();
c.Categories.Remove(_FullDB.Categories.First());
wcfServiceClientObject.UpdateDatabaseASync(_FullDB);
// here my c.Categories.Count is updated correctly to delete the item
// but when on the server after (in the UpdateDatabase function) the item
// I deleted is still there
I really dont understand why the Add would work but not the Remove.
Finally found the problem. Now it works, but I'm not sure it is the best way to do it.
When removing the category from the company, I also had to also remove the company from the category...
Company comp = _FullDB.Companies.First();
Category cat = _FullDB.Categories.First();
comp.Categories.Remove(cat);
cat.Companies.Remove(comp);
wcfServiceClientObject.UpdateDatabaseASync(_FullDB);