How to check record is fully empty on left join in jooq query - spring-boot

I try to fetch a record from table with left join on another table. An information in the second table can be not found but I expect an information from the first table.
val citizenship = Tables.COUNTRIES.`as`("citizenship")
try {
return context.selectFrom(Tables.CLIENT_PROJECTIONS
.leftJoin(citizenship).on(
Tables.CLIENT_PROJECTIONS.CITIZENSHIP_COUNTRY_CODE.eq(
citizenship.CODE_ALPHA2
)
)
).where(Tables.CLIENT_PROJECTIONS.ID.eq(id)).fetchOne {
val clientProjection = ClientProjectionMapper.map(it.into(Tables.CLIENT_PROJECTIONS)) ?: return#fetchOne null
clientProjection.citizenship = CountryMapper.map(it.into(citizenship))
clientProjection
}
} catch (ex: DataAccessException) {
logger.error("Failed to access to database", ex)
throw ex
}
I convert data from CountriesRecord to Entity in CountryMapper:
object CountryMapper : RecordMapper<CountriesRecord, Country> {
override fun map(record: CountriesRecord?): Country? = when {
record != null -> {
Country(
countryCode = record.codeAlpha,
title = record.title
)
}
else -> {
null
}
}
}
But if query returns null in every fields of CountriesRecord my map method receive a non-nullable entity but everyone fields of this entity is empty.
I can check every field of CountriesRecord is it null but i think that isn't good idea. Can I check it by another more best way? May be I should write more correct query to database?

A LEFT JOIN in SQL does exactly that. It produces nulls for all columns of the left joined table for which there was no match in the join's ON clause.
You don't have to check whether each column is null. The primary key will be good enough, because that should have a NOT NULL constraint on it, meaning that if the primary key value is null (record.codeAlpha), then that must be because of the LEFT JOIN.
Change your second mapper to this:
object CountryMapper : RecordMapper<CountriesRecord, Country> {
override fun map(record: CountriesRecord?): Country? = when {
record?.codeAlpha != null -> {
Country(
countryCode = record.codeAlpha,
title = record.title
)
}
else -> {
null
}
}
}

Related

Setting default for DefaultIfEmpty method in LINQ when the default has multiple with-many relationships?

