NRules: match a collection - c#

I'm trying to figure out the BRE NRules and got some examples working but having a hard time to match a collection.
IEnumerable<Order> orders = null;
When()
.Match<IEnumerable<Order>>(o => o.Where(c => c.Cancelled).Count() >= 3)
.Collect<Order>(() => orders, o => o.Cancelled);
Then()
.Do(ctx => orders.ToList().ForEach(o => o.DoSomething()));
Basically what I want is if there are 3 orders cancelled then do some action. But I can't seem get a match on a collection, single variables do work.
The program:
var order3 = new Order(123458, customer, 2, 20.0);
var order4 = new Order(123459, customer, 1, 10.0);
var order5 = new Order(123460, customer, 1, 11.0);
order3.Cancelled = true;
order4.Cancelled = true;
order5.Cancelled = true;
session.Insert(order3);
session.Insert(order4);
session.Insert(order5);
session.Fire();
What am I doing wrong here?

With the 0.3.1 version of NRules, the following will activate the rule when you collected 3 or more canceled orders:
IEnumerable<Order> orders = null;
When()
.Collect<Order>(() => orders, o => o.Cancelled)
.Where(x => x.Count() >= 3);
Then()
.Do(ctx => orders.ToList().ForEach(o => o.DoSomething()));
Update:
For posterity, starting with version 0.4.x the right syntax is to use reactive LINQ. Matching a collection will look like this:
IEnumerable<Order> orders = null;
When()
.Query(() => orders, q => q
.Match<Order>(o => o.Cancelled)
.Collect()
.Where(x => x.Count() >= 3));
Then()
.Do(ctx => DoSomething(orders));

In your example, it should be pretty straightforward
IEnumerable<Order> orders = null;
When()
.Collect<Order>(() => orders, o => o.Cancelled == true);
Then()
.Do(ctx => orders.ToList().ForEach(o => o.DoSomething()));
I think the important part is the o.Cancelled alone without the == true. I know this sound wack, but somehow the property evaluation alone that is not an expression (i.e. just the property) is not well supported in NRules.
I ran into this problem and when I added the == true everything fell into place.

How to join Multiple Collection based on some expression like
IEnumerable<RawMsp> rawMsps = null;
IEnumerable<AsmMasterView> asmMasterViews = null;
IEnumerable<AsmInvestor> asmInvestors = null;
When()
.Match<AsmInvestor>(() => rawMsps)
.Match<AsmInvestor>(() => asmInvestor, i => i.InvestorId.ToString() == rawMsp.INVESTOR_CODE)
.Match<AsmMasterView>(() => asmMasterView, x => x.ChildAssumptionHistId == asmInvestor.AssumptionHistId);
Match is applicable individual object , Not sure apply equals of Enumerable Objects

Related

Group By Select New Object

I want to retrieve a list of games from my database and the count the number of games that a specified team won and lost and put it into an object with a win and loss property. I was trying this but it doesn't seem to be correct.
var winLoss = _teamService.GetGames()
.Where(x => x.Result != "Tie")
.GroupBy(x => x.Result)
.Select(x => new
{
Wins = x.Count(a => a.Result == "Hello"),
Losses = x.Count(a => a.Result != "Hello")
});
The return type for this is an IQueryable whereas I want it to just be a single object with a Win and Loss property.
Doing a GroupBy on the Results would put all the Wins for the current team into one group and then separate groups for each team they lost to in their own separate group.
Using a LINQ query you're going to end up with a collection, but what you care about is essentially a list of keys and values. I believe this will supply you the information you're looking for:
var winLoss = _teamService.GetGames()
.Where(x => x.Result != "Tie").GroupBy(x => x.Result)
.ToDictionary(e => e.Key, e => e.Count());
int wins = 0;
int losses = 0;
winLoss.TryGetValue("WIN", out wins);
winLoss.TryGetValue("LOSS", out losses);
I just went with two simple count calls to the SQL database.
Wins = _teamService.GetGames().Count(x => x.Result == "Name");
Loses = _teamService.GetGames().IsNotTie().Count(x => x.Result != "Name");
It's not 100% what I wanted but to do it in one call involved more complicated LINQ and therefore more complicated SQL.
You need to count the wins and losses for each team:
var winLoss = _teamService.GetGames()
.GroupBy(x => x.Team)
.Where(gg => gg.Key == "Hello")
.Select(gg => new
{
Wins = gg.Count(g => g.Result == "Hello"),
Losses = gg.Count(g => g.Result != "Hello")
});
Add FirstOrDefault() at the end of your linq query so you will get only the first element:
var winLoss = _teamService.GetGames()
.Where(x => x.Result != "Tie").GroupBy(x => x.Result);
var win = winLoss.Select(x => x.Count(a => a.Result == "Hello")).FirstOrDefault();
var loose = winLoss.Select(x => x.Count(a => a.Result != "Hello")).FirstOrDefault();

