C# Reactive Extensions (rx) FirstOrDefault enumerates entire collection - c#

It seems that the expected behavior of FirstOrDefault is to complete after finding an item that matches the predicate and the expected behavior of concat is to evaluate lazily. However, the following example enumerates the entire collection even though the predicate matches the first item.
(Thanks for the friendlier code Shlomo)
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
Is this the expected behavior? Is there a way to defer the enumeration of the objects (specifically the building in this case) until truly needed?

The following is Linqpad-friendly code equivalent to what you have:
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
If you change GetObservable to the following, you'll get the desired result:
public IObservable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.ToObservable().Select(i => Observable.Return(BuildEntity(i)));
}
It appears the implementation of Concat<TSource>(IEnumerable<IObservable<TSource>>) is eager in evaluating the enumerable, whereas the implementation of Concat<TSource>(IObservable<IObservable<TSource>>) and ToObservable<TSource>(IEnumerable<TSource>) maintain laziness appropriately. I can't say I know why.

Related

Cartesian Product of Anonymous type

I am working on code which will give Cartesian product of two anonymous types. These 2 anonymous types are generated from database.
Code for 1st anonymous type:
private IEnumerable<object> GetItem()
{
return _unitOfWork.GetRepository<Item>()
.ListAll()
.Select(x => new
{
itemId = x.Id,
itemName = x.Name
})
}
Code for 2nd anonymous type:
private IEnumerable<object> GetVenue()
{
return _unitOfWork.GetRepository<Venue>()
.ListAll()
.Select(x => new
{
locationName = x.Address.City,
venueId = x.VenueId,
venueName = x.Name
})
}
I have following method to get the data and perform Cartesian product and return the data.
public object GetRestrictLookupInfo(IEnumerable<int> lookupCombinations)
{
IEnumerable<object> restrictList = new List<object>();
if (lookupCombinations.Contains(1))
{
var tempProductProfileList = GetItem();
restrictList = tempProductProfileList.AsEnumerable();
}
if (lookupCombinations.Contains(2))
{
var tempProductGroupList = GetVenue();
restrictList = (from a in restrictList.AsEnumerable()
from b in tempProductGroupList.AsEnumerable()
select new { a, b });
}
return restrictList;
}
I have controller which calls this method and return data in json format.
Controller Code
public HttpResponseMessage GetData(IEnumerable<int> lookupCombinations)
{
var lookupRestrictInfo = _sellerService.GetRestrictLookupInfo(lookupCombinations);
return Request.CreateResponse(HttpStatusCode.OK, lookupRestrictInfo);
}
Response expected is:-
[ {
"itemId": 1,
"itemName": "Music",
"locationName": "Paris",
"venueId": 99,
"venueName": "Royal Festival Hall"
} ]
Response which I receive is
[ {
"a": {
"itemId": 1,
"itemName": "Music"
},
"b": {
"locationName": "Paris",
"venueId": 99,
"venueName": "Royal Festival Hall" } }]
I am not able to get the expected JSON string.
You should start with the simplest possible code that shows your problem; your code above has a lot of complexities that may (or may not) have anything to do with your problem. Is this about manipulating anonymous types? Doing a Cartesian product with LINQ? Converting an object to JSON?
Here's one possible answer to what you might be looking for; notice that you can pass around anonymous types using generics instead of object.
namespace AnonymousTypes
{
class Program
{
static string Serialize(object o)
{
var d = (dynamic)o;
return d.ItemId.ToString() + d.ItemName + d.VenueId.ToString() + d.LocationName + d.VenueName;
}
static string GetData<T>(IEnumerable<T> result)
{
var retval = new StringBuilder();
foreach (var r in result)
retval.Append(Serialize(r));
return retval.ToString();
}
static string GetRestrictLookupInfo()
{
var restrictList = new[] { new { Id = 1, Name = "Music" }, new { Id = 2, Name = "TV" } };
var tempProductGroupList = new[] { new { LocationName = "Paris", Id = 99, Name = "Royal Festival Hall" } };
var result = from item in restrictList
from venue in tempProductGroupList
select new
{
ItemId = item.Id,
ItemName = item.Name,
LocationName = venue.LocationName,
VenueId = venue.Id,
VenueName = venue.Name
};
return GetData(result);
}
public static string GetData()
{
return GetRestrictLookupInfo();
}
static void Main(string[] args)
{
var result = GetData();
}
}
}
If that's not what you're looking for, you might start with code that doesn't use anonymous types, such as
namespace AnonymousTypes
{
sealed class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
sealed class Venue
{
public string LocationName { get; set; }
public int Id { get; set; }
public string Name { get; set; }
}
sealed class ItemAndVenue
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public string LocationName { get; set; }
public int VenueId { get; set; }
public string VenueName { get; set; }
}
class Program
{
static IEnumerable<Item> GetItem()
{
return new[] { new Item { Id = 1, Name = "Music" } };
}
static IEnumerable<Venue> GetVenue()
{
return new[] { new Venue { LocationName = "Paris", Id = 99, Name = "Royal Festival Hall" } };
}
static IEnumerable<ItemAndVenue> GetRestrictLookupInfo()
{
var restrictList = GetItem();
var tempProductGroupList = GetVenue();
var result = from item in restrictList
from venue in tempProductGroupList
select new ItemAndVenue
{
ItemId = item.Id,
ItemName = item.Name,
LocationName = venue.LocationName,
VenueId = venue.Id,
VenueName = venue.Name
};
return result;
}
static string GetData()
{
var v = GetRestrictLookupInfo().First();
return v.ItemId.ToString() + v.ItemName + v.VenueId.ToString() + v.LocationName + v.VenueName;
}
static void Main(string[] args)
{
var result = GetData();
}
}
}
In order to produce a single item in the output you need to create a new type, named or anonymous. Since you are using objects rather than actual types, the quickest approach is to cast them to dynamic:
var tempProductGroupList = GetVenue();
restrictList = (from a in restrictList.Cast<dynamic>()
from b in tempProductGroupList.Cast<dynamic>()
select new {
itemId = (int)a.itemId,
itemName = (string)a.itemName,
locationName = (string)b.locationName,
venueId = (int)b.venueId,
venueName = (string)b.venueName
});
This code is tightly coupled to the code producing both lists, because it assumes the knowledge of the field names of types passed into it dynamically. Any change in the structure of source data must be followed by a change in the code making combinations. In addition, it defeats run-time checking, so you need to be very careful with this code.
Try to create a simple object instead of nesting:
select new { a.itemId, a.itemName, b.locationName }
Like an option:
public object GetRestrictLookupInfo(IEnumerable<int> lookupCombinations)
{
List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
if (lookupCombinations.Contains(1))
{
var tmp = _unitOfWork.GetRepository<Item>()
.ListAll()
.Select(x => new
{
itemId = x.Id,
itemName = x.Name
})
.Select(x =>
{
var dic = new Dictionary<string, object>();
dic.Add(nameof(x.itemId), x.itemId);
dic.Add(nameof(x.itemName), x.itemName);
return dic;
});
result.AddRange(tmp);
}
if (lookupCombinations.Contains(2))
{
var tmp = _unitOfWork.GetRepository<Venue>()
.ListAll()
.Select(x => new
{
locationName = x.Address.City,
venueId = x.VenueId,
venueName = x.Name
})
.Select(x =>
{
var dic = new Dictionary<string, object>();
dic.Add(nameof(x.locationName), x.locationName);
dic.Add(nameof(x.venueId), x.venueId);
dic.Add(nameof(x.venueName), x.venueName);
return dic;
});
result = result.SelectMany(r => tmp.Select(t => r.Concat(t)));
}
return result;
}
It looks like some magic. I uses dictionary instead of object. It can be make in more clear way (extract few methods), but the idea should be clear.
Then, during serialization it will be presented as you need.