I have three objects:
Event (relationships: has many EventProducts)
Product (relationships: has many EventProducts)
EventProduct (relationships: with many Events, with many Products, has properties like quantity_sold, quantity_allocated, etc which should be stored per product and per event)
In my application, when someone clicks an Event, a new window initializes with a list of all products. From there they can modify cells I need to populate a datagrid in WPF such that when someone clicks an event. So in the context of this code, the Event is known and constant.
The issue I'm having is with creating default types of EventProduct.
What I need is to have a query where if there is no EventProduct in the DB, it will instantiate an EventProduct with EventProduct.Event = currentEvent (currentEvent will be constant for all EventProducts created with this query) and EventProduct.Product = product (product will change for each row)
This code works well when there is an associated EventProduct in the database. But if there isn't, my selection returns my Product just fine but the entire EventProduct is null.
var query2 = from product in dbContext.Products
join eventProduct in dbContext.EventProducts
on new { pIndex = product.index, eIndex = currentEvent.index }
equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
from eventProduct in temp.DefaultIfEmpty() // this is my problem line
select new {
Product = product,
EventProduct = eventProduct
};
I have tried creating a constructor for EventProduct(Event e, Product p) and passing in the values to the constructor in my DefaultIfEmpty() method but I get errors that my constructor has to have 0 arguments to be used in that way. I can't do that because there is no way to tell my EventProduct() object which Event and Product it should be associated with if I do that.
I have also tried no constructor, just creating a new EventProduct and setting its properties but I get the error "The entity or complex type ...EventProduct cannot be constructed in a LINQ to Entites query".
At the end result, I want to select my Product and EventProduct. If there is no EventProduct associated with both that Product and Event, then my EventProduct selection should be set to a default which has the currentEvent, the current row's Product, and all properties set to a default value (all are decimals and should be 0 in this case).
EDIT: I've just tried this query and it also gives me an Unsupported error:
var query2 = from product in dbContext.Products
join eventProduct in dbContext.EventProducts
on new { pIndex = product.index, eIndex = currentEvent.index }
equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
from eventProduct in temp.DefaultIfEmpty()
select new {
Product = product,
EventProduct = eventProduct != null ?
eventProduct : new EventProduct
{
Product = product,
Event = currentEvent,
quantity_allocated = 0,
quantity_sold = 0,
quantity_sampled = 0
}
};
EDIT: Resolved with this technique:
1) Create an object because anonymous objects are read-only:
class Associations
{
public class ProductEventProduct
{
public Product Product { get; set; }
public EventProduct EventProduct { get; set; }
}
}
2) Foreach null object in the dataset, replace with a default object
var query = from product in dbContext.Products
join eventProduct in dbContext.EventProducts
on new { pIndex = product.index, eIndex = currentEvent.index }
equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
from eventProduct in temp.DefaultIfEmpty()
select new Associations.ProductEventProduct {
Product = product,
EventProduct = eventProduct
};
var dataSource = query.ToList();
foreach (Associations.ProductEventProduct entry in dataSource)
{
if (entry.EventProduct == null)
{
entry.EventProduct = new EventProduct
{
Product = entry.Product,
Event = currentEvent,
quantity_allocated = 0,
quantity_sold = 0,
quantity_sampled = 0
};
}
}
It appears that the issue you are having is that you are asking Entity Framework to create a query that it doesn't know how to create.
Keep in mind that Linq uses deferred execution, so when you write your query, it's just a placeholder of where and how to get the data rather than the actual data. It isn't until some other code asks for the data that the query actually runs and your information is populated.
For Linq to Entities, this means it will build up a query in the DB language and hold on to it until you need the data, then execute the query as you would expect through whatever DB provider you are using when you call for it, and store it in memory to work against.
So when you try to construct a new EventProduct in your query to fill the null as you have it, the DB doesn't know what that is and you get the error, since you are still in the part of the query where the DB is responsible for objects. The DB Provider doesn't know how to construct that new object, so EF can't translate the query and you get the error.
The solution is to "hydrate" your information before you try to construct the new EventProduct, by running your query sans EventProduct constructor through a call to ToList() or ToArray or similar, which forces the query to run right then.
Then, AFTER the data is hydrated (in memory), go through all of the objects you created in the query and if the EventProduct is null, construct a new one and add it dynamically, however you want to do it. (A Second query with a Select statement maybe?) This should get you around the error you mentioned.
I'm a bit out of touch on if your original query, which produces anonymous objects, would be tracked by the DBContext or not, but keep that in mind as well and test for it. If you add a bunch of EventProduct instances that are tied to those anonymous objects, you could have those included by default in tracking, and a call to SaveChanges() might inadvertently write them all to the DB when you weren't intending to do so. Something to keep in mind.
Hope this helps, let me know!
Have you tried setting default value in case of null in the select clause?
......
select new
{
Product = product,
EventProduct = eventProduct != null
? eventProduct
: new EventProduct { .... set default values}
}
UPD
There are possible workarounds to this problem, either introduce DTO (or another anonymous type) as this
answer suggests:
select new
{
Product = product,
EventProduct = new
{
Product = product,
EventId = currentEvent.Id, // possibly you will need to copy every field by hand
quantity_allocated = eventProduct == null ? 0 : eventProduct .quantity_allocated ,
....
}
}
Or just replace nulls after the query to db cause you should have all the needed data already:
var query2 = from product in dbContext.Products
join eventProduct in dbContext.EventProducts
on new { pIndex = product.index, eIndex = currentEvent.index }
equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
from eventProduct in temp.DefaultIfEmpty()
select new {
Product = product,
EventProduct = eventProduct
};
var results = query2.ToList();
foreach(var r in results)
{
r.EventProduct = r.EventProduct != null
? r.EventProduct
: new EventProduct { .... set default values}
}

BootstrapVue table : sort by date and by string?

I am quite new to VueJS, and currently using BootstrapVue (latest version, v2.0.0), mostly its b-table feature. I load table items dynamically (from a JSON file), and one of my field (column) is a string, the other is a formatted date (dd/MM/YYYY). I would like be able to sort those dates like other string or number fields.
The doc mention the possibility to create custom sorting function, so I wrote one (as a global function, using moment.js as suggested) :
function sortDate(a, b, key) {
aDate = moment(a[key], 'DD/MM/YYYY')
bDate = moment(b[key], 'DD/MM/YYYY')
if (aDate.isValid && bDate.isValid) {
if (aDate < bDate) {
return -1
}
else if (aDate > bDate) {
return 1
}
else {
return 0
}
}
return null
}
I then integrate it to HTML b-table using the :sort-compare tag :
<b-table id="bh_table" :items="items" :fields="fields" :sort-compare="sortDate"></b-table>
The problem is that the regulat string-sorting is broken, and I am not sure how to fix it ? Should I create a global method that should detect column type, and sort accordingly ?
It seems to be the thing to do here, but I think it is quite counter-intuitive, getting possible duplicates (I have other table that contains number and dates, only dates, etc.)
You are not checking for which key is being sorted on. Also note a and b are the entire row data.
function sortDate(a, b, key) {
if (key !== 'myDateField') {
// `key` is not the field that is a date.
// Let b-table handle the sorting for other columns
// returning null or false will tell b-table to fall back to it's
// internal sort compare routine for fields keys other than `myDateField`
return null // or false
}
aDate = moment(a[key], 'DD/MM/YYYY')
bDate = moment(b[key], 'DD/MM/YYYY')
if (aDate.isValid && bDate.isValid) {
if (aDate < bDate) {
return -1
}
else if (aDate > bDate) {
return 1
}
else {
return 0
}
}
return null
}

