I have this entities:
public class A
{
public List<B> B { get;set; }
}
public class B
{
public DateTime Date { get; set; }
public List<C> C { get; set; }
}
public class C { }
and I need to get C from A where B.Date > DateTime.Now..
I'm trying something like this:
var users = _db.A
.Select(a => new
{
A = a,
B = a.B
.Where(b => b.Date >= DateTime.Now).Select(p => new
{
B = b,
C = b.C
})
})
.AsEnumerable()
.Select(a => a.A)
.ToList();
but C is always null.
How can I get C?
UPDATE
If I set virtual in 'List C' it works!!
How can I make this work without setting virtual?
You can try to build the object graph manually. The following will run only a single database query (when the first foreach loop executes, the rest is performed in memory):
var userQuery = _db.A
.Select(a => new
{
A = a,
B = a.B.Where(b => b.Date >= DateTime.Now).Select(p => new
{
B = b,
C = b.C
})
});
var users = new List<A>();
foreach (var u in userQuery)
{
A a = u.A;
a.B = new List<B>();
foreach (var x in u.B)
{
B b = x.B;
b.C = x.C;
a.B.Add(b);
}
users.Add(a);
}
Related
I have these parameters in a class:
public class UserParams
{
public string Gender {get; set;}
public int MinAge {get; set;} = 1;
public int MaxAge {get; set;} = 19;
}
The query is done in the repository as shown below. First is to query for the child sex or gender and the second is to query for the child sex or gender
var query = _context.Children.AsQueryable();
query = query.Where(c => c.Sex == userParams.Gender);
var minchildDob = DateTime.Today.AddYears(-userParams.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-userParams.MinAge);
query = query.Where(u => u.DateOfBirth >= minchildDob && u.DateOfBirth <= maxchildDob);
return await PagedList<Child>.CreateAsync(query.AsNoTracking(), userParams.PageNumber, userParams.PageSize);
The gender filter returns empty array of children and the minchildDob and maxchildDob too not working
if (!string.IsNullOrEmpty(temp.Gender))
{
all = all.Where(u => new[] { "men", "women" }.Contains(u.sex));
//all = all.Where(t => t.sex == temp.Gender);
}
=======================Update=======================
var temp = new UserParams();
temp.Gender = "men";
var minchildDob = DateTime.Today.AddYears(-temp.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-temp.MinAge);
IEnumerable<Table> all = from m in _context.data
select m;
_logger.LogError("all data");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (!string.IsNullOrEmpty(temp.Gender)) {
all = all.Where(t => t.sex == temp.Gender);
}
_logger.LogError("filter gender");
foreach (var item in all) {
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (temp.MaxAge > 0) {
all = all.Where(t => t.birthday >= minchildDob && t.birthday <= maxchildDob);
}
_logger.LogError("filter age");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");
TableB has a field TableAId which is linked to the Id of TableA. I want to select the count from TableB based on TableAId like -
SELECT *,
(SELECT COUNT(*) FROM tableB where tableB.TableAId = tableA.Id) as count
FROM tableA
So far I have the code:
var data = _context.TableA.AsQueryable();
...
data = data.Select(l => l.TableAId= p.Id).Count();
But on the last line, p is not recognized as a variable.
How do I make this work?
EDIT :
my original query is quiet complex and already filtering data
var data = _context.TableA.AsQueryable();
data = data.Include(p => p.SomeClassA)
.Include(p => p.SomeClassB);
data = data.Where(p => p.Id == somevalue);
data = data.Where(p => p.SomeClassA.Name.Contains(someothervalue));
data = data.Where(p => p.SomeClassA.SomeField.Contains(yetanothervalue));
I tried adding this but it cannot compile
(TableAId & Count do not exist):
data = data.Join(
_context.TableB,
groupByQuery => groupByQuery.TableAId ,
TableA => TableA.Id,
(groupByQuery, TableAItem) => new
{
TableAId = groupByQuery.Id,
Count = groupByQuery.Count,
TableAItem = TableAItem
}
);
If you are just interested in count, then do the following:
var data = _context.TableB.AsQueryable();
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
});
var result = groupByCountQuery.ToList(); // or change to ToListAsync()
This will give you the count based on TableAId.
If you need the tableA items as well in the result, following ca be done:
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
}).Join(_context.TableA,
groupByQuery => groupByQuery.TableAId,
tableA => tableA.Id,
(groupByQuery , tableA) => new {
TableAId = groupByQuery.TableAId,
Count = groupByQuery.Count,
TableAItem = tableA
} );
Let's say TableA has properties - Id, FieldA1 and FieldA2.
First, you have to include TableB in your query so that you can take its count, like -
var data = _context.TableA.Include(p=> p.TableB).AsQueryable();
Then in the Select method you have to create a new object with TableA's properties and TableB's count, like -
var list = data.Select(p =>
new
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
Notice, this is assigned to a new variable list. That is because it does not return a list of TableA, it returns a list of an anonymous object with properties - Id, A1, A2 and Count. Therefore, you cannot assign it to the previously declared data variable, because data is of type IQueryable<TableA>.
Alternatively, you can declare a class to hold the data values, like -
public class MyData
{
public int Id { get; set; }
public string A1 { get; set; }
public string A2 { get; set; }
public int Count { get; set; }
}
and use it like -
var list = data.Select(p =>
new MyData
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
If you want to start from TableA, you can use the following linq query:
var data = _context.TableAs.AsQueryable();
var x = (from a in data
join b in _context.TableBs on a.Id equals b.TableAId
group a.TableB by a.Id into g
select new
{
TableAId = g.Key,
TableBItem = g.FirstOrDefault(),
Count = g.Count()
}).ToList();
Result:
We regularily write extension methods like this that convert from Database objects to DTO objects for use elsewhere in our system.
As you can see in the example below, the actual mapping code is repeated. Is it possible to write a reusable select mapping that can be used in both of these methods?
public static async Task<List<Group>> ToCommonListAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
var groups =
await entityGroups.Select(
g =>
new Group()
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
}).ToListAsync();
return groups;
}
public static async Task<Group> ToCommonFirstAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
var group =
await entityGroups.Select(
g =>
new Group()
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
}).FirstOrDefaultAsync();
return group;
}
You could move your mapping/projection code out into a variable like this:
public static class Extensions
{
private static readonly Expression<Func<DataLayer.Models.Group, Group>> Projection = g =>
new Group
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
};
public static async Task<List<Group>> ToCommonListAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
return await entityGroups.Select(Projection).ToListAsync();
}
public static async Task<Group> ToCommonFirstAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
return await entityGroups.Select(Projection).FirstOrDefaultAsync();
}
}
I have one observable that I use GroupBy on to get a number of streams. I actually want a Scan result over each sub-stream. Let's say the observable is over product prices and the scan result is average price per product type.
I have another stream of events pertaining to those 'products' (let's say "show product price" events) and I want to combine it with the previous stream's latest product price. So the Scan output per group needs to be combined with each element of the event stream to get the latest average price for that event's product.
For some reason I cannot get the right syntax and I have been bashing away at this all day. Can someone please help?
Update
I am adding the code below to illustrate the approximate intent.
public class Node
{
private List<int> Details = new List<int>();
public void AddInfo(int x)
{
Details.Add(x );
}
public Node(int x)
{
Details.Add(x);
}
public int Index => Details[0]%10; //just to simplify the grouping and debugging
public int Latest => Details.Last();
}
public class Message
{
private static Random _random = new Random();
public int MessageNodeInfo { get; private set; }
public Message()
{
MessageNodeInfo = _random.Next();
}
}
public class AccumulatingInfoTest
{
private static Random _random=new Random();
private IObservable<Message> MessageStream()
{
TimeSpan timeSpan = TimeSpan.FromSeconds(0.5);
var ret= Observable.Generate(0,
_ => { return true; },
_ => { return 0; },
_ => { return new Message(); },
_=> timeSpan)
.Publish()
.RefCount();
return ret;
}
public class ArbitraryCommonClass
{
public int K { get; set; }
public Message M { get; set; }
public Node D { get; set; }
public ArbitraryCommonClass Combine(ArbitraryCommonClass a)
{
return new ArbitraryCommonClass()
{
K = this.K,
M = this.M ?? a.M,
D = this.D ?? a.D
};
}
}
public void Start()
{
var inputStream = MessageStream();
inputStream.Subscribe(y => Console.WriteLine("Input: K " + y.MessageNodeInfo % 10 + " V " + y.MessageNodeInfo));
var nodeInfoStream = inputStream
.Select(nodeInfo => new Node(nodeInfo.MessageNodeInfo))
.GroupBy(node => node.Index)
.Select(groupedObservable => new
{
Key = groupedObservable.Key,
Observable = groupedObservable
.Scan(
(nodeAcc, node) => { nodeAcc.AddInfo(node.Latest); return nodeAcc; }
)
.Select(a => new ArbitraryCommonClass() { K = a.Index, M = (Message)null, D = a })
}
);
var groupedMessageStream =
inputStream
.GroupBy(
m => new Node(m.MessageNodeInfo).Index
)
.Select(a => new
{
Key =a.Key,
Observable = a.Select(b => new ArbitraryCommonClass() { K = a.Key, M = b, D = null })
});
var combinedStreams = nodeInfoStream
.Merge(groupedMessageStream)
.GroupBy(s => s.Key)
.Select(grp => grp
.Scan(
(state, next) => new { Key = state.Key, Observable = Observable.CombineLatest(state.Observable, next.Observable, (x, y) => { return x.Combine(y); }) }
)
)
.Merge()
.SelectMany(x => x.Observable.Select(a=>a));
combinedStreams.Where(x=>x.M!=null).Subscribe(x => Console.WriteLine(x.K + " " + x.M.MessageNodeInfo + " " + x.D.Latest));
}
}
Assuming the following class:
public class Product
{
public string Type { get; set; } = "Default";
public decimal Price { get; set; }
}
Here's a use of GroupBy with Scan (shows the average product price grouped by type). The trick is to Select over the grouped observable to get to the individual groupings, do whatever, then (presumably) merge them back together. You could collapse the Select and the Merge into a single SelectMany, but it can be easier to read when separated:
var productSubject = new Subject<Product>();
var printSignal = new Subject<Unit>();
var latestAverages = productSubject.GroupBy(p => p.Type)
.Select(g => g
.Scan((0, 0.0m), (state, item) => (state.Item1 + 1, state.Item2 + item.Price)) //hold in state the count and the running total for each group
.Select(t => (g.Key, t.Item2 / t.Item1)) //divide to get the average
)
.Merge()
.Scan(ImmutableDictionary<string, decimal>.Empty, (state, t) => state.SetItem(t.Key, t.Item2)); //Finally, cache the average by group.
printSignal.WithLatestFrom(latestAverages, (_, d) => d)
.Subscribe(avgs =>
{
foreach (var avg in avgs)
{
Console.WriteLine($"ProductType: {avg.Key}. Average: {avg.Value}");
}
Console.WriteLine();
});
var productsList = new List<Product>()
{
new Product { Price = 1.00m },
new Product { Price = 2.00m },
new Product { Price = 3.00m },
new Product { Price = 2.00m, Type = "Alternate" },
new Product { Price = 4.00m, Type = "Alternate" },
new Product { Price = 6.00m, Type = "Alternate" },
};
productsList.ForEach(p => productSubject.OnNext(p));
printSignal.OnNext(Unit.Default);
productSubject.OnNext(new Product { Price = 4.0m });
printSignal.OnNext(Unit.Default);
productSubject.OnNext(new Product { Price = 8.0m, Type = "Alternate" });
printSignal.OnNext(Unit.Default);
This uses nuget package System.Collections.Immutable.
I Made a new notMapped class "BuyingHistory", that have some property (not all) of two database tables
how to fill this class with entity? I made the conditions, but how do I select the properties to a list? (I know how to do it for one property but not for a list)
IQueryable<BuyingHistory> _buyingList =
_db.Orders
.Join(_db.EventPages
,o => o.EventID
,e => e.ID
,(o, e) => new { orders = o, events = e })
.Where(o => o.orders.UserID == LS.CurrentUser.ID)
.Select( // I don't know how to continue
it's work in this way bellow, but how can I do it in one command like the example above
var _List =
_db.Orders
.Join(_db.EventPages
, o => o.EventID
, e => e.ID
, (o, e) => new { orders = o, events = e })
.Where(o => o.orders.UserID == LS.CurrentUser.ID).ToList();
List<BuyingHistory> _buyingList = new List<BuyingHistory>();
foreach (var item in _List)
{
_buyingList.Add(new BuyingHistory()
{
CreatedDate = item.orders.CreatedDate,
EventName = item.events.Title,
NumberOfTickets = item.orders.TicketNumber,
OrderID = item.orders.ID,
Status = item.orders.Status.ToString(),
Total = item.orders.TicketNumber
});
}
I'd use query syntax to begin with, and then do the query like so:
from ord in _db.Orders
join evt in _db.EventPages on ord.EventID equals evt.ID
where ord.UserID == LS.CurrentUser.ID
select new BuyingHistory
{
CreatedDate = ord.CreatedDate,
EventName = evt.Title,
NumberOfTickets = ord.TicketNumber,
OrderID = ord.ID,
Status = ord.Status.ToString(),
Total = ord.TicketNumber
})
If you have EF version 6 the ToString() won't throw exceptions. If not, you have to change the type of BuyingHistory.Status into the type coming from the database.