Combining multiple IQueryable from different object types for TreeList DataSource - entity-framework

I search for a way to combine two or more IQueryables from different Object types in order to use it as a datasource for my treelist.
For the treelist I use the DevExpress WinForms component "TreeList".
It provides me the properties "KeyFieldName" which is usually mapped to the "ID" and the ParentFieldName which is mapped to the parent id in order to build a hierarchy.
I use entity framework 6 as or mapper.
I have the two following classes I would need to combine:
XObject:
[Table("tbl_objects")]
public class XObject
{
[Column("id")]
public int Id { get; set; }
[Column("display_name")]
public String DisplayName { get; set; }
[Column("description")]
public String Description { get; set; }
[Column("usage_reason")]
public String UsageReason { get; set; }
[Column("is_network_compatible")]
public bool IsNetworkCompatible { get; set; }
[Column("ip_address")]
public String IpAddress { get; set; }
[Column("network_name")]
public String NetworkName { get; set; }
[Column("serial_number")]
public String SerialNumber { get; set; }
[Column("manufacturer_identification_code")]
public String ManufacturerIdentificationCode { get; set; }
[Column("web_link")]
public String WebLink { get; set; }
[Column("warranty")]
public int WarrantyInDays { get; set; }
[Column("ref_manufacturer")]
public virtual XManufacturer Manufacturer { get; set; }
[Column("ref_order")]
public virtual XOrder Order { get; set; }
[Column("ref_owner")]
public virtual XOwner Owner { get; set; }
[Column("ref_room")]
public virtual XRoom Room { get; set; }
[Column("ref_object_folder")]
public virtual XObjectFolder ObjectFolder { get; set; }
public virtual ICollection<XAdditionalObjectData> AdditionalObjectData { get; set; }
}
XObjectFolder:
[Table("tbl_object_folders")]
public class XObjectFolder
{
[Column("id")]
public int Id { get; set; }
[Column("display_name")]
public String DisplayName { get; set; }
[Column("short_name")]
public String ShortName { get; set; }
[Column("ref_parent_folder")]
public virtual XObjectFolder ParentFolder { get; set; }
public virtual ICollection<XObjectFolder> ChildFolders { get; set; }
public virtual ICollection<XObject> Objects { get; set; }
[NotMapped]
public int ParentFolderId { get { return ParentFolder == null ? -1 : ParentFolder.Id; } }
}
As you've probably already seen, an object folder can contain subfolders but also objects.
My goal is to see this as one "datasource" in my treelist.
For example like this:
Object Folder A
Object Sub-Folder A
Object 1
Object 1
In other questions here I've found the possibilities to concat or union queryables, but that only works with them being the same type:
using (var db = new XDbContext(_conString))
{
// Queryables
var ofs = from of in db.ObjectFolders orderby of.DisplayName ascending select of; // <- All ObjectFolders
var obs = from obj in db.Objects orderby obj.DisplayName ascending select obj; // <- All Objects
// Concat them
var comb = ofs.Concat(obs); // <- not the same type
// As DataSource for my TreeList
TreeListObjects.DataSource = comb.ToList();
}
Which is why I am searching for a good way to make this possible.
I could also imagine me using a pretty bad approach to reach my goal. So I am open to suggestions. This is a personal project which I do to improve myself at stuff.
Thanks in advance!
EDIT
So I managed to get a step further by using an interface both classes share:
public interface ITreeListCombinable
{
int Id { get; set; }
int ParentId { get; }
String DisplayName { get; set; }
}
But... who would've thought... there occures another problem:
Have a look at the db structure:
Db_Struture
Since both objects are stored in different tables, the id's will certainly not be unique when combining them.
Which is necessary when setting the datasource.