Many-To-Many Entity Framework Update

I have an object that has a many-to-many relationship with another object. I am trying to write an update statement that doesn't result in having to delete all records from the many-to-many table first.
My data is:
StoredProcedure - StoredProcedureId, Name
Parameter - ParameterId, Name
StoredProcedure_Parameter - StoredProcedureId, ParameterId, Order
I have a UI for updating a stored procedured object (adding/removing parameters or changing the order of the parameters).
When I save, I end up at:
var storedProcedure = context.Sprocs.FirstOrDefault(s => s.SprocID == sproc.StoredProcedureId);
if (storedProcedure == null)
{
//do something like throw an exception
} else
{
storedProcedure.Name = sproc.Name;
//resolve Parameters many to many here
//remove all Params that are not in sproc.Params
//Add any params that are in sproc.Params but not in storedProcedure.Params
//Update the Order number for any that are in both
}
I know I could simply call .Clear() on the table and then reinsert all of the values with their current state (ensuring that all parameters that were removed by the UI are gone, new ones are added, and updated Orders are changed). However, I feel like there must be a better way to do this. Do many-to-many updates with EF usually get resolved by deleting all of the elements and reinserting them?
Here there is my code that I use and it works. The difference is that instead o having your 3 tables( StoredProcedure, StoredProcedure_Parameter and Parameter ) I have the following 3 tables: Order, OrdersItem(this ensure the many-to-many relation) and Item. This is the procedure that I used for updating or add an order, or after I change an existing OrderItem or add a new one to the Order.
public void AddUpdateOrder(Order order)
{
using (var db = new vitalEntities())
{
if (order.OrderId == 0)
{
db.Entry(order).State = EntityState.Added;
}
else
{
foreach (var orderItem in order.OrdersItems)
{
if (orderItem.OrderItemsId == 0)
{
orderItem.Item = null;
if (order.OrderId != 0)
orderItem.OrderId = order.OrderId;
db.Entry(orderItem).State = EntityState.Added;
}
else
{
orderItem.Order = null;
orderItem.Item = null;
db.OrdersItems.Attach(orderItem);
db.Entry(orderItem).State = EntityState.Modified;
}
}
db.Orders.Attach(order);
db.Entry(order).State = EntityState.Modified;
}
SaveChanges(db);
}
}

LINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method

I've looked at the various solutions here but none of them seem to work for me, probably because I'm too new to all this and am groping in the dark a bit. In the code below, the object "appointment" contains some basic LDAP information. From a list of such objects I want to be able to get a single record, based on employee id. I hope the code here is sufficient to illustrate. FTR, I've tried various formulations, including trying to use from and a select. All fail with the error given in the Title above.
IQueryable<appointment> query = null;
foreach(var record in results)
{
BoiseStateLdapDataObject record1 = record;
query = db.appointments.Where(x => x.student_id == record1.Values["employeeid"]);
}
if (query != null)
{
var selectedRecord = query.SingleOrDefault();
}
Try to move employee id getting out of query:
IQueryable<appointment> query = null;
foreach(var record in results)
{
var employeeId = record.Values["employeeid"];
query = db.appointments.Where(x => x.student_id == employeeId);
}
if (query != null)
{
var selectedRecord = query.SingleOrDefault();
}

Explicit construction of entity type in query is not allowed [duplicate]

