I want to create linq query which should be dynamic enough to get all properties of specific class and build expression tree for 'where' and 'select' in linq.
There is a domain model which consists of bool properties and numeric properties. Numeric properties will be used for ranges.
Example:
Domain Model
public class MakeMeReal
{
public bool isA { get; set; }
public bool isB { get; set; }
public int Type1 { get; set; }
public double Type2 { get; set; }
public double Type3 { get; set; }
public string Type4 { get; set; }
}
Input Model:
public class LinqDynamic
{
public bool isA { get; set; }
public bool isB { get; set; }
public int lowerRangeForTyp1 { get; set; }
public int UpperRangeForTyp1 { get; set; }
public double LowerRangeForType2 { get; set; }
public double UpperRangeForType2 { get; set; }
}
Simple query:
public void query(LinqDynamic dynamicInput)
{
SampleDbContext db = new SampleDbContext();
var result = from m in db.MakeMeReal
where m.isB == dynamicInput.isB && m.isA == dynamicInput.isA &&
m.Type1 >= dynamicInput.lowerRangeForTyp1 && m.Type1 < dynamicInput.UpperRangeForTyp1 &&
m.Type2 >= dynamicInput.LowerRangeForType2 && m.Type1 < dynamicInput.UpperRangeForType2
select new { a = m.Type1, b = m.Type2, c = m.Type3, d = m.Type4 };
}
I want to build query which will go through domain model and it'll build expression tree and it'll run against the inputs which'll be provided to at runtime using 'LinqDynamic' class as input parameter.
I have more than 10 booleans and 30 range selectors, and number of booleans and range selctors keeps changing as per project requirements, so it's headache to change query each time. I'd like to make it dynamic, so that it'll be agnostic to any change
I read about https://msdn.microsoft.com/en-us/library/bb397951.aspx and some other links which I can't post here due to lack of reputation points
yet I am not sure how to achieve it
What might help is to use a predicate in your query. There is a NuGet package that will assist you to do. The documentation will explain how to do this. See LINQKit here
Related
I am trying to make my code more compromised, and use overall less, however currently I'm running into the problem of not being able to send a list of Objects sorted by linq as a parameter.
the problem is in this part of the code:
List<Afspraken> dataAfspraken = new List<Afspraken>();
public Form1()
{
InitializeComponent();
fillListsForLinq();
loadReceptionData();
}
private void fillListsForLinq()
{
dataAfspraken = data.getAfsprakenData();
//here it fills the list with Afspraken objects
}
private void loadReceptionData()
{
private void loadReceptionGrid
var receptionToFinnish =
(from AFspraken in dataAfspraken
where Afspraken.factuur_betaald == true && Afspraken.volledig_afgerond == false
join Users in dataUsers on Afspraken.gekoppelde_klant equals Users.id
select new
{
Id = Afspraken.id,
Klant = Users.gebruikersnaam,
Betaald = Afspraken.factuur_betaald,
Afgerond = Afspraken.volledig_afgerond
}).ToList();
changeDataviewReception(receptionToFinnish);
}
private void changeDataviewReception(List<Object> listData)
{
dgvReceptionData.DataSource = listData
}
the Afspraken class looks like this
public class Afspraken
{
public int id { get; set; }
public bool bevestigd { get; set; }
public DateTime datum { get; set; }
public int gekoppelde_klant { get; set; }
public int gekoppelde_monteur { get; set; }
public string benodigde_hadelingen { get; set; }
public decimal totaalprijs { get; set; }
public bool klaar { get; set; }
public bool factuur_betaald { get; set; }
public bool volledig_afgerond { get; set; }
public string opmerkingen { get; set; }
}
How do I get receptionToFinnish as a parameter into changeDataviewReception?
receptionToFinnish will be a list full of objects of an anonymous type. But your method requires a List<object>. This is now allowed since a list is not a variant type.
Say for example that you have a list of bananas and want to give it to someone that wants a list of fruits. This will not work since that other person might try to add an orange to the list of bananas.
To fix this, cast the values to object explicitly, for example:
select new
{
Id = Afspraken.id,
Klant = Users.gebruikersnaam,
Betaald = Afspraken.factuur_betaald,
Afgerond = Afspraken.volledig_afgerond
} as object
I have nested documents such as;
public sealed class CampaignIndexModel : ElasticEntity<Guid>
{
public Guid StoreId { get; set; }
public string Slug { get; set; }
public string SlugKey { get; set; }
public string Title { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public string Condition { get; set; }
public string PreviewImageUrl { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public bool IsPublished { get; set; }
public DateTime CreatedOnUtc { get; set; }
[Nested]
public List<BadgeIndexModel> Badges { get; set; }
}
public class BadgeIndexModel
{
public string Code { get; set; }
public string Name { get; set; }
}
I'd like to query in nested object with multiple values. For example, I need to query which included Code property which are "AD", "NEW". All documents must have badge and their code properties must be "AD" and "NEW". The code properties can be dynamically. Actually I'd like to search list of string in the nested object's code property.
Note that the classes are auto-mapped while creating indexes.
I hope the question is clear, understandable.
Thank you.
UPDATE
As far as I researched Elasticsearch documentations, as below, the query result returns match exactly given badges codes.
q.Bool(b=>b
.Must(x=>x.
Nested(n=>n
.Path(p=>p.Badges)
.Query(qq=>qq
.Term(t=>t
.Field(f=>f.Badges.First().Code.Suffix("keyword"))
.Value(badge))))))
Then, the answer which is marked correct, returns documents which contains badge codes
I know it has been a little while you posted the question. But here you go -- You could do this by creating a Nested Query within which you could filter upon your list and pass this to your search method. Below method shows how this can be done. This takes the list of strings that you want to use as values for codes.
private static QueryContainer BuildNestedQuery(List<string> badgeCodes)
{
// badgeCodes is your list of strings that you want to filter on
return new QueryContainerDescriptor<CampaignIndexModel>()
.Nested(n =>
n.Path(c => c.Badges)
.Query(q => q
.Terms(t => t
.Field(f => f.Badges.FirstOrDefault().Code)
.Terms(badgeCodes.ToArray())
)
)
)
}
This QueryContainer can further be passed to the Search method of the NEST client like something shown below. However, please bear in mind that there could be slight changes in the way you trigger the client's search method depending on how you're doing it, but hooking it to the search method remains more or less the same as shown below.
// replace T with type of your choice
// client is a reference to NEST client
var result = client.Search<T>(
.From(0)
.Size(20)
.Query(q => BuildNestedQuery(badgeCodesList))
// other methods that you want to chain go here
)
I have an Entity MVC app with a code-first database. I need to produce a search box to search between 2 dates and return the records between those dates.
I will call the method with jQuery/ajax and render the results in a table.
I've tried writing an API, with no success. I am not even sure if this is the correct way to go about it?
namespace Areometrex_Leaflet.Models
{
[Table ("Flight_Lines")]
public class Flight_line
{
[Key]
public string Swath_name { get; set; }
public string Flight_name { get; set; }
public string Swath_record { get; set; }
public string Flight_date { get; set; }
public decimal Start_lat { get; set; }
public decimal Start_long { get; set; }
public decimal End_lat { get; set; }
public decimal End_long { get; set; }
public decimal Altitude { get; set; }
public DateTime Time_start { get; set; }
public DateTime Time_end { get; set; }
public string Sensor { get; set; }
}
public class FlightLineContext : DbContext
{
public DbSet<Flight_line> Flight_Lines { get; set; }
}
}
This is my model that holds the objects in the database. I need to search the "Flight_date" property, that is held in my DB in this following format as an "nvarchar" :
17/11/2018 11:09:18 PM
My current API looks something like this:
[HttpPost]
public IEnumerable<Flight_line> SearchFlight_Line()
{
string start, end;
var rc = RequestContext;
var data = rc.Url.Request.GetQueryNameValuePairs();
{
start = data.FirstOrDefault().Value ?? string.Empty;
end = data.LastOrDefault().Value ?? string.Empty;
}
//db format: 17/11/2018 11:22:56 PM
var s = DateTime.Parse(start);
var flightSearch = new List<Flight_line>();
using (_context)
{
var sql = $"SELECT * FROM Flight_Lines WHERE Flight_Date BETWEEN '{start}' AND '{end}'";
flightSearch = _context.Flight_Lines.SqlQuery(sql).ToList<Flight_line>();
}
return flightSearch;
}
Ideally, I want to call this API with jquery/Ajax and return results to be displayed in an MVC view. My guess is that this is dead easy, but I am only learning and I'm running out of ideas. I would have thought this was really simple, but I am struggling to find the answers I am looking for online, which leads me to believe perhaps I am doing it wrong?
First of all, don't save dates as string in your database, you will just have problems later on.
Instead of:
public string Flight_date { get; set; }
Set it up as DateTime:
public DateTime Flight_date { get; set; }
As far as the query for searching flights go, you can try this. This will return a list of "Flight_line" objects which you can then return wherever you need.
DateTime start = DateTime.Now;
DateTime end = DateTime.Now.AddDays(7);
var flights = _context.Flight_line.Where(f => f.Flight_date >= start && f.Flight_date <= end).ToList();
I receive this parameter from my request:
sort=homeCountry
I need to sort by whatever column is sent to the sort parameter, so I created a method like so:
string sort = "homeCountry";
Func<PaymentRateTrip, string> orderBy = (
x => sort == "homeCountry" ? x.HomeCountry :
sort == "hostCountry" ? x.HostCountry :
sort == "homeLocation" ? x.HomeLocation :
sort == "hostLocation" ? x.HostLocation :
x.HomeLocation
);
This works correctly.
However, the columns above are all strings. But, I also need to add decimal columns like so:
string sort = "homeCountry";
Func<PaymentRateTrip, string> orderBy = (
x => sort == "homeCountry" ? x.HomeCountry :
sort == "hostCountry" ? x.HostCountry :
sort == "homeLocation" ? x.HomeLocation :
sort == "hostLocation" ? x.HostLocation :
sort == "oneWayRate" ? x.oneWayRate :
x.HomeLocation
);
It gives me an error on the x.HostLocation that says:
Type of conditional expression cannot be determined because there is
no implicit conversion between 'string' and 'decimal?'
Is there a better way to do what I am trying to do? I am looking for:
A way that will work.
A way that is readable (something like a switch case).
Edit: PaymentRateTrip.cs
public class PaymentRateTrip
{
public Guid? Id { get; set; }
public Guid HomeCountryId { get; set; }
public string HomeCountry { get; set; }
public Guid HomeLocationId { get; set; }
public string HomeLocation { get; set; }
public Guid? HostCountryId { get; set; }
public string HostCountry { get; set; }
public Guid? HostLocationId { get; set; }
public string HostLocation { get; set; }
public decimal? OneWayRate { get; set; }
public decimal? RoundTripRate { get; set; }
public Guid? OneWayCurrencyId { get; set; }
public Guid? RoundTripCurrencyId { get; set; }
}
I'd just make an extension method:
public static IEnumerable<PaymentRateTrip> Sort(this IEnumerable<PaymentRateTrip> list, string sort)
{
switch (sort)
{
case "homeCountry":
return list.OrderBy(x => x.Whatever);
case "somethingElse":
return list.OrderBy(x => x.Whatever);
//....
}
}
It might seem to simple but just do this
var prop = typeof(PaymentRateTrip).GetProperty(sort);
var ordered = lst.OrderBy(p => prop.GetValue(p));
this works as long as the sort name is part of the object.
In your case the function is (p => prop.GetValue(p))
I have this item:
public partial class PACK
{
public int PACK_IDE { get; set; }
public string PACK_DESCR { get; set; }
public Nullable<System.DateTime> PACK_DATE_CREATED { get; set; }
public Nullable<System.DateTime> PACK_DATE_MODIFIED { get; set; }
public Nullable<System.DateTime> PACK_DATE_LAST_CALC { get; set; }
public decimal PACK_COST { get; set; }
public int PACK_QTY_POSS { get; set; }
public string PACK_NOTE { get; set; }
public int PACK_QTY_SOLD { get; set; }
public decimal PACK_AVRG_SELL_PRICE { get; set; }
public Nullable<int> PACK_DESTINATION { get; set; }
public virtual ICollection<INVENTORY_PACK> INVENTORY_PACK { get; set; }
}
Which contains, as you can see, a list of Inventory Packs which are shaped like this:
public partial class INVENTORY_PACK
{
public int INVENT_PACK_IDE { get; set; }
public int INVENT_IDE { get; set; }
public int PACK_IDE { get; set; }
public int QTY { get; set; }
public virtual INVENTORY INVENTORY { get; set; }
public virtual PACK PACK { get; set; }
}
And, lastly, the Inventory Items, which has 2 important fields that are of importance right now:
public partial class INVENTORY
{
public int INVENT_IDE { get; set; }
public Nullable<int> CARD_IDE { get; set; }
public Nullable<int> INVENT_NB_IN_STOCK { get; set; }
public Nullable<int> INVENT_NB_QT_SOLD { get; set; }
public string INVENT_ITEM_STATE { get; set; }
public virtual CARD CARD { get; set; }
public virtual ICollection<INVENTORY_PACK> INVENTORY_PACK { get; set; }
}
When I actually save or create a new pack, I need to find a way to check if the actual pack exists that has the exact same Inventory Items based on INVENT_ITEM_STATE and CARD_IDE and, also, of QTY in INVENTORY_PACK. If these three values are identical, then we may consider having the same children. I basically need to search through any Packs (using Linq or any Linq-To-Sql call) which childrens are the same as the one I have right now, but I don't really know how to do this except for massive mind-blowing for/foreach loops.
EDIT
As requested, here's an example of what I've been trying to do.
internal void CreatePack(PackInfo _pack)
{
using (TransactionScope scope = TransactionUtils.CreateTransactionScope())
{
try
{
var packQry = from pa in mDb.PACK
select pa;
if (!packQry.Any())
{
PACK packToAdd = DataConverter.PackInfoToPACKData(_pack);
mDb.PACK.Add(packToAdd);
mDb.SaveChanges();
int packID = mDb.PACK.Max(_x => _x.PACK_IDE);
foreach (INVENTORY_PACK inventoryPack in packToAdd.INVENTORY_PACK)
{
inventoryPack.PACK_IDE = packID;
mDb.SaveChanges();
}
}
else
{
List<PACK> listPacks = new List<PACK>();
foreach (var inventoryPackInfo in _pack.mListInventoryPackInPack)
{
packQry = from pa in mDb.PACK
where pa.INVENTORY_PACK.Any(_item =>
_item.INVENTORY.INVENT_IDE ==
inventoryPackInfo.mInventoryItem.mInventoryID)
where pa.INVENTORY_PACK.Any(
_item =>
_item.INVENTORY.INVENT_ITEM_STATE ==
inventoryPackInfo.mInventoryItem.mItemState)
where pa.INVENTORY_PACK.Any(_item => _item.QTY == inventoryPackInfo.mQuantity)
select pa;
if (packQry.Any())
{
listPacks.AddRange(packQry);
}
}
if (_pack.mListInventoryPackInPack.Count == 1)
{
}
IDictionary<PACK, int> counts = new Dictionary<PACK, int>();
foreach (var pack in listPacks)
{
if (!counts.ContainsKey(pack))
{
counts.Add(pack, 1);
}
else
{
counts[pack]++;
}
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
scope.Complete();
}
}
EXAMPLE
I think that I need to clarify my need. Here's an example.
Say that I have 1 PACK containing 2 INVENTORY_PACK: 1 is an INVENTORY item with INVENT_IDE 1234, CARD_IDE 4321, QTY is 1, and INVENT_ITEM_STATE is PERFECT. The second object is INVENT_IDE 4567, CARD_IDE 7654, QTY is 2 and INVENT_ITEM_STATE PERFECT.
I need to check through the packages to see if there's already a package containing exactly there two items in the selected parameters. So there are many possibilities:
If we have another existing PACK that has the same items and the same number of items (in this case, 2), quantities and IDS, we have a perfect match and we consider that the PACK already exists;
If there is a PACK containing the same items, but with another one (3 items or more for this example), is it considered another pack; then we do not have a match;
If any package has only one of these items, we do not have a match.
If I understand well, you could do the following :
Implement two EqualityComparer (may be implemented in your business layer as it's business logic only)
class PACK_Comparer : EqualityComparer<PACK>
{
public override bool Equals(PACK p1, PACK p2)
{
// Two PACK are Equals if their INVENTORYs contains the same INVENTORY items
return (p1.INVENTORY_PACK.Count() == p2.INVENTORY_PACK.Count()
&& p1.INVENTORY_PACK.Intersect(p2.INVENTORY_PACK, new INVENTORY_PACK_Comparer()).Count() == p1.INVENTORY_PACK.Count());
}
public override int GetHashCode(PACK p)
{
// Ensure that if the Equals method returns true for two PACK p1 and p2
// then the value returned by the GetHashCode method for p1 must equal the value returned for p2
INVENTORY_PACK_Comparer comp = new INVENTORY_PACK_Comparer();
int hCode = 0;
foreach (var i in p.INVENTORY_PACK)
hCode ^= comp.GetHashCode(i);
return hCode.GetHashCode();
}
}
class INVENTORY_PACK_Comparer : EqualityComparer<INVENTORY_PACK>
{
public override bool Equals(INVENTORY_PACK i1, INVENTORY_PACK i2)
{
// Two INVENTORY_PACK are Equals if their INVENT_ITEM_STATE, CARD_IDE and QTY are Equals
return (i1.INVENTORY.INVENT_ITEM_STATE == i2.INVENTORY.INVENT_ITEM_STATE
&& i1.INVENTORY.CARD_IDE == i2.INVENTORY.CARD_IDE
&& i1.QTY == i2.QTY);
}
public override int GetHashCode(INVENTORY_PACK i)
{
// Ensure that if the Equals method returns true for two INVENTORY_PACK i1 and i2
// then the value returned by the GetHashCode method for i1 must equal the value returned for i2
int hCode = i.INVENTORY.INVENT_ITEM_STATE.GetHashCode()
^ i.INVENTORY.CARD_IDE.GetHashCode()
^ i.QTY.GetHashCode();
return hCode.GetHashCode();
}
}
Then check if a same PACK already exist is as short as
bool exist = mDb.PACK.Contains(_pack, new PACK_Comparer());
And if you want to fetch the actual PACK which already exist in your mDb :
PACK_Comparer comp = new PACK_Comparer();
PACK existingPack = mDb.PACK.FirstOrDefault(p => comp.Equals(p, _pack));
Note that I removed the 'test is null' things to make it simplier.
You'll need to implement this on your own.
Regards,
Gerard
This is probably what you want
int count = (from p in _pack.INVENTORY_PACK
where pack.INVENTORY.INVENT_ITEM_STATE == p.INVENTORY.INVENT_ITEM_STATE
select p).Count();
After that check for if(pack.QTY == count)