Solution:
So I've taken my own approach to my problem and it worked out.
Full disclosure -> I consider myself a beginner, so this solution is probably not the best. Still, if anyone is in a similar situation, here's how it could work:
First I created an interface, which both the folder and objects share:
ITreeListCombinable
public interface ITreeListCombinable
{
int Id { get; set; }
int ParentId { get; }
int ListId { get; set; }
int ParentListId { get; set; }
String DisplayName { get; set; }
ObjectTreeListElementTypes TreeListElementType { get; }
}
I then made sure, both my XObject and XObjectFolder classes held the ObjectTreeListElementTypes value they're corresponding to:
ObjectTreeListElementTypes Enum:
public enum ObjectTreeListElementTypes
{
Folder,
Object
}
Classes:
[NotMapped]
public ObjectTreeListElementTypes TreeListElementType => ObjectTreeListElementTypes.Folder; // or *.Object for that matter
So afterwards I've wrote my own "controller" which handles my specific scenario.
ObjectTreeListElementController:
public class ObjectTreeListElementController
{
private List<ITreeListCombinable> _list;
public ObjectTreeListElementController()
{
_list = new List<ITreeListCombinable>();
}
public void AddRange(List<ITreeListCombinable> list)
{
// add incoming items to private _list
_list.AddRange(list);
}
public List<ITreeListCombinable> GetDataSourceList()
{
// create auto increment list id
var listId = 0;
foreach (var item in _list)
{
item.ListId = listId;
listId++;
}
// set new parent list id according to incremental list id
foreach (var item in _list)
{
var parents = _list.Where(x => x.Id == item.ParentId && x.TreeListElementType == ObjectTreeListElementTypes.Folder);
if (parents.Count() > 0)
item.ParentListId = parents.First().ListId;
else
item.ParentListId = -1;
}
return _list;
}
}
Essentially, when calling the GetDataSourceList() method, it firstly distributes incremental, temporary list-ids.
In a second loop I then search for the original parent id and match the tree list element type. If none is found, this folder is a root folder in my treelist, if one is found, the given list-id becomes the parent list id:
using (var db = new XDbContext(_conString))
{
// Queryables
IQueryable<ITreeListCombinable> ofs = from of in db.ObjectFolders orderby of.DisplayName ascending select of;
IQueryable<ITreeListCombinable> objs = from obj in db.Objects orderby obj.DisplayName ascending select obj;
var lofs = ofs.ToList();
var lobjs = objs.ToList();
var ctrl = new ObjectTreeListElementController();
ctrl.AddRange(lofs);
ctrl.AddRange(lobjs);
var sourceList = ctrl.GetDataSourceList();
// As DataSource for my TreeList
TreeListObjects.DataSource = sourceList;
}
And this brought me the exact output I've wanted:
Hope this helps another beginner :)

Related

Join multiple one to many related tables in EF and select as view model