Finding the most specific matching item

User input will be like 'BY1 2PX', which will split and stored into list like below
var items = new List<string> {'BY1 2PX', 'BY12', 'BY1', 'BY'};
I have source list of Products
public class Product
{
public string Name {get;set;}
public string Id {get;set;}
}
Below is a sample product list. There is no guarentee on ordering, it could be in any order.
var products = new List<Product>{
new Product("1", "BY1 2PX"),
new Product("2", "BY12"),
new Product("3", "BY1"),
new Product("4", "BY"),
new Product("5", "AA2 B2X"),
//...etc
}
my output should fetch 1, because its most specific match. If Id = 1 is not there then it should have fetched Id =2 like that...etc Could anyone help me in writing a linq query. I have tried something like below, is this fine?
var result = items.Select(x => products.FirstOrDefault(p =>
string.Equals(p.Name.Trim(), x, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault();
Well, you can use dictionary with its fast lookups :
var productsDict = products.ToDictionary(p => p.Name, p => p);
var key = items.FirstOrDefault(i => productsDict.ContainsKey(i));
Product result = key != null ? productsDict[key] : null;
Or as Tim suggested, if you have multiple elements with same names you can use Lookup :
var productsDict = products.ToLookup(p => p.Name, p => p);
var key = items.FirstOrDefault(i => productsDict.Contains(i));
Product result = key != null ? productsDict[key] : null;
If you want to select the best-matching product you need to select from the product- not the string-list. You could use following LINQ approach that uses List.FindIndex:
Product bestProduct = products
.Select(p => new {
Product = p,
Index = items.FindIndex(s => String.Equals(p.Name, s, StringComparison.OrdinalIgnoreCase))
})
.Where(x => x.Index != -1)
.OrderBy(x => x.Index) // ensures the best-match logic
.Select(x => x.Product)
.FirstOrDefault();
The Where ensures that you won't get an arbitrary product if there is no matching one.
Update:
A more efficient solution is this query:
Product bestProduct = items
.Select(item => products.FirstOrDefault(p => String.Equals(p.Name, item, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault(p != null); // ensures the best-match logic
You can try to find resemblance of words by using a specific algorythm called Levenshtein's distance algorythm, which is mostly used on "Did you mean 'word'" on most search websites.
This solution can be found here:
https://stackoverflow.com/a/9453762/1372750
Once you find the distance difference, you can measure which word or phrase is more "like" the searched one.
This will find for each product what is the "most specific" (the longest) match in items and will return the product with the longest match (regardless to order of either of the collections)
var result = products
.Select(p => new
{
Product = p,
MostSpecific = items.Where(item => p.Name.Contains(item))
.OrderByDescending(match => match.Length
.FirstOrDefault()
})
.Where(x => x.MostSpecific != null)
.OrderByDescending(x => x.MostSpecific.Length)
.Select(x => x.Product)
.FirstOrDefault();

Check if last Row Entity Framework through Select

Ok people, before bashing, this is a little bit different from what I've investigated.
I currently have the following code:
public async Task<ParticipantTournament> GetParticipantTournamentByDescending(int tournamentId, int participantId)
{
var response = await _dbRepositories.TournamentParticipantMatchRepository
.Where(x => x.TournamentId == tournamentId)
.OrderByDescending(y => y.TournamentMatch.RoundNumber)
.ThenByDescending(y => y.TournamentMatch.Id)
.Include(x => x.Tournament)
.Include(x => x.Participant1)
.Include(x => x.Participant2)
.Include(x => x.TournamentMatch)
.Select(z => new TournamentParticipantMatchLogicDto
{
IsLastMatch = OneTrue() // <==== Problem here,
TournamentParticipantMatch = z
}).Where(x => (x.TournamentParticipantMatch.Participant1Id == participantId || x.TournamentParticipantMatch.Participant2Id == participantId))
.ToListAsync();
return new ParticipantTournament
{
ParticipantMatches = response,
};
}
/**
* This may be something super dumb. But I couldnt' come up with something better.
* How do I detect the latest match?
* http://stackoverflow.com/questions/14687136/check-if-record-is-last-or-first-in-list-with-linq
*
* Well, after digging a little bit, I've found that this is not possible :o
* */
private bool OneTrue()
{
if (!IsLast) return false;
IsLast = false;
return true;
}
I am building a tournament platform. I need to know which is the last match so I can give the players 5 rounds instead of 3. Instead of creating a new column and filling it with false or true, I decided to filter it out. I thought that I could take advantage of LINQ's deferred execution:
I would Select the whole data set from the tournament.
I would then order it by descending and select the first row as the last one. (All the matches were inserted in order, so the biggest id is the last one)
Then filter out which are from the users and which are not.
Possible solutions I think it could work:
- Create a boolean column that will hold "true" value for the last matches.
- Using Specification Pattern (Which I don't know how to apply in this situation. Tried using Expressions and Func but couldn't map them correctly)
- Load all the Ids and select the last one of those Ids. Compare those Ids with the ones that all the users have. Unfortunately this would add an extra roundtrip to the database.
What should I do?
Thanks!
P.S: The OneTrue() method it does what it does, it returns true once, and then it returns false. (Didn't find anything else after a quick Google search)
Edit
For clarification purposes:
The tables show a simulation of the data I currently have. I only need to extract what the current user needs, so I don't need the other 2 rows (which you can see in table #2). Once I select those two rows I exclude the other ones, which could potentially have the last match, but by only selecting this 2 rows I will not know. I'm trying to save any redundancy by trying to query it from the first try. I know that the last match Id is the last of the tournament
So what I was trying to do, is to order them all by descending (because they are in order), and select the last one as the last match.
Could you not just query your subset of data after your return it with linq, like:
var temp = from e in _dbRepositories.TournamentParticipantMatchRepository
where (from f in _dbRepositories.TournamentParticipantMatchRepository
where f.TournamentId == tournamentId)
.Include(x => x.Tournament)
.Include(x => x.Participant1)
.Include(x => x.Participant2)
.Include(x => x.TournamentMatch)
.Select(z => new TournamentParticipantMatchLogicDto
{
IsLastMatch = false, // <==== Problem here,
TournamentParticipantMatch = z
}).Where(x => (x.TournamentParticipantMatch.Participant1Id == participantId || x.TournamentParticipantMatch.Participant2Id == participantId))
.ToListAsync();
int maxResult= temp.Max(t => t.TournamentParticipantMatch.Id);
var update= temp.SingleOrDefault(x => x.TournamentParticipantMatch.Id== maxResult);
if(update!= null)
update.IsLastMatch= true;
This is what I ended up doing. Please if you see any improvements let me know!
public async Task<ParticipantTournament> GetParticipantTournamentByDescending(int tournamentId, int participantId)
{
var lastMatchId = await _dbRepositories.TournamentParticipantMatchRepository
.Where(x => x.TournamentId == tournamentId)
.OrderByDescending(y => y.TournamentMatch.RoundNumber)
.ThenByDescending(y => y.TournamentMatch.Id)
.Select(x => x.Id).FirstOrDefaultAsync();
var response = await _dbRepositories.TournamentParticipantMatchRepository
.Where(x => x.TournamentId == tournamentId)
.Where(x => (x.Participant1Id == participantId || x.Participant2Id == participantId))
.Include(x => x.Tournament)
.Include(x => x.Participant1)
.Include(x => x.Participant2)
.Include(x => x.TournamentMatch)
.ToListAsync();
var logic = response.Select(z=> new TournamentParticipantMatchLogicDto
{
IsLastMatch = z.Id == lastMatchId,
TournamentParticipantMatch = z
})
;
return new ParticipantTournament
{
ParticipantMatches = logic,
};
}

LinQ method syntax multiple tables with .where() and without joins

How do fetch data from multiple tables with method syntax without using joins, but only .where() methods?
I'm making a select against EF5 db context which maps to this legacy table structure where I have a persons detail table and another table which refers both to itself to create a hierarchy and to the person details table this way:
PersonSet
.Where(p => p.LastName.ToLower()=="surname")
.Join(SubsetSet, p => p.Id, ss => ss.SubsetLink, (p, ss) => new { PersonDetail = p, person = ss })
.Where(m => m.person.Frame=="a" && m.person.Purpose=="1")
.Join(SubsetSet, ss1 => ss1.person.Owner, person => person.SubsetLink, (ss1, ss2) => new { person = ss1, club = ss2 })
.Where(a => a.club.Frame=="b" && a.club.Purpose=="2")
.Join(SubsetSet, ss => ss.club.Owner, ss2 => ss2.SubsetLink, (ss, ss2) => new { club = ss, association = ss2 })
.Where(a => a.association.Frame=="b" && a.association.Purpose=="3")
.Join(SubsetSet, ss => ss.association.Owner, ss3 => ss3.SubsetLink, (ss, ss3) => new { association = ss, district = ss3})
.Where(d => d.district.Frame=="b" && d.district.Purpose=="4" && d.district.SubsetLink=="12345")
.Select(proj => new { proj.association.club.person, proj.association.club, proj.association, proj.district })
.OrderByDescending(a => a.association.club.person.phyperson.FirstName)
.Take(10).Dump();
The above query works at least in LinqPad but, it seems to me that If I could get rid of those joins the statement might look a bit nicer. Now I know, like in the Albahari example below, that this can be done with query syntax. But I couldn't find an example that would illustrate this situation with method syntax. The way I'm trying to approach this might of course be wrong and that's why I can't find suitable examples.
Here I found something similar, but couldn't make it work in LinQPad:
Is multiple .Where() statements in LINQ a performance issue?
Or this one, where the solution is again in query syntax:
Cross Join with Where clause
Or this example by Albahari: (http://www.linqpad.net/WhyLINQBeatsSQL.aspx)
from p in db.Purchases
where p.Customer.Address.State == "WA" || p.Customer == null
where p.PurchaseItems.Sum (pi => pi.SaleAmount) > 1000
select p
Consider this query:
var q = from order in orders
from orderline in order.Lines
where orderline.Count > 10
select order.Discount * orderline.Price;
this more or less corresponds to
var q = orders
.SelectMany(order => order.Lines, (order, orderline) => new { order, orderline})
.Where(item => item.orderline.Count > 10)
.Select(item => item.order.Discount * item.orderline.Price);
For more information on SelectMany, see the MSDN documentation.
If you don't have associations defined:
var q = from order in orders
from orderline in orderLines
where orderline.OrderId == order.Id
where orderline.Count > 10
select order.Discount * orderline.Price;
this more or less corresponds to
var q = orders
.SelectMany(order => orderLines, (order, orderline) => new { order, orderline})
.Where(item => item.orderline.OrderId == item.order.Id)
.Where(item => item.orderline.Count > 10)
.Select(item => item.order.Discount * item.orderline.Price);

Joins in nhibernate

I am using blow code for join in nhibernate, which is working fine. But i don't wants to use .List on both queries before join, i wants to use .List after join. I don't know nhibernate too much.. please provide me help that what i should make changes in below funciton to first join the data and then apply .List over it.
public IEnumerable<PGrp> GetSol()
{
_pGrpR = null;
_pGrp = null;
QueryOver<Phy, Phy> activePhyQuery = GetDataQuery();
var phyGrpR = _session.QueryOver(() => _pGrpR)
.Where(
Subqueries.WhereProperty<PGrpR>(p => _pGrpR.PhyId).In(
activePhyQuery))
.List<PGrpR>();
IList<PGrp> pGrps = _session.QueryOver(() => _pGrp)
.Where(x => !x.AC)
.List<PGrp>();
var newPGrps = pGrps
.Join(
pGrpR,
p => p.Id,
x => x.PGrpId,
(p, x) => p
).Distinct().OrderBy(x => x.Name);
return newPGrps;
}
Thanks
PGrpR pGrpR = null;
IList<PGrp> pGrps = _session.QueryOver(() => _pGrp)
.Where(x => !x.AC)
.JoinAlias(pgrps => pgrps.pGrpR, () => pGrpR) // Set real property name
.OrderBy(() => pGrpR.Name).Asc
.Select(Projections.Distinct(Projections.Property(() => pGrpR."PropertyForDistinct")))
.TransformUsing(Transformers.DistinctRootEntity)
.List<PGrp>();

Categories