EF Code first join based on a range - entity-framework

I am using EF v6 Code First against an existing SQL DB (so the schema is fairly fixed).
I need to build a model for the following classes and wanted to know if there is a was a way to map a relationship between a Reading and a TemperatureCategory where the Reading's temperature falls between the lower and upper bounds of the TemperatureCategory?
public class Reading
{
public int Id { get; set; }
...
public double Temperature { get; set; }
...
public virtual TemperatureCategory Category { get; set; }
}
public class TemperatureCategory
{
public int Id { get; set; }
public string Description { get; set; }
public double LowerBound { get; set; }
public double UpperBound { get; set; }
...
}
Many thanks

It's an interesting idea, but no, you can't. Joins are always based on equality.
So you have to query the TemperatureCategory. For instance
from r in context.Readings
let category = context.TemperatureCategories
.FirstOrDefault(c => c.LowerBound < r.Temperature
&& c.UpperBound >= r.Temperature)
select new { Reading = r, Category = category.Description }

Related

Combining multiple IQueryable from different object types for TreeList DataSource

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 :)

Entity Framework 6 (code first) using child collection foreign key without the parents primary key

I really think I am missing something here that's probably really simple that's not jumping out at me.
I have these objects and I am trying to join a parent object to a child collection but not necessarily using the parent's primary key. In sql I can do this pretty easily, but it's bugging me why this cannot happen using code first. I am trying to join CompetitorMatchInformation to BrandSkuPricing by the ErpSkuId.
public class CompetitorMatchInformation {
[Key(), Column("MatchId")]
public long MatchId { get; set; }
[Column("ErpSkuId")]
public int? ErpSkuId { get; set; }
[Column("CompetitorId")]
public int CompetitorId { get; set; }
[ForeignKey("CompetitorId")]
public virtual Competitors Competitor { get; set; }
[ForeignKey("CompetitorItemToErpSkuMatchId")]
//[ForeignKey("ErpSkuId")]
public virtual List<BrandSkuPricing> BrandSkuPricing { get; set; }
}
public class Competitors
{
[Key(), Column("CompetitorId")]
public int CompetitorId { get; set; }
[Column("CompetitorName")]
public string CompetitorName { get; set; }
}
public class BrandSkuPricing
{
[Key(), Column("BrandSkuId")]
public int BrandSkuId { get; set; }
[Column("CompetitorItemToErpSkuMatchId")]
public long CompetitorItemToErpSkuMatchId { get; set; }
[Column("ErpSkuId")]
public int? ErpSkuId { get; set; }
[Column("Price")]
public decimal? Price { get; set; }
[Column("BrandId")]
public int? BrandId { get; set; }
[Column("BrandSourceSytemId")]
public string BrandSourceSytemId { get; set; }
[Column("BrandName")]
public string BrandName { get; set; }
[Column("BrandSkuNumber")]
public string BrandSkuNumber { get; set; }
}
The Competitor comes over correctly, but the child collection not so much. This isn't a normal scenario I know, but the underlying view for BrandSkuPricing has a relationship that's not entirely normal.
The query I am using is
public List<CompetitorMatchInformation> GetCompetitorMatchInfoByCompetitorItemId(long competitorItemId, int? brandId = null)
{
var query = this.Entity.Include(x => x.CurrentChallenges).Include(x => x.BrandSkuPricing);
var list = query.Where(x => x.CompetitorItemId == competitorItemId &&
((x.CurrentChallenges.Count > 0 && x.CurrentChallenges.Any(w => !w.IsResolved)) ||
x.CurrentChallenges.Count == 0))
.ToList();
list.ForEach(l =>
{
if (brandId.HasValue)
{
l.BrandSkuPricing = l.BrandSkuPricing.Where(x => x.BrandId == brandId).ToList();
}
});
return list;
}
And in the model builder, I have nothing. I have tried but cannot get it to work even in the builder. Anyway I can get the child collection to join on ErpSkuId? I have changed the underlying view to pull in the CompetitorItemToErpSkuMatchId so it working that way, but this scenario of joining on something that isn't a key will come up for me a lot soon.
Thanks!

How to join two model and display them in view in mvc 3.0 EF 5

I have two tables which have primary and foriegn key concept. I want to get the combined data on behalf of those keys. i don't know how to bind both the table into single model and display it into view.
Model
public class TVSerialModel
{
public Int32 Serial_ID { get; set; } // primary key
public string Serial_Name { get; set; }
public int? Release_Year { get; set; }
}
public class TVSerialEpisodeModel
{
public Int64 Video_ID { get; set; }
public Int32 Serial_ID { get; set; }// foriegn key
public string Episode_Name { get; set; }
public string Description { get; set; }
public DateTime Uploaded_Time { get; set; }
}
public class TVSerial_Episode_VM
{
public IEnumerable<TVSerialEpisodeModel> tvserialEpisode { get; set; }
public IEnumerable<TVSerialModel> Tvserial { get; set; }
}
Controller
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.
Join(tvContext.dbTvSerials, p => p.Serial_ID, r => r.Serial_ID,(p, r) => new { p, r }).
Select(o => new TVSerial_Episode_VM
{ ****what should i write here to get all columns from both table**** }).
Take(9).ToList();
return View(tves);
}
Expected Result
If TVSerialEpisode has a property TVSerial, you can just dot through your foreign keys.
cDBContext.dbTvSerialEpisode
.Select(t =>
new {
t.TVSerial.Serial_ID,
t.TVSerial.Serial_Name,
t.Episode_Name
})
.Take(9)
.ToList();
You need to improve little bit the models you used with EF. You must include the reference object in model.
Like this
public virtual TVSerialModel TVSerialModel { get; set; }
in main table. This way you can select referred table too.
EF Include
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.Include("TVSerialEpisodeModel")
.Include("TVSerialModel").ToList();
return View(tves);
}

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);
});

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);
}