How to do Cascading Include in LiteDB

here is example on how to store cross-referenced entities in LiteDB. LiteDB stores the cross-referenced entities perfectly fine, but problem comes when I am trying to find/load entities back. My goal is NOT ONLY the requested entity but also referenced ones. There is quick tutorial section "DbRef for cross references" on LiteDB webpage how one can realize it. LiteDB has "Include" option (which is called before "FindAll") which says which referenced entities must be loaded as well. I am trying to achieve it in this code example but with no results, i.e, the code raises Exception("D_Ref") meaning "D_Ref" reference is not loaded:
namespace _01_simple {
using System;
using LiteDB;
public class A {
public int Id { set; get; }
public string Name { set; get; }
public B B_Ref { set; get; }
}
public class B {
public int Id { set; get; }
public string Name { set; get; }
public C C_Ref { set; get; }
}
public class C {
public int Id { set; get; }
public string Name { set; get; }
public D D_Ref { set; get; }
}
public class D {
public int Id { set; get; }
public string Name { set; get; }
}
class Program {
static void Main(string[] args) {
test_01();
}
static string NameInDb<T>() {
var name = typeof(T).Name + "s";
return name;
}
static void test_01() {
if (System.IO.File.Exists(#"MyData.db"))
System.IO.File.Delete(#"MyData.db");
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var Bs = db.GetCollection<B>(NameInDb<B>());
var Cs = db.GetCollection<C>(NameInDb<C>());
var Ds = db.GetCollection<D>(NameInDb<D>());
LiteDB.BsonMapper.Global.Entity<A>().DbRef(x => x.B_Ref, NameInDb<B>());
LiteDB.BsonMapper.Global.Entity<B>().DbRef(x => x.C_Ref, NameInDb<C>());
LiteDB.BsonMapper.Global.Entity<C>().DbRef(x => x.D_Ref, NameInDb<D>());
var d = new D { Name = "I am D." };
var c = new C { Name = "I am C.", D_Ref = d };
var b = new B { Name = "I am B.", C_Ref = c };
var a = new A { Name = "I am A.", B_Ref = b };
Ds.Insert(d);
Cs.Insert(c);
Bs.Insert(b);
As.Insert(a);
}
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var all_a = As
.Include(x => x.B_Ref)
.FindAll();
foreach (var a in all_a) {
if (a.B_Ref == null)
throw new Exception("B_Ref");
if (a.B_Ref.C_Ref == null)
throw new Exception("C_Ref");
if (a.B_Ref.C_Ref.D_Ref == null)
throw new Exception("D_Ref");
}
}
}
}}
after small research I've resolved the issue simply by adding extra "Include" parameterize by "x => x.B_Ref.C_Ref" lambda where x.B_Ref.C_Ref is a path in hierarchy of references:
var all_a = As
.Include(x => x.B_Ref)
.Include(x => x.B_Ref.C_Ref)
.FindAll();
Here is complete example
namespace _01_simple {
using System;
using LiteDB;
public class A {
public int Id { set; get; }
public string Name { set; get; }
public B B_Ref { set; get; }
}
public class B {
public int Id { set; get; }
public string Name { set; get; }
public C C_Ref { set; get; }
}
public class C {
public int Id { set; get; }
public string Name { set; get; }
public D D_Ref { set; get; }
}
public class D {
public int Id { set; get; }
public string Name { set; get; }
}
class Program {
static void Main(string[] args) {
test_01();
}
static string NameInDb<T>() {
var name = typeof(T).Name + "s";
return name;
}
static void test_01() {
if (System.IO.File.Exists(#"MyData.db"))
System.IO.File.Delete(#"MyData.db");
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var Bs = db.GetCollection<B>(NameInDb<B>());
var Cs = db.GetCollection<C>(NameInDb<C>());
var Ds = db.GetCollection<D>(NameInDb<D>());
LiteDB.BsonMapper.Global.Entity<A>().DbRef(x => x.B_Ref, NameInDb<B>());
LiteDB.BsonMapper.Global.Entity<B>().DbRef(x => x.C_Ref, NameInDb<C>());
LiteDB.BsonMapper.Global.Entity<C>().DbRef(x => x.D_Ref, NameInDb<D>());
var d = new D { Name = "I am D." };
var c = new C { Name = "I am C.", D_Ref = d };
var b = new B { Name = "I am B.", C_Ref = c };
var a = new A { Name = "I am A.", B_Ref = b };
Ds.Insert(d);
Cs.Insert(c);
Bs.Insert(b);
As.Insert(a);
}
using (var db = new LiteDatabase(#"MyData.db")) {
var As = db.GetCollection<A>(NameInDb<A>());
var all_a = As
.Include(x => x.B_Ref)
.Include(x => x.B_Ref.C_Ref)
.Include(x => x.B_Ref.C_Ref.D_Ref)
.FindAll();
foreach (var a in all_a) {
if (a.B_Ref == null)
throw new Exception("B_Ref");
if (a.B_Ref.C_Ref == null)
throw new Exception("C_Ref");
if (a.B_Ref.C_Ref.D_Ref == null)
throw new Exception("D_Ref");
}
}
}
}}
I hope it saves someone's time.
Update: LiteDB author says there is no support for Cascading Include. But it is planned in the next version (see issue). Consider, once, let say, B_Ref is a Lite of B, then there is no mechanism to force deeper Include.

How to bypass or implement IComparable

I get the below error and I can't understand why and how to get past it (or implement icomparable).
I'm trying to get the property Group from the object that has the biggest count of Group using Max().
public class Program
{
private static void Main(string[] args) {
var list = new List<Foo>();
for (var i = 0; i < 10; i++) {
list.Add(new Foo());
if (i == 5) {
var foo = new Foo() {
Group = { "One", "Two", "Three" }
};
list.Add(foo);
}
}
var maxGroup = list.Max(x => x.Group); //throws error
}
}
public class Foo {
public Guid Id { get; } = new Guid();
public int Field1 { get; set; }
public int Field2 { get; set; }
public int Field3 { get; set; }
public int Field4 { get; set; }
public List<string> Group { get; set; } = new List<string>();
}
at least one object must implement icomparable
I'm trying to get the property Group from the object that has the longest list
You don't want to do Max for that. Simply order by the length of the list, and take the first one:
Foo res = list.OrderByDescending(x => x.Group.Count).FirstOrDefault();
if (res != null) {
List<string> longestList = res.Group;
}

Linq At least one object must implement IComparable

I am trying to order a List of Entities that contains another list of Entities. I have implemented IComparable for all entities and still get the exception. All of the examples I have seen address the issue where you have one list and you order by a given field in that list but not where you have a list of lists. This issue is happening for Linq to Objects per below and also for Linq to Entities. What am I missing?
[TestClass]
public class OrderBy
{
[TestMethod]
public void OrderByTest()
{
var hobbies = new Collection<Hobby> { new Hobby { HobbyId = 1, Name = "Eating" }, new Hobby() { HobbyId = 2, Name = "Breathing" } };
var p1 = new Person
{
PersonId = 1,
Name = "A",
PersonHobbies = new Collection<PersonHobby> { new PersonHobby() { PersonHobbyId = 1}}
};
var p2 = new Person
{
PersonId = 2,
Name = "Z",
PersonHobbies = new Collection<PersonHobby> { new PersonHobby() { PersonHobbyId = 2 }}
};
var people = new List<Person> { p1, p2 };
var pplEnumerable = people.AsEnumerable();
pplEnumerable = pplEnumerable.OrderByDescending(r => r.PersonHobbies.OrderByDescending(p => p.Hobby.Name));
foreach (var person in pplEnumerable)
{
Console.WriteLine(person.Name);
}
}
public class Person : IComparable
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual ICollection<PersonHobby> PersonHobbies { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
var otherPerson = obj as Person;
return PersonId.CompareTo(otherPerson.PersonId);
}
}
public class PersonHobby : IComparable
{
public int PersonHobbyId { get; set; }
public int HobbyId { get; set; }
public virtual Person Person{ get; set; }
public int PersonId { get; set; }
public virtual Hobby Hobby { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
var otherPersonHobby = obj as PersonHobby;
return PersonHobbyId.CompareTo(otherPersonHobby.PersonHobbyId);
}
}
public class Hobby : IComparable
{
public int HobbyId { get; set; }
public string Name { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
var otherHobby = obj as Hobby;
return HobbyId.CompareTo(otherHobby.HobbyId);
}
}
}
You cannot apply ordering to lists by default. You need to write up a custom class (sort of EquatableList etc.) or use LINQ Except & Intersect operators to compare lists.
But based on your comment, if you're looking for the LINQ equivalent of:
select * from Person p join PersonHobby ph
on ph.PersonId = p.PersonId join Hobby h
on h.HobbyId = ph.HobbyId order by h.Name
then that can be achieved as:
var query = people.SelectMany(p => p.PersonHobbies)
.Join(hobbies, ph => ph.HobbyId, h => h.HobbyId,
(ph, h) => new
{
Person = ph.Person, PersonHobby = ph, Hobby = h
})
.OrderBy(r => r.Hobby.Name);
basically we join person, person hobbies and hobby on the keys, and project all columns and sort it by the hobby.name field, as mentioned in your SQL.

compare properties in classes of list in class

What I've got are two classes which each contain Lists of Classes with propperties of different types. The first list is an updated version of the second and i need to find all differences (deleted/added classes in lists and updated classes).
public class ClassOfKb
{
public List<Data> KbData {get;set;}
public List<Info> KbInfo {get;set;}
}
class Data
{
public Guid ID {get;set}
public byte[] file {get;set}
public string name {get;set}
}
class Info
{
public Guid ID {get;set}
public string text {get;set}
public DateTime date {get;set}
}
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
first KbA and KbB will be filled from the same DataSet, then i delete, add and modify some of KbA Child-Classes.
now i need to compare KbA with KbB to find out where the differences are. i need the ID of deleted or added classes in KbA and the exact changes of modified Child-Classes properties. How would i do this? Preffered with Linq.
I suggest that create two comparers one for Data and one for Info
class DataComparer : IEqualityComparer<Data>
{
public bool Equals(Data x, Data y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Data d)
{
//logic to return a hash code
}
}
class InfoComparer : IEqualityComparer<Info>
{
public bool Equals(Info x, Info y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Info i)
{
//logic to return a hash code
}
}
The you can use Intersect and Except LINQ methods
IEnumerable<Data> DataInAandNotInB = KbA.KbData.Except(KbB.KbData,new DataComparer());
IEnumerable<Info> InfoInAandInB = KbA.KbInfo.Intersect(KbB.KbInfo,new InfoComparer ());
For simplicity, I skipped comparison of the byte array and DateTime data membes, only left the IDs and the string data members, but to add them you will need some small modification.
The test is very-very basic, but shows all three of the changes options:
static void Main(string[] args)
{
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
// Test data --------
Data data1 = new Data() { ID = Guid.NewGuid(), name = "111" };
Data data2 = new Data() { ID = Guid.NewGuid(), name = "222" };
Data data2_changed = new Data() { ID = data2.ID, name = "222_changed" };
Data data3 = new Data() { ID = Guid.NewGuid(), name = "333" };
Info info1 = new Info() { ID = Guid.NewGuid(), text = "aaa" };
Info info2 = new Info() { ID = Guid.NewGuid(), text = "bbb" };
Info info2_changed = new Info() { ID = info2.ID, text = "bbb_changed" };
Info info3 = new Info() { ID = Guid.NewGuid(), text = "ccc" };
KbA.KbData.Add(data1);
KbA.KbData.Add(data2);
KbA.KbInfo.Add(info1);
KbA.KbInfo.Add(info2);
KbB.KbData.Add(data2_changed);
KbB.KbData.Add(data3);
KbB.KbInfo.Add(info2_changed);
KbB.KbInfo.Add(info3);
// end of test data ---------
// here is the solution:
var indexes = Enumerable.Range(0, KbA.KbData.Count);
var deleted = from i in indexes
where !KbB.KbData.Select((n) => n.ID).Contains(KbA.KbData[i].ID)
select new
{
Name = KbA.KbData[i].name,
KbDataID = KbA.KbData[i].ID,
KbInfoID = KbA.KbInfo[i].ID
};
Console.WriteLine("deleted:");
foreach (var val in deleted)
{
Console.WriteLine(val.Name);
}
var added = from i in indexes
where !KbA.KbData.Select((n) => n.ID).Contains(KbB.KbData[i].ID)
select new
{
Name = KbB.KbData[i].name,
KbDataID = KbB.KbData[i].ID,
KbInfoID = KbB.KbInfo[i].ID
};
Console.WriteLine("added:");
foreach (var val in added)
{
Console.WriteLine(val.Name);
}
var changed = from i in indexes
from j in indexes
where KbB.KbData[i].ID == KbA.KbData[j].ID &&
(//KbB.KbData[i].file != KbA.KbData[j].file ||
KbB.KbData[i].name != KbA.KbData[j].name ||
//KbB.KbInfo[i].date != KbA.KbInfo[j].date ||
KbB.KbInfo[i].text != KbA.KbInfo[j].text
)
select new
{
Name = KbA.KbData[j].name,
KbDataID = KbA.KbData[j].ID,
KbInfoID = KbA.KbInfo[j].ID
};
Console.WriteLine("changed:");
foreach (var val in changed)
{
Console.WriteLine(val.Name);
}
Console.ReadLine();
}
}
public class ClassOfKb
{
public List<Data> KbData = new List<Data>();
public List<Info> KbInfo = new List<Info>();
}
public class Data
{
public Guid ID { get; set; }
public byte[] file { get; set; }
public string name { get; set; }
}
public class Info
{
public Guid ID { get; set; }
public string text { get; set; }
public DateTime date { get; set; }
}

Categories

Resources