This is where i am doing the Query i am getting null values on using dynamic query
var responsedata = await _elasticClient.SearchAsync<Acquirer>(s => s
.Query(q => q.ConstantScore(cs => cs.Filter(
f =>
f.Term("MERCHANTNO.keyword", MerchantNo)
)))
);
This is my model
public class Acquirer
{
public string MERCHANTNO { get; set; }
}
This is where index mapping is done
client.Map<Acquirer>(m =>
{
var putMappingDescriptor = m.Index(Indices.Index("acquiringdata")).AutoMap();
return putMappingDescriptor;
});
I am getting the exact number of records but i am getting the null values
This Error can be Resolved by using this
[PropertyName("MERCHANTNO")]
Related
we are a small development team.
We develop in ASP.NET and we are starting to use generic controllers and services.
The goal is to have solid methods for things that are repetitive.
What we ask ourselves is if it is a good idea to do some transformation in the data models to allow us to reuse our functions that we know are working?
Exemple: we have a combobox and we want to manage the display and search. It's always the same and redundant.
This is my class
[Table("stage.Test")]
public partial class Test : IBaseEntity, ICombobox
{
public virtual Product Product { get; set; }
public string nom { get; set; }
public string prenom { get; set; }
public string title { get; set; }
[NotMapped]
public virtual string AffichageCombobox => nom + prenom;
[NotMapped]
public virtual string TexteRecherche => Product.Gabarit.Description;
}
as you can see i have two columns with the tag [NotMapped]. These are the columns in the interface ICombobox
public interface ICombobox
{
string AffichageCombobox { get;}
string TexteRecherche { get; }
}
this is the first service where I use one of my two columns which redirects to other columns. [We use the column "AffichageCombobox" from the model]
public List<ComboboxViewModel> GetComboboxViewModel(int? id, bool actifseulement, string text)
{
var query = _requestDatabaseService.GetComboboxQuery<T>(id, actifseulement, text);
var list = query.Select(table => new ComboboxViewModel
{
Id = table.Id,
AffichageCombobox = table.DateHFin == null ? table.AffichageCombobox : table.AffichageCombobox + " (inactif)"
}).ToList();
return list;
}
This is the RequestDatabaseService [We use the column "TexteRecherche" from the model]
public List<T> GetComboboxQuery<T>(int? id, bool actifseulement, string text) where T : class, IBaseEntity, ICombobox
{
text = text.ToLower();
var list = _dbContext.Set<T>()
.If(id.HasValue,
q => q.Where(x => x.Id == id))
.If(actifseulement,
q => q.Where(x => x.DateHFin == null))
.If(text != "",
q => q.Where(x => x.TexteRecherche.ToLower() == text))
.ToList();
return list;
}
As you can see, I am using an interface to add columns to redirect to the correct columns to my data model to avoid overriding my methods for two column.
Is it a good idea, a good practice ?
What do you think is the best practice if we want to do generic functions, but the columns are not called the same way?
Thank you!
Your solution has a lot of weaknesses
You have extended Model to handle specific UI cases. In my opinion it is bad practice.
Your virtual properties will not work in LINQ query. EF translates only Expression because it canot look into compiled property body.
What we can do here is simplifying of building such comboboxes. I have defind set fo extensions which can be reused for such scenarios. Sorry if there some mistakes, written from memory.
How it can be used:
Assuming that GetComboboxViewModel is not in generic class
public List<ComboboxViewModel> GetComboboxViewModel(int? id, bool actifseulement, string text)
{
// uncover DbContext. All that we need is IQueryable<Test>
var ctx = _requestDatabaseService.GetContext();
var query = ctx.Test.AsQueryable();
var comboItems = query
.FilterItems(id, actifseulement)
.GetComboboxQuery(text, e => e.Product.Gabarit.Description, e => e.nom + e.prenom)
.ToList();
return comboItems;
}
Think about this solution and yes, we can register somewhere pair of Lmbdas Dictionary<Type, (LambdaExpression: searchProp, LambdaExpression: displayProp)> and dynamically build call above.
Realisation:
public static class QueryableExtensions
{
// more simlified version for filtering
public static IQueryable<T> WhereIf(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> predicate)
{
return condition ? query.Where(predicate) : query;
}
// handy function for filtering
public static IQueryable<T> FilterItems<T>(this IQueryable<T> query, int? id, bool onlyActive)
where T : IBaseEntity
{
query = query
.WhereIf(id.HasValue, x => x.Id == id)
.WhereIf(onlyActive, x => x.DateHFin == null)
return query;
}
// dynamic generation of filtering and projection
public static IQueryable<ComboboxViewModel> GetComboboxQuery<T>(this IQueryable<T> query, string text, Expression<Func<T, string>> searchProp, Expression<Func<T, string>> dsiplayProp)
where T : IBaseEntity
{
if (!string.IsNullOrEmpty(text))
{
text = text.ToLower();
// defining search pattern
// this also extension point, you may use here `Contains` or FullText search functions
Expression<Func<string, string, bool>> filterFunc = (s, t) => s.ToLower() == t;
// reusing parameter from searchProp lambda
var param = searchProp.Parameters[0];
// applying pattern to searchprop
var filterBody = ExpressionReplacer.GetBody(filterFunc, searchProp.Body, Expression.Constant(text));
// applying generated filter
var filterPredicate = Expression.Lambda<Func<T, bool>>(filterBody, param);
query = query.Where(filterPredicate);
}
// defining template for Select
Expression<Func<T, string, ComboboxViewModel>> createTemplate = (entity, dp) => new ComboboxViewModel
{
Id = entity.Id,
AffichageCombobox = entity.DateHFin == null ? dp : dp + " (inactif)"
};
// reusing parameter from dsiplayProp lambda
var entityParam = dsiplayProp.Parameters[0];
// injecting dsiplayProp into createTemplate
var selectBody = ExpressionReplacer.GetBody(createTemplate, entityParam, dsiplayProp.Body);
var selectLambda = Expression.Lambda<Func<T, ComboboxViewModel>>(selectBody, entityParam);
// applying projection
var comboQuery = query.Select(selectLambda);
return comboQuery;
}
// helper class for correcting expressions
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}
Well, after writing this sample, I think, it can be cardinally simplified by using LINQKit. Will post another answer with LINQKit usage if you are interested,
I use Generic and Reflection, so the main problem is add several fields. When i use this code with one field it OK but when i try somehow add new fields it doesn't work:
public static ISearchResponse<T> PartSearch<T>(ElasticClient client, string query, List<string> fieldList = null, int from = 0, int size = 1) where T : class
{
if (client == null)
throw new ArgumentNullException();
if (String.IsNullOrEmpty(query))
throw new ArgumentNullException();
ISearchResponse<T> results;
if (fieldList == null)
{
results = client.Search<T>(q =>
q.Query(q =>
q.QueryString(qs => qs.Query(query))
).From(from).Size(size)
);
}
else
{
results = client.Search<T>(q =>
q.Query(q => q
.QueryString(qs =>
{
//This place where i try to add several fields
List<Field> fildArray = new List<Field>();
foreach (var arg in fieldList)
{
var fieldString = new Field(typeof(T).GetProperty(arg));
fildArray.Add(fieldString);
}
qs.Fields(f => f.Field(fildArray));
qs.Query(query);
return qs;
})
).From(from).Size(size)
);
}
return results;
}
I created an example using Lenient() that can help you with your question:
https://github.com/hgmauri/elasticsearch-with-nest/blob/master/src/Sample.Elasticsearch.Domain/Abstractions/NestExtensions.cs
Problem was in one QueryString parameter Lenient:
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
Fixed this by setting it on True.
I have a books database, which has an ICollection of authors. I want to return the author object based on the AuthorId using LINQ.
Book db
int BookId
string Name { get; set; }
public ICollection<Author> Authors { get; set; }
Author db
int AuthorId
string Name
ICollection<Quote> Quotes { get; set; }
ICollection<Penname> Pennames { get; set; } - Edit: Added for clarity
I have tried:
var test = _context.Book.Include(x => x.Authors).Include("Authors.Quotes")
.Select(y => y.Authors)
Which gave me:
EntityQueryable<ICollection<Authors>>
[0] {HashSet<Author>} [0]{Author} [1]{Author} [3]{Author}
[1] {HashSet<Author>} [0]{Author} [1]{Author}
[2] {HashSet<Author>} [0]{Author} [1]{Author}
I just can't figure out how to iterate though the Authors in the Authors list. Something like the below:
var id = 2
var test = _context.Book.Include(x => x.Authors).Include("Authors.Quotes")
.Select(y => y.Authors.Select(x => x.Author).Where(x => x.AuthorId == id))
If I ever do a major update I might use elastic...
Update #Marko Papic:
Thanks. Weirdly if I use the below to get a list of books with authors, I get the quotes and pennames lists populated as I expect
var test = _context.Book.Include(x => x.Authors)
.ThenInclude(x => x.Quotes)
.Include(x => x.Authors)
.ThenInclude(x => x.Pennames)
However if I use SelectMany, then the quotes and pennames end up as null
var test = _context.Book.Include(x => x.Authors)
.ThenInclude(x => x.Quotes)
.Include(x => x.Authors)
.ThenInclude(x => x.Pennames)
.SelectMany(x => x.Authors).Where(x => x.AuthorId == id);
Author myauthor
int AuthorId = 2
string Name = "Bob"
ICollection<Quote> Quotes = null
ICollection<Penname> Pennames = null
You can use SelectMany:
var test = _context.Book.Include(x => x.Authors).ThenInclude(x => x.Quotes)
.SelectMany(x => x.Authors).Where(x => x.AuthorId == id);
I think the includes are ignored because the result type of the query is not the same of the type of your dbset with when you start, from the documentation:
If you change the query so that it no longer returns instances of the
entity type that the query began with, then the include operators are
ignored.
I assume the relationship between Books and Authors is many to many, if that is the case then this is how I would do your query:
var query=_context.Authors.Include(a=>a.Books)
.Include(a=>a.Quotes)
.Include(a=>a.Pennames)
.Where(a=> a.AuthorId == id);
I have 3 tables of same structure so i have created the following entity using ServiceStack
public class GenericEntity
{
[Alias("COL_A")]
public string ColumnA { get; set; }
}
For retriving the results I use the following line of code. In it I pass the table name like "TableA"/"TableB" so that i can pull the appropriate results
db.Select<GenericEntity>(w => w.Where(whereExperssion).OrderBy(o => o.ColumnA).From("TableA"));
For delete i use the following code
db.Delete<GenericEntity>(w => w.Where(q => q.ColumnA == "A").From("TableA"));
With From() I can pass table name for SELECT & DELETE operations. Is there a similar way for Inserting and updating? The below is the snippet code I am using for update and insert
Insert
db.Insert(new GenericEntity() {});
Update
db.Update<GenericEntity>(new GenericEntity { ColumnA = "ModifiedData"},p => p.ColumnA == "OriginalData");
As you're wanting to this for multiple API's I've added a test showing how to achieve the desired behavior by extending OrmLite's API's with your own custom extension methods that modifies OrmLite's table metadata at runtime to add new API's that allow specifying the table name at runtime, i.e:
var tableName = "TableA"'
db.DropAndCreateTable<GenericEntity>(tableName);
db.Insert(tableName, new GenericEntity { Id = 1, ColumnA = "A" });
var rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "A"));
rows.PrintDump();
db.Update(tableName, new GenericEntity { ColumnA = "B" },
where: q => q.ColumnA == "A");
rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "B"));
rows.PrintDump();
With these extension methods:
public static class GenericTableExtensions
{
static object ExecWithAlias<T>(string table, Func<object> fn)
{
var modelDef = typeof(T).GetModelMetadata();
lock (modelDef) {
var hold = modelDef.Alias;
try {
modelDef.Alias = table;
return fn();
}
finally {
modelDef.Alias = hold;
}
}
}
public static void DropAndCreateTable<T>(this IDbConnection db, string table) {
ExecWithAlias<T>(table, () => { db.DropAndCreateTable<T>(); return null; });
}
public static long Insert<T>(this IDbConnection db, string table, T obj, bool selectIdentity = false) {
return (long)ExecWithAlias<T>(table, () => db.Insert(obj, selectIdentity));
}
public static List<T> Select<T>(this IDbConnection db, string table, Func<SqlExpression<T>, SqlExpression<T>> expression) {
return (List<T>)ExecWithAlias<T>(table, () => db.Select(expression));
}
public static int Update<T>(this IDbConnection db, string table, T item, Expression<Func<T, bool>> where) {
return (int)ExecWithAlias<T>(table, () => db.Update(item, where));
}
}
Adding your own extension methods in this way allows you to extend OrmLite with your own idiomatic API's given that OrmLite is itself just a suite of extension methods over ADO.NET's IDbConnection.
I have a room class, which has 3 linked tables, Property, Sex (as in Male/Female), Stage.
I can get the Index controller to return the linked table items, by using Include:
var rooms = db.Rooms.Include(r => r.Property).Include(r => r.Sex).Include(r => r.Stage);
What I don't know is, how to you include these linked table items in the Details controller:
Room room = db.Rooms.Find(id);
The full controllers are listed below:
//
// GET: /Room/
public ActionResult Index()
{
var rooms = db.Rooms.Include(r => r.Property).Include(r => r.Sex).Include(r => r.Stage);
return View(rooms.ToList());
}
//
// GET: /Room/Details/5
public ActionResult Details(int id = 0)
{
Room room = db.Rooms.Find(id);
if (room == null)
{
return HttpNotFound();
}
return View(room);
}
Thanks for any help,
Mark
After Include statments you could use LINQ's SingleOrDefault method
Like this:
Room room = db.Rooms.Include(r => r.Property)
.Include(r => r.Sex)
.Include(r => r.Stage)
.SingleOrDefault(r => r.Id = id);