Database Models of my Application are:
public class Restaurant
{
public int Id { get; set; }
.........
}
public class Review
{
public int Id { get; set; }
public string ReviewTitle { get; set; }
public string ReviewContent { get; set; }
public int UserId { get; set; }
public int RestaurantId { get; set; }
}
public ReviewHelpful
{
public int Id { get; set; }
public int ReviewId { get; set; }
public bool IsHelpfull { get; set; }
}
public ReviewImage
{
public int Id { get; set; }
public string ImageLink { get; set; }
public int ReviewId { get; set; }
}
There is no navigation property in any table. In ReviewHelpful table, If user finds helpfull of this review than value is true otherwise false.
Now I want to create a view-model Like this:
public class ReviewViewModel
{
public int ReviewId { get; set; }
public int RestaurantId { get; set; }
public string ReviewTitle { get; set; }
public string ReviewContent { get; set; }
public int UserId { get; set; }
public int NumberOfHelpfull { get; set; }
public int NumberOfNotHelpfull { get; set; }
public List<string> ImagesLinks { get; set; }
}
For that reason, I want to write this kind of query :
var reviews = (from review in _foodTrackerContext.RestaurantReviews
join helpful in _foodTrackerContext.Helpfuls on review.Id equals helpful.ReviewId
join reviewPicture in _foodTrackerContext.ReviewPictures on review.Id equals reviewPicture.ReviewId
where review.ResturantId == 2
select new ReviewViewModel()
{
Id = review.Id,
RestaurantId = 2,
ReviewTitle = review.ReviewTitle,
ReviewContent = review.ReviewContent,
NumberOfHelpfull = .. ??,
NumberOfNotHelpfull = ... ??,
ImagesLinks = ... ???
}
I can not retrieve HelpfulYes, HelpfulNo, ImagesLinks with this query. What would be query for finding these variables?.
This query produces multiple rows for single review with each ReviewImage and each ReviewHelpful.
The query that ypu need to do is this one:
var model =
from review in ctx.Reviews
where review.RestaurantId == 2
join helpful in ctx.ReviewHelpfuls
on review.Id equals helpful.ReviewId into helpfuls
join image in ctx.ReviewImages
on review.Id equals image.ReviewId into images
select new RestaurantReviewViewModel
{
Id = review.Id,
RestaurantId = 2,
ReviewTitle = review.ReviewTitle,
ReviewContent = review.ReviewContent,
NumberOfHelpfull = helpfuls.Count(h => h.IsHelpfull),
NumberOfNotHelpfull = helpfuls.Count(h => !h.IsHelpfull),
ImagesLinks = (from image in images select image.ImageLink).ToList()
};
Please, note that when you do a one to manyh join you need to include an into to give a nameto the joined entities to be able to work on them.
I've used the dot syntax for selecting the count, but you could use the query syntax if you wanted. Over time, I've found dot synatx more natural.
NOTE: if you used navigation properties this would become much easier. Why are you not using them? With navigation properties you don't need to make the joins explicitly, as they are already available.
List<ReviewViewModel> listModel = new List<ReviewViewModel>();
context.dbRestaurant
.include("Review")
.include("Review.ReviewHelpful")
.include("Review.ReviewImage").ToList().ForEach((item) =>
{
ReviewViewModel model = new ReviewViewModel();
model.ID = item.ID
listModel.Add(model);
});

How do i check whether a collection has been eager loaded with Entity Framework?

I recently used the following code
var errorCount =
split.Profiles.SelectMany(p => p.Logs)
.Count(l => l.LogTypeId == (int)LogType.Error);
errorCount returned zero because I forgot to include my logs table when I built the split entity.
How can I detect whether the split.Profiles.Logs collection has been eager loaded?
I am using Model First.
the class for Profile is
public partial class Profile
{
public Profile()
{
this.Log = new HashSet<Log>();
}
public int ProfileId { get; set; }
public int SplitId { get; set; }
public string Filename { get; set; }
public System.DateTime StartTime { get; set; }
public System.DateTime EndTime { get; set; }
public virtual ICollection<Log> Log { get; set; }
public virtual SplitUpload SplitUpload { get; set; }
}
#Hopeless you pointed me in the right direct. First I had to check the first collection was loaded. Then I needed to check that if it had any members then their child collection was also loaded

LINQ to Entities does not recognize the method ... get_Item(Int32)

(Please read before marking as duplicate as my particular scenario is unique)
I have the following code:
// Get each treasure hunt
var treasureHunts = dbContext.TreasureHunts.Where(i => i.UserName == User.Identity.Name).ToList();
// Populate each treasure hunt with the list of leaderboard entries
for (int i = 0; i <= treasureHunts.Count; i++)
{
treasureHunts[i].Leaderboard = dbContext.Leaderboard.Where(
leaderboard => leaderboard.TreasureHuntId == treasureHunts[i].TreasureHuntId).ToList();
}
On running the program, I get the following error from the second database query (dbContext.Leaderboard.Where...):
LINQ to Entities does not recognize the method
'QrCodeTreasureHunter.Models.TreasureHuntDetails get_Item(Int32)'
method, and this method cannot be translated into a store expression.
In the first query, I'm getting each of the treasure hunts associated with a particular user.
In the second part, I'm attempting to iterate through each of the treasure hunts, and populate the treasure hunt's Leaderboard List property with the associated leaderboard entries from my Leaderboard table.
From what I understand from reading around is that this query isn't possible in its current form with Entity Framework.
What workarounds or solutions would you be able to recommend to solve this problem? The ideal solution would involve no changes to the data models.
If it's relevant, here is the TreasureHunt model:
public class TreasureHuntDetails
{
public TreasureHuntDetails()
{
Clues = new List<Clue>();
Leaderboard = new List<Leaderboard>();
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof (TreasureHuntDetails), null, int.MaxValue,
false, true, null);
xml.SetSerializer<TreasureHuntDetails>(dcs);
}
[Key]
public int TreasureHuntId { get; set; }
[Required]
public virtual string UserName { get; set; }
[Required]
public String Name { get; set; }
public String Description { get; set; }
[Required]
public String Password { get; set; }
public String CompletionMessage { get; set; }
public String State { get; set; }
public List<Clue> Clues { get; set; }
public List<Leaderboard> Leaderboard { get; set; }
}
And here is the Leaderboard model:
public class Leaderboard
{
[Key]
public int Id { get; set; }
public int TreasureHuntId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Completion { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public Int64 TimeTaken { get; set; }
public TreasureHuntDetails TreasureHuntDetails { get; set; }
}
Good luck!
I'm not able to test it right now but it could be the indexer, try this:
foreach (var treauserHunt in treasureHunts)
{
treasureHunt.Leaderboard = dbContext.Leaderboard.Where(leaderboard =>
leaderboard.TreasureHuntId == treasureHunt.TreasureHuntId).ToList();
}
I'm not sure this is the problem, but I remember having some issues with indexing in arrays in LINQ queries, just can't remember if it was with the LINQ method syntax (the one you are using) or the other (the SQL-like);
Thanks for the answer; it solved my problem. I still wanted to use a for loop so I did something like this:
for (int i = 0; i <= treasureHunts.Count; i++)
{
var thisTreasureHunt = treasureHunts[i];
treasureHunts[i].Leaderboard = dbContext.Leaderboard.Where(
leaderboard => leaderboard.TreasureHuntId == thisTreasureHunt.TreasureHuntId).ToList);
}

