Question
I'm trying to use the Dynamic Linq Sample from Microsoft with BindingList<T> objects. But it looks like the Dynamic Linq will only work with IQueryable. What's the deal here, why doesn't BindingList<T> implement IQueryable. And is there a way around this?
Background Detail: I have many data sets that I need to dynamically filter at run time. Here is an example:
BindingList<MyObject> list = new BindingList<MyObject>();
MyObject selectedObj = list.FirstOrDefault(o => o.Name == "Master P")
// then later ...
MyObject selectedObj = list.FirstOrDefault(o => o.City == "Boston")
I am trying to make these queries dynamic, so the user can choose from all properties of MyObject to use in the query.
There is an Extension method on BindingList; AsQueryable(). So you can use
list.AsQueryable();
But if you want to search on all criteria could you create a search that uses an instance of MyObject as the search criteria and then generated a result set based on the Criteria in the object using standard link.
For example:
public List<MyObject> Search(MyObject SearchCriteria)
{
BindingList<MyObject> list = new BindingList<MyObject>();
list.Add(new MyObject("Test", "Boston"));
list.Add(new MyObject("Test2", "Atlanta"));
IEnumerable<MyObject> results = list.AsEnumerable();
if (!String.IsNullOrEmpty(SearchCriteria.Name))
results = results.Where(l => l.Name.Contains(SearchCriteria.Name));
if (!String.IsNullOrEmpty(SearchCriteria.City))
results = results.Where(l => l.City.Contains(SearchCriteria.City));
return results.ToList();
}
So in the following, Results1 will have 2 results and Results 2 will have only 1.
List<MyObject> results1 = Search(new MyObject("Test", ""));
List<MyObject> results2 = Search(new MyObject("Test", "Boston"));
I used a simple structure for MyObject as an example in this:
public class MyObject
{
public MyObject(string name, string city)
{
this.Name = name;
this.City = city;
}
public string Name { get; set; }
public string City { get; set; }
}
Related
I am still new to C# and I am struggling to find a solution to my problem. My SQL dapper query returns a table (based on my understanding though it is not really a table if it is IEnumerable unlike what I am use to working with ADO and recordsets) with three columns col1, col2, and col3 and has multiple rows. I need to loop through this query result for each row and test the values (ie, a foreach loop where I check row(0).field1=5, row(1).field1 = 5 for each row, etc) do what I need to do. This seems so basic but I all the dapper tutorials I see do not show examples for this and if they do they seem to utilize class objects rather than accessing the results directly (if thats even possible or do you have to map the results to a model?) My code is as follows:
String query = "exec dbo.storeProcedure #jsonData, #mainDocJSON, #supportingDocsJSON";
IEnumerable queryResult;
using (var connection = new SqlConnection(connectionString))
{
queryResult = connection.Query(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
}
I also end up returning IEnumerable results from the controller this code resides in so I send it back to the user in JSON using the following.
return Ok(queryResult);
connection.Query return a IEnumerable, why dont we create a class to map the set from ? Dapper is a micro-ORM, but still... ORM.
For ex: Your table return 3 column Id, Name, CreatedDate.
// declare a class to map the result first
public class ResultHolderDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
}
// query somewhere
// This will return IEnumerable<ResultHolderDto>, feel free to play around as normal
var queryResult = await connection.QueryAsync<ResultHolderDto>(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
foreach(var item in queryResult)
{
var col1Value = queryResult.Id;
var col2Value = queryResult.Name;
var col3Value = queryResult.CreatedDate;
// Then do something with col1Value, col2Value, col3Value...
}
My native join query produces a new result that is a combination of database tables, so I created a dto for that resulting object (will be a list of records on a screen).
I believe I need to make it an entity, so JPA can recognize it, would that be the best way to do it?
Also, the entity needs an id, and I was hoping to let jpa generate it auto, but I'm getting "Invalid parameter: Unknown column name id. ERRORCODE=-4460, SQLSTATE=null"
My result set contains 4 of the same records instead of 4 different, and I think it has to do with my id field not set properly
Any help would be appreciated on the subject, thanks.
`public interface ErrorCodeRepo extends JpaRepository<Errors, ErrorsPK> {
#Query("SELECT e.transDate, e.category FROM Errors e")
List<QueuedErrors> findQueuedErrors();
}`
DTO class:
`
public class QueuedErrors {
private String transDate;
private String category;
public QueuedErrors(String transDate, String category) {
this.transDate = transDate;
this.category = category;
}
public String getTransDate() {
return transDate;
}
public void setTransDate(String transDate) {
this.transDate = transDate;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
`
When you create navite query which contains results from multiple tables (after joins) you don't have to create new entities.
Better way to solve this problem is to projection with interface or class DTO.
For example, if you want to combine results from Person and Address Entities, simply create interface:
public interface PersonView {
String getFirstName();
String getLastName();
String getStreet();
}
You can see combined fileds from Person (firstName, lastName) and Address (street).
You have to use it as query response, like this:
#Query(...)
List<PersonView> getPersonWithStreet(String state);
You can read more about it here:
https://www.baeldung.com/spring-data-jpa-projections
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
http://localhost:8080/users?firstName=a&lastName=b ---> where firstName=a and lastName=b
How to make it to or ---> where firstName=a or lastName=b
But when I set QuerydslBinderCustomizer customize
#Override
default public void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(String.class).all((StringPath path, Collection<? extends String> values) -> {
BooleanBuilder predicate = new BooleanBuilder();
values.forEach( value -> predicate.or(path.containsIgnoreCase(value) );
});
}
http://localhost:8080/users?firstName=a&firstName=b&lastName=b ---> where (firstName=a or firstName = b) and lastName=b
It seem different parameters with AND. Same parameters with what I set(predicate.or/predicate.and)
How to make it different parameters with AND like this ---> where firstName=a or firstName=b or lastName=b ??
thx.
Your current request param are grouped as List firstName and String lastName. I see that you want to keep your request parameters without a binding, but in this case it would make your life easier.
My suggestion is to make a new class with request param:
public class UserRequest {
private String lastName;
private List<String> firstName;
// getters and setters
}
For QueryDSL, you can create a builder object:
public class UserPredicateBuilder{
private List<BooleanExpression> expressions = new ArrayList<>();
public UserPredicateBuilder withFirstName(List<String> firstNameList){
QUser user = QUser.user;
expressions.add(user.firstName.in(firstNameList));
return this;
}
//.. same for other fields
public BooleanExpression build(){
if(expressions.isEmpty()){
return Expressions.asBoolean(true).isTrue();
}
BooleanExpression result = expressions.get(0);
for (int i = 1; i < expressions.size(); i++) {
result = result.and(expressions.get(i));
}
return result;
}
}
And after you can just use the builder as :
public List<User> getUsers(UserRequest userRequest){
BooleanExpression expression = new UserPredicateBuilder()
.withFirstName(userRequest.getFirstName())
// other fields
.build();
return userRepository.findAll(expression).getContent();
}
This is the recommended solution.
If you really want to keep the current params without a binding (they still need some kind of validation, otherwise it can throw an Exception in query dsl binding)
you can group them by path :
Map<StringPath,List<String>> values // example firstName => a,b
and after that to create your boolean expression based on the map:
//initial value
BooleanExpression result = Expressions.asBoolean(true).isTrue();
for (Map.Entry entry: values.entrySet()) {
result = result.and(entry.getKey().in(entry.getValues());
}
return userRepository.findAll(result);
Background
I'm attempting to use ServiceStack.OrmLite to grab some values (so I can cache them to run some processing against them).
I need to grab a combination of three values, and I have a custom SQL statement that will yield them (does the joins, etc.)
Because this will be a large list of combinations, I'd like to pass in some lists of values and use Sql.In to filter to only the results that have those values.
Specifics
I need to check whether an invoice is unique to a firm and another value (called ClaimLawsuitID here).
so have my poco:
public class FirmIDClaimLawsuitIDInvoiceNumberCombination
{
public string FirmID { get; set; }
public string ClaimLawsuitID { get; set; }
public string InvoiceNumber { get; set; }
}
and I have my SQL statement:
select tblDefenseInvoice.FirmID, tblDefInvClaimantDetail.ClaimLawsuitID, tblDefInvClaimantDetail.invoiceNumber
from tblDefenseInvoice
inner join tblDefInvClaimantDetail
on(tblDefenseInvoice.DefenseInvoiceID = tblDefInvClaimantDetail.DefenseInvoiceID)
I would like to run the following:
public List<FirmIDClaimLawsuitIDInvoiceNumberCombination> GetFirmIDClaimLawsuitIDInvoiceNumberCombinationsForExistingItems(IEnumerable<int> firmIds, IEnumerable<long> claimLawsuitIDs, IEnumerable<string> invoiceNumbers)
{
var sql = #"select tblDefenseInvoice.FirmID, tblDefInvClaimantDetail.ClaimLawsuitID, tblDefInvClaimantDetail.invoiceNumber
from tblDefenseInvoice
inner join tblDefInvClaimantDetail
on(tblDefenseInvoice.DefenseInvoiceID = tblDefInvClaimantDetail.DefenseInvoiceID)";
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<tblClaimLawsuit>();
var firmFilter = PredicateBuilder.True<tblDefenseInvoice>();
var claimLawsuitFilter = PredicateBuilder.True<tblDefInvClaimantDetail>();
var invoiceNumberFilter = PredicateBuilder.True<tblDefInvClaimantDetail>();
firmFilter = x => Sql.In(x.FirmID, firmIds);
claimLawsuitFilter = x => Sql.In(x.ClaimLawsuitID, claimLawsuitIDs);
invoiceNumberFilter = x => Sql.In(x.InvoiceNumber, invoiceNumbers);
ev.Select(sql);
ev.Where(firmFilter);
ev.Where(claimLawsuitFilter);
ev.Where(invoiceNumberFilter);
return dal.DB.Select<FirmIDClaimLawsuitIDInvoiceNumberCombination>(ev.ToSelectStatement());
}
Question
Is this possible to achieve this way?
Is there some other way of achieving this within ServiceStack's OrmLite that is cleaner and I'm unaware of?
Since I was selecting to a POCO, I simply needed to add the filters based on that POCO.
The following worked just fine:
public List<FirmIDClaimLawsuitIDInvoiceNumberCombination>
GetFirmIDClaimLawsuitIDInvoiceNumberCombinationsForExistingItems(
IEnumerable<long> firmIds,
IEnumerable<long> claimLawsuitIDs)
{
var sql = #"select tblDefenseInvoice.FirmID, tblDefInvClaimantDetail.ClaimLawsuitID, tblDefInvClaimantDetail.invoiceNumber
from tblDefenseInvoice
inner join tblDefInvClaimantDetail
on(tblDefenseInvoice.DefenseInvoiceID = tblDefInvClaimantDetail.DefenseInvoiceID)";
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<FirmIDClaimLawsuitIDInvoiceNumberCombination>();
var firmFilter = PredicateBuilder.True<FirmIDClaimLawsuitIDInvoiceNumberCombination>();
var claimLawsuitFilter = PredicateBuilder.True<FirmIDClaimLawsuitIDInvoiceNumberCombination>();
firmFilter = x => Sql.In(x.FirmID, firmIds);
claimLawsuitFilter = x => Sql.In(x.ClaimLawsuitID, claimLawsuitIDs);
ev.Select(sql);
ev.Where(firmFilter);
ev.Where(claimLawsuitFilter);
return dal.DB.Select<FirmIDClaimLawsuitIDInvoiceNumberCombination>(ev.ToSelectStatement());
}
given a query in the form of an ICriteria object, I would like to use NHibernate (by means of a projection?) to find an element's order,
in a manner equivalent to using
SELECT ROW_NUMBER() OVER (...)
to find a specific item's index in the query.
(I need this for a "jump to page" functionality in paging)
any suggestions?
NOTE: I don't want to go to a page given it's number yet - I know how to do that - I want to get the item's INDEX so I can divide it by page size and get the page index.
After looking at the sources for NHibernate, I'm fairly sure that there exists no such functionality.
I wouldn't mind, however, for someone to prove me wrong.
In my specific setting, I did solve this problem by writing a method that takes a couple of lambdas (representing the key column, and an optional column to filter by - all properties of a specific domain entity). This method then builds the sql and calls session.CreateSQLQuery(...).UniqueResult(); I'm not claiming that this is a general purpose solution.
To avoid the use of magic strings, I borrowed a copy of PropertyHelper<T> from this answer.
Here's the code:
public abstract class RepositoryBase<T> where T : DomainEntityBase
{
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector, TWhere whereValue) where TWhere : DomainEntityBase
{
if (entity == null || entity.Id == Guid.Empty)
{
return -1;
}
var entityType = typeof(T).Name;
var keyField = PropertyHelper<T>.GetProperty(uniqueSelector).Name;
var keyValue = uniqueSelector.Compile()(entity);
var innerWhere = string.Empty;
if (whereSelector != null)
{
// Builds a column name that adheres to our naming conventions!
var filterField = PropertyHelper<T>.GetProperty(whereSelector).Name + "Id";
if (whereValue == null)
{
innerWhere = string.Format(" where [{0}] is null", filterField);
}
else
{
innerWhere = string.Format(" where [{0}] = :filterValue", filterField);
}
}
var innerQuery = string.Format("(select [{0}], row_number() over (order by {0}) as RowNum from [{1}]{2}) X", keyField, entityType, innerWhere);
var outerQuery = string.Format("select RowNum from {0} where {1} = :keyValue", innerQuery, keyField);
var query = _session
.CreateSQLQuery(outerQuery)
.SetParameter("keyValue", keyValue);
if (whereValue != null)
{
query = query.SetParameter("filterValue", whereValue.Id);
}
var sqlRowNumber = query.UniqueResult<long>();
// The row_number() function is one-based. Our index should be zero-based.
sqlRowNumber -= 1;
return sqlRowNumber;
}
public long GetIndexOf<TUnique>(T entity, Expression<Func<T, TUnique>> uniqueSelector)
{
return GetIndexOf(entity, uniqueSelector, null, (DomainEntityBase)null);
}
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector) where TWhere : DomainEntityBase
{
return GetIndexOf(entity, uniqueSelector, whereSelector, whereSelector.Compile()(entity));
}
}
public abstract class DomainEntityBase
{
public virtual Guid Id { get; protected set; }
}
And you use it like so:
...
public class Book : DomainEntityBase
{
public virtual string Title { get; set; }
public virtual Category Category { get; set; }
...
}
public class Category : DomainEntityBase { ... }
public class BookRepository : RepositoryBase<Book> { ... }
...
var repository = new BookRepository();
var book = ... a persisted book ...
// Get the index of the book, sorted by title.
var index = repository.GetIndexOf(book, b => b.Title);
// Get the index of the book, sorted by title and filtered by that book's category.
var indexInCategory = repository.GetIndexOf(book, b => b.Title, b => b.Category);
As I said, this works for me. I'll definitely tweak it as I move forward. YMMV.
Now, if the OP has solved this himself, then I would love to see his solution! :-)
ICriteria has this 2 functions:
SetFirstResult()
and
SetMaxResults()
which transform your SQL statement into using ROW_NUMBER (in sql server) or limit in MySql.
So if you want 25 records on the third page you could use:
.SetFirstResult(2*25)
.SetMaxResults(25)
After trying to find an NHibernate based solution for this myself, I ultimately just added a column to the view I happened to be using:
CREATE VIEW vw_paged AS
SELECT ROW_NUMBER() OVER (ORDER BY Id) AS [Row], p.column1, p.column2
FROM paged_table p
This doesn't really help if you need complex sorting options, but it does work for simple cases.
A Criteria query, of course, would look something like this:
public static IList<Paged> GetRange(string search, int rows)
{
var match = DbSession.Current.CreateCriteria<Job>()
.Add(Restrictions.Like("Id", search + '%'))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(1)
.UniqueResult<Paged>();
if (match == null)
return new List<Paged>();
if (rows == 1)
return new List<Paged> {match};
return DbSession.Current.CreateCriteria<Paged>()
.Add(Restrictions.Like("Id", search + '%'))
.Add(Restrictions.Ge("Row", match.Row))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(rows)
.List<Paged>();
}