Using Linq commands and Linq To SQL datacontext, Im trying to instance an Entity called "Produccion" from my datacontext in this way:
Demo.View.Data.PRODUCCION pocoProduccion =
(
from m in db.MEDICOXPROMOTORs
join a in db.ATENCIONs on m.cmp equals a.cmp
join e in db.EXAMENXATENCIONs on a.numeroatencion equals e.numeroatencion
join c in db.CITAs on e.numerocita equals c.numerocita
where e.codigo == codigoExamenxAtencion
select new Demo.View.Data.PRODUCCION
{
cmp = a.cmp,
bonificacion = comi,
valorventa = precioEstudio,
codigoestudio = lblCodigoEstudio.Content.ToString(),
codigopaciente = Convert.ToInt32(lblCodigoPaciente.Content.ToString()),
codigoproduccion = Convert.ToInt32(lblNroInforme.Content.ToString()),
codigopromotor = m.codigopromotor,
fecha = Convert.ToDateTime(DateTime.Today.ToShortDateString()),
numeroinforme = Convert.ToInt32(lblNroInforme.Content.ToString()),
revisado = false,
codigozona = (c.codigozona.Value == null ? Convert.ToInt32(c.codigozona) : 0),
codigoclinica = Convert.ToInt32(c.codigoclinica),
codigoclase = e.codigoclase,
}
).FirstOrDefault();
While executing the above code, I'm getting the following error that the stack trace is included:
System.NotSupportedException was caught
Message="The explicit construction of the entity type 'Demo.View.Data.PRODUCCION' in a query is not allowed."
Source="System.Data.Linq"
StackTrace:
en System.Data.Linq.SqlClient.QueryConverter.VisitMemberInit(MemberInitExpression init)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.Visit(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.VisitSelect(Expression sequence, LambdaExpression selector)
en System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitMethodCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.Visit(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.VisitFirst(Expression sequence, LambdaExpression lambda, Boolean isFirst)
en System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitMethodCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.ConvertOuter(Expression node)
en System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
en System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
en System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
en System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
en Demo.View.InformeMedico.realizarProduccionInforme(Int32 codigoExamenxAtencion, Double precioEstudio, Int32 comi) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 602
en Demo.View.InformeMedico.UpdateEstadoEstudio(Int32 codigo, Char state) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 591
en Demo.View.InformeMedico.btnGuardar_Click(Object sender, RoutedEventArgs e) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 683
InnerException:
Is that now allowed in LINQ2SQL?
Entities can be created outside of queries and inserted into the data store using a DataContext. You can then retrieve them using queries. However, you can't create entities as part of a query.
I am finding this limitation to be very annoying, and going against the common trend of not using SELECT * in queries.
Still with c# anonymous types there is a workaround, by fetching the objects into an anonymous type, and then copy it over into the correct type.
For example:
var q = from emp in employees where emp.ID !=0
select new {Name = emp.First + " " + emp.Last, EmployeeId = emp.ID }
var r = q.ToList();
List<User> users = new List<User>(r.Select(new User
{
Name = r.Name,
EmployeeId = r.EmployeeId
}));
And in the case when we deal with a single value (as in the situation described in the question) it is even easier, and we just need to copy directly the values:
var q = from emp in employees where emp.ID !=0
select new { Name = emp.First + " " + emp.Last, EmployeeId = emp.ID }
var r = q.FirstOrDefault();
User user = new User { Name = r.Name, EmployeeId = r.ID };
If the name of the properties match the database columns we can do it even simpler in the query, by doing select
var q = from emp in employees where emp.ID !=0
select new { emp.First, emp.Last, emp.ID }
One might go ahead and write a lambda expression that can copy automatically based on the property name, without needing to specify the values explictly.
Here's another workaround:
Make a class that derives from your LINQ to SQL class. I'm assuming that the L2S class that you want to return is Order:
internal class OrderView : Order { }
Now write the query this way:
var query = from o in db.Order
select new OrderView // instead of Order
{
OrderID = o.OrderID,
OrderDate = o.OrderDate,
// etc.
};
Cast the result back into Order, like this:
return query.Cast<Order>().ToList(); // or .FirstOrDefault()
(or use something more sensible, like BLToolkit / LINQ to DB)
Note: I haven't tested to see if tracking works or not; it works to retrieve data, which is what I needed.
I have found that if you do a .ToList() on the query before trying to contruct new objects it works
I just ran into the same issue.
I found a very easy solution.
var a = att as Attachment;
Func<Culture, AttachmentCulture> make =
c => new AttachmentCulture { Culture = c };
var culs = from c in dc.Cultures
let ac = c.AttachmentCultures.SingleOrDefault(
x => x.Attachment == a)
select ac == null ? make(c) : ac;
return culs;
I construct an anonymous type, use IEnumerable (which preserves deferred execution), and then re-consruct the datacontext object. Both Employee and Manager are datacontext objects:
var q = dc.Employees.Where(p => p.IsManager == 1)
.Select(p => new { Id = p.Id, Name = p.Name })
.AsEnumerable()
.Select(item => new Manager() { Id = item.Id, Name = item.Name });
Within the book "70-515 Web Applications Development with Microsoft .NET Framework 4 - Self paced training kit", page 638 has the following example to output results to a strongly typed object:
IEnumerable<User> users = from emp in employees where emp.ID !=0
select new User
{
Name = emp.First + " " + emp.Last,
EmployeeId = emp.ID
}
Mark Pecks advice appears to contradict this book - however, for me this example still displays the above error as well, leaving me somewhat confused. Is this linked to version differences? Any suggestions welcome.
I found another workaround for the problem that even lets you retain your result as IQueryale, so it doesn't actually execute the query until you want it to be executed (like it would with the ToList() method).
So linq doesn't allow you to create an entity as a part of query? You can shift that task to the database itself and create a function that will grab the data you want. After you import the function to your data context, you just need to set the result type to the one you want.
I found out about this when I had to write a piece of code that would produce a IQueryable<T> in which the items don't actually exist in the table containing T.
pbz posted a work around by creating a View class inherited from an entity class that you could be working with. I'm working with a dbml model of a table that has > 200 columns. When I try and return the whole table I get "Root Element missing" errors. I couldn't find anyone who wanted to deal with my particular issue so I was looking at rewriting my entire approach. Just creating a view class for the entitiy class worked in my case.
As pbz suggests : Create a view class that inherits from your entity class. For me this is tbCamp so :
internal class tbCampView : tbCamp
{
}
Then use the view class in your query :
using (var dc = ConnectionClass.Connect(Dev))
{
var camps = dc.tbCamps.Select(s => new tbCampView
{
active = s.active,
idCamp = s.idCamp,
campName = s.campName
});
SmartTableViewer(camps, dg1);
}
private void SmartTableViewer<T>(IEnumerable<T> allRecords)
{
// Build sorted rows back into new table
var table = new DataTable();
// Create columns based on type
if (allRecords is IEnumerable<tbCamp> tbCampRecords)
{
// Get the columns you want
table.Columns.Add("idCamp");
table.Columns.Add("campName");
foreach (var record in tbCampRecords)
{
// Make a new row
var r = table.NewRow();
// Add the contents to each column of the row
r["idCamp"] = record.idCamp;
r["campName"] = record.campName;
// Add the row to the table.
table.Rows.Add(r);
}
}
else
{
MessageBox.Show("Unhandled type. Add support for new data type in SmartTableViewer()");
return;
}
// Update table in grid
dg1.DataSource = table.DefaultView;
}
Here is what happens when you try and create an entity class object in the query.
I didn't want to have to use an anonymous type if I could help it because I wanted the type to be tbCamp. Since tbCampView is of type tbCamp the is operator works well. see Brian Hasden's answer Passing a generic List<> in C#
I'm surprised this is even an issue but with larger tables I run into this error so I thought I would just show it here :
When trying to read this table into memory I get the following error. There are < 2000 rows but the columns are > 200 for each. I don't know if that is an issue or not.
If I just want a few columns I need to create a custom class and handle that which isn't that big of a pain. With the approach pbz provided I don't have to worry about it.
Here is the entire project in case it helps someone.
public partial class Form1 : Form
{
private const bool Dev = true;
public Form1()
{
InitializeComponent();
}
private void btnGetAllCamps_Click(object sender, EventArgs e)
{
using (var dc = ConnectionClass.Connect(Dev))
{
IQueryable<tbCampView> camps = dc.tbCamps.Select(s => new tbCampView
{
// Project columns as needed.
active = s.active,
idCamp = s.idCamp,
campName = s.campName
});
// pass in as a
SmartTableViewer(camps);
}
}
private void SmartTableViewer<T>(IEnumerable<T> allRecords)
{
// Build sorted rows back into new table
var table = new DataTable();
// Create columns based on type
if (allRecords is IEnumerable<tbCamp> tbCampRecords)
{
// Get the columns you want
table.Columns.Add("idCamp");
table.Columns.Add("campName");
foreach (var record in tbCampRecords)
{
//var newRecord = record;
// Make a new row
var r = table.NewRow();
// Add the contents to each column of the row
r["idCamp"] = record.idCamp;
r["campName"] = record.campName;
// Add the row to the table.
table.Rows.Add(r);
}
}
else
{
MessageBox.Show("Unhandled type. Add support for new data type in SmartTableViewer()");
return;
}
// Update table in grid
dg1.DataSource = table.DefaultView;
}
internal class tbCampView : tbCamp
{
}
}

Resources