Eager loading including navigational property of derived class

Sample class structure
class Order
{
public int Id { get; set; }
public DateTime Date { get; set; }
public List<OrderDetail> Details { get; set; }
}
class OrderDetail
{
public int Id { get; set; }
public int Qty { get; set; }
public Item Item { get; set; }
}
class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
class ElectronicItem : Item
{
public MoreDetail Detail { get; set; }
}
class MoreDetail
{
public int Id { get; set; }
public string SomeData { get; set; }
}
In order to populate order object with all navigational properties, I wrote
context.Orders.Include("Details").Include("Details.Item")
I also want to load MoreDetail object, hence I tried
context.Orders.Include("Details").Include("Details.Item.Detail")
It didn't work. How to load complete Order object?
It is currently not possible but it is feature requested by community on User DataVoice as you already found. There is also related bug on MS Connect.
You simply cannot eager load navigation properties of derived types but you can load them with separate query:
var moreDetails = context.MoreDetails;
EF should automatically fix your navigation properties. If you use filtering on orders in your original query you must apply that filter in more details query as well:
var moreDetails = cotnext.MoreDetials.Where(m => m.Item.Order ....);

Internationalization of content in Entity Framework

I keep coming across an i18n requirement where my data (not my UI) needs to be internationalized.
public class FooEntity
{
public long Id { get; set; }
public string Code { get; set; } // Some values might not need i18n
public string Name { get; set } // but e.g. this needs internationalized
public string Description { get; set; } // and this too
}
What are some approaches I could use?
Some things I've tried:-
1) Store a resource key in the db
public class FooEntity
{
...
public string NameKey { get; set; }
public string DescriptionKey { get; set; }
}
Pros: No need for complicated queries to get a translated entity. System.Globalization handles fallbacks for you.
Cons: Translations can't easily be managed by an admin user (have to deploy resource files whenever my Foos change).
2) Use a LocalizableString entity type
public class FooEntity
{
...
public int NameId { get; set; }
public virtual LocalizableString Name { get; set; }
public int NameId { get; set; }
public virtual LocalizableString Description { get; set; }
}
public class LocalizableString
{
public int Id { get; set; }
public ICollection<LocalizedString> LocalizedStrings { get; set; }
}
public class LocalizedString
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual LocalizableString Parent { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Value { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string CultureCode { get; set; }
}
Pros: All localised strings in the same table. Validation can be performed per-string.
Cons: Queries are horrid. Have to .Include the LocalizedStrings table once for each localizable string on the parent entity. Fallbacks are hard and involve extensive joining. Haven't found a way to avoid N+1 when retrieving e.g. data for a table.
3) Use a parent entity with all the invariant properties and child entities containing all the localized properties
public class FooEntity
{
...
public ICollection<FooTranslation> Translations { get; set; }
}
public class FooTranslation
{
public long Id { get; set; }
public int ParentId { get; set; }
public virtual FooEntity Parent { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set }
public string Description { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string CultureCode { get; set; }
}
Pros: Not as hard (but still too hard!) to get a full translation of an entity into memory.
Cons: Double the number of entities. Can't handle partial translations of an entity - especially the case where, say, Name is coming from es but Description is coming from es-AR.
I have three requirements for a solution
Users can edit entities, languages, and translations at runtime
Users can supply partial translations with missing strings coming from a fallback as per System.Globalization
Entities can be brought into memory without running into e.g. N+1 issues
Why don't you take the best of both worlds?
Have a CustomResourceManager that handles the loading of resources and picking the right culture and use a CustomResourceReader that uses whatever backing store you like. A basic implementation could look like this, relying on convention of the Resourceky being Typename_PropertyName_PropertyValue. If for some reason the structure of the backingstore(csv/excel/mssql/table structure) need to change you only have the change the implementation of the ResourceReader.
As an added bonus I also got the real/transparent proxy going.
ResourceManager
class MyRM:ResourceManager
{
readonly Dictionary<CultureInfo, ResourceSet> sets = new Dictionary<CultureInfo, ResourceSet>();
public void UnCache(CultureInfo ci)
{
sets.Remove(ci):
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
{
ResourceSet set;
if (!sets.TryGetValue(culture, out set))
{
IResourceReader rdr = new MyRR(culture);
set = new ResourceSet(rdr);
sets.Add(culture,set);
}
return set;
}
// sets Localized values on properties
public T GetEntity<T>(T obj)
{
var entityType = typeof(T);
foreach (var prop in entityType.GetProperties(
BindingFlags.Instance
| BindingFlags.Public)
.Where(p => p.PropertyType == typeof(string)
&& p.CanWrite
&& p.CanRead))
{
// FooEntity_Name_(content of Name field)
var key = String.Format("{0}_{1}_{2}",
entityType.Name,
prop.Name,
prop.GetValue(obj,null));
var val = GetString(key);
// only set if a value was found
if (!String.IsNullOrEmpty(val))
{
prop.SetValue(obj, val, null);
}
}
return obj;
}
}
ResourceReader
class MyRR:IResourceReader
{
private readonly Dictionary<string, string> _dict;
public MyRR(CultureInfo ci)
{
_dict = new Dictionary<string, string>();
// get from some storage (here a hardcoded Dictionary)
// You have to be able to deliver a IDictionaryEnumerator
switch (ci.Name)
{
case "nl-NL":
_dict.Add("FooEntity_Name_Dutch", "nederlands");
_dict.Add("FooEntity_Name_German", "duits");
break;
case "en-US":
_dict.Add("FooEntity_Name_Dutch", "The Netherlands");
break;
case "en":
_dict.Add("FooEntity_Name_Dutch", "undutchables");
_dict.Add("FooEntity_Name_German", "german");
break;
case "": // invariant
_dict.Add("FooEntity_Name_Dutch", "dutch");
_dict.Add("FooEntity_Name_German", "german?");
break;
default:
Trace.WriteLine(ci.Name+" has no resources");
break;
}
}
public System.Collections.IDictionaryEnumerator GetEnumerator()
{
return _dict.GetEnumerator();
}
// left out not implemented interface members
}
Usage
var rm = new MyRM();
var f = new FooEntity();
f.Name = "Dutch";
var fl = rm.GetEntity(f);
Console.WriteLine(f.Name);
Thread.CurrentThread.CurrentUICulture = new CultureInfo("nl-NL");
f.Name = "Dutch";
var dl = rm.GetEntity(f);
Console.WriteLine(f.Name);
RealProxy
public class Localizer<T>: RealProxy
{
MyRM rm = new MyRM();
private T obj;
public Localizer(T o)
: base(typeof(T))
{
obj = o;
}
public override IMessage Invoke(IMessage msg)
{
var meth = msg.Properties["__MethodName"].ToString();
var bf = BindingFlags.Public | BindingFlags.Instance ;
if (meth.StartsWith("set_"))
{
meth = meth.Substring(4);
bf |= BindingFlags.SetProperty;
}
if (meth.StartsWith("get_"))
{
// get the value...
meth = meth.Substring(4);
var key = String.Format("{0}_{1}_{2}",
typeof (T).Name,
meth,
typeof (T).GetProperty(meth, BindingFlags.Public | BindingFlags.Instance
|BindingFlags.GetProperty).
GetValue(obj, null));
// but use it for a localized lookup (rm is the ResourceManager)
var val = rm.GetString(key);
// return the localized value
return new ReturnMessage(val, null, 0, null, null);
}
var args = new object[0];
if (msg.Properties["__Args"] != null)
{
args = (object[]) msg.Properties["__Args"];
}
var res = typeof (T).InvokeMember(meth,
bf
, null, obj, args);
return new ReturnMessage(res, null, 0, null, null);
}
}
Real/Transparent proxy usage
var f = new FooEntity();
f.Name = "Dutch";
var l = new Localizer<FooEntity>(f);
var fp = (FooEntity) l.GetTransparentProxy();
fp.Name = "Dutch"; // notice you can use the proxy as is,
// it updates the actual FooEntity
var localizedValue = fp.Name;
First one is worthy if you have static content in database. For example if you have categories that relatively are not going to be changed by user. You can change them at next deploy. I do not like this solution personally. I do not consider this as a nice solution. This is just an escape of the problem.
Second one is the best but can cause a problem when you have two or more localizable fields in one entity. You can simplify it a bit and hard code languages on it like this
public class LocalizedString
{
public int Id { get; set; }
public string EnglishText { get; set; }
public string ItalianText { get; set; }
public string ArmenianText { get; set; }
}
Third one is not a good one neither. From this structure I can't be sure that all nodes (literals, lines, strings etc.) translated in specific culture.
Do not generalize too much. Each problem is kind of specialized and it needs specialized solution too. Too much generalization makes unjustified issues.