Include Properties at All Level - c#

My model is:
public class WhereClause
{
[Key]
public int Id { get; set; }
public int? ColumnId { get; set; }
[StringLength(100)]
public string Logic { get; set; }
[StringLength(250)]
public string Value { get; set; }
public List<WhereClause?> WhereClauses { get; set; }
public short HierarchyCount { get; set; }
}
As you see, model contain self-list of object and i want to include all inner list in any level at one fetch. Based on Entity Framework - Include Multiple Levels of Properties I know that can use Include and ThenInclude but i don't know how many level of inner list in model. currently i store HierarchyCount property at model for this purpose and create string of include like .Include("WhereClauses,WhereClauses.WhereClauses,WhereClauses.WhereClauses.WhereClauses") . but are there any solution better of that?
Update:
WhereClause is part of Report Table and can't load whole WhereClause for every report query:
public class Report:ModelAbstract
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
public Table Table { get; set; }
public List<SelectClause> SelectClauses { get; set; }
[ForeignKey("WhereClause")]
public int? WhereClauseId { get; set; }
public WhereClause WhereClause { get; set; }
}

You can use Recursive CTE for such task. CTE will help to return all children objects then we can filter out root. Under hood, EF Core will correct children collections automatically.
var sql = #"
WITH Clauses AS
(
SELECT
Id
FROM WhereClause
WHERE Id = {0}
UNION ALL
SELECT
w.Id
FROM Clauses c
JOIN WhereClause w ON w.WhereClauseId = c.Id
)
SELECT wc.*
FROM WhereClause wc
JOIN Clauses c ON wc.Id = c.Id";
var whereId = 7;
var whereClause = ctx.WhereClause
.FromSqlRaw(sql, whereId)
.ToList()
.First(w => w.Id == whereId);

Related

Linq to SQL - Get Related Data

I've got some data that I need to return some of its related data and put it all in a model. I have all the appropriate fields setup in my model, and looks like this:
public class ComputerModel
{
public int MachineId { get; set; }
public int GroupId { get; set; }
public int SoftwareVersionId { get; set; }
public string GroupName { get; set; }
public string SoftwareVersion { get; set; }
public string IPAddress { get; set; }
public string HostName { get; set; }
public string MACAddress { get; set; }
public string Title { get; set; }
public bool IsIGMonitor { get; set; }
public string UpTime { get; set; }
public DateTime DateEntered { get; set; }
public string EnteredBy { get; set; }
public Nullable<DateTime> DateUpdated { get; set; }
public string UpdatedBy { get; set; }
public ICollection<MachineRole> MachineRoles { get; set; }
public ICollection<Role> Roles { get; set; }
}
Here's the linq statement I'm trying to use:
var query = (from m in unitOfWork.Context.Machines
join u in unitOfWork.Context.Users
on m.EnteredBy equals u.UserId into EntByUser
from EnteredByUser in EntByUser.DefaultIfEmpty()
join u2 in unitOfWork.Context.Users
on m.UpdatedBy equals u2.UserId into UpdByUser
from UpdatedByUser in UpdByUser.DefaultIfEmpty()
join g in unitOfWork.Context.Groups
on m.GroupId equals g.GroupId into Grp
from Groups in Grp.DefaultIfEmpty()
join s in unitOfWork.Context.SoftwareVersions
on m.SoftwareVersionId equals s.SoftwareVersionId into SW
from SoftwareVersions in SW.DefaultIfEmpty()
join mr in unitOfWork.Context.MachineRoles
on m.MachineId equals mr.MachineId into MachRoles
from MachineRoles in MachRoles.DefaultIfEmpty()
join r in unitOfWork.Context.Roles
on MachineRoles.RoleId equals r.RoleId into Rolz
from Rolz2 in Rolz.DefaultIfEmpty()
select new ComputerModel()
{
MachineId = m.MachineId,
GroupId = m.GroupId,
SoftwareVersionId = m.SoftwareVersionId,
GroupName = Groups.GroupName,
SoftwareVersion = SoftwareVersions.Version,
IPAddress = m.IPAddress,
HostName = m.HostName,
MACAddress = m.MACAddress,
Title = m.Title,
IsIGMonitor = m.IsIGMonitor,
UpTime = m.UpTime,
DateEntered = m.DateEntered,
DateUpdated = m.DateUpdated,
EnteredBy = EnteredByUser.FirstName + " " + EnteredByUser.LastName,
UpdatedBy = UpdatedByUser.FirstName + " " + UpdatedByUser.LastName,
MachineRoles = m.MachineRoles,
Roles = ?????
}).ToList();
I can get MachineRoles to populate but I cannot get Roles to populate. I've tried Roles = Rolz2 but Rolz returns a single instance of Role, not a collection.
How can I get this query to return Machines and the related data for both MachineRoles and Roles?
I've looked at the following articles but haven't had much luck:
This SO Article
Loading Related Data - MSDN
Using Include with Entity Framework
UPDATE
I notice if I remove my model and use an anonymous type, then I don't get any errors:
select new ()
{
GroupName = Groups.GroupName,
......
}).ToList();
But this doesn't help me in my project because I need to use a Model for the data.
If all the above tables are related via PK-FK relationship you can use linq lambda functions .Include() to include related tables and then use Navigation properties to access data.
If the tables are not related you can use LINQ left joins as shown in http://www.devcurry.com/2011/01/linq-left-join-example-in-c.html.
It looks like you need a mix of inner and left joins. The above example and only achieve inner joins.

Linq query to include items with on a join object

I'm trying to get a collection of objects using a Linq query that would include child objects. I can do an include on the main table and get results. But if I do an include on one of the tables I join to, I do not get the object that should be returned by the include.
Here are my models:
public class RequestReviewViewModel
{
public Guid RequestId { get; set; }
public Guid ResourceToReviewId { get; set; }
public Guid ReviewRequiredId { get; set; }
public ReviewRequired ReviewRequired { get; set; }
}
public class Required : BaseEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid RequiredId { get; set; }
[Display(Name = "Review Name")]
public int ReviewNameId { get; set; }
[ForeignKey("ReviewNameId")]
public ReviewName ReviewName { get; set; }
}
Here's the linq query I'm trying. The line Required.Include(revr => revr.ReviewName) doesn't seem to do anything:
var requests = (from req in Request.Include(rr => rr.Resource)
join revreq in Required.Include(revr => revr.ReviewName)
on req.ReviewRequiredId equals revreq.RequiredId
where req.IsActive
select new RequestReviewViewModel
{
RequestId = req.RequestId,
ResourceToReviewId = req.ResourceToReviewId,
ReviewRequiredId = req.ReviewRequiredId,
Required = revreq
};
requests.FirstOrDefault().Required.ReviewName.Dump();
While requests.FirstOrDefault().Required.ReviewNameId has a value, the ReviewName object is null. The relationship is in the database, and was created by Code First.
Your ReviewName is not declared virtual which enables eager/lazy loading (and automatic change-tracking to be complete). Add virtual and it should work:
public virtual ReviewName ReviewName { get; set; }

Use multiple conditions in join LINQ. i,e AND

How to use multiple condition in LINQ joins, i.e. in my scenario I need to get all the users from table User where group ID = 4 from table UserInGroup, where UserInGroup is intermediate table between User and Group table as in SQL-T we use join as
select *
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == 4
....
In another approach I am using lambda expression along with LINQ, how I can apply where groupID = 4 in following one??
public IEnumerable<User> GetUsersByGroupID(int _groupID)
{
List<User> _listedUsersByGroupID = new List<User>();
using(var _uow = new UserManagement_UnitOfWork())
{
_listedUsersByGroupID = (from _users in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
select _users).ToList();
return _listedUsersByGroupID;
}
}
User Model
[Table("User")]
public class User
{
public User() { }
[Key]
public int UserID { get; set; }
[StringLength(250)]
[Required]
public string FirstName { get; set; }
[StringLength(250)]
[Required]
public string LastName { get; set; }
[Required]
public int Age { get; set; }
[StringLength(250)]
[Required]
public string EmailAddress { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
UserInGroup Model
[Table("UserInGroup")]
public class UserInGroup
{
public UserInGroup() { }
[Key]
public int UserGroupID { get; set; }
[Required]
public int UserID { get; set; }
[Required]
public int GroupID { get; set; }
public User User { get; set; }
public Group Group { get; set; }
}
Group Model
public class Group
{
public Group() { }
[Key]
public int GroupID { get; set; }
[StringLength(250)]
[Required]
public string GroupName { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
You only need to add a condition to filter the users that belong to the group 4. Try this:
_listedUsersByGroupID = (from _user in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
where user.UserInGroup.Any(ug=>ug.groupID==4)
select _user).ToList();
Lambda query would look something like:
ctx.User.Where(user=>
ctx.UserInGroup.Any(userIngroup=>
user.userID == userIngroup.userID && userIngroup.groupID == 4
)
)
That however is just query, if you want to get results add .AsList() or .AsEnumerable() to end.
However you can write silly and inefficient code if you do not fully understand what you are doing. I would reccomend you try this instead:
var publications = ctx.Database.SqlQuery<UserResults>(String.Format(#"
select UserID, FirstName,LastName,Age,EmailAddress,UserInGroup
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == {0}
order by UserID
", Config.Group));
Where Config.Group is 4; UserResults can be User table as well, if you do not want other fields. You need to execute or enumerate over the sql query to use the data and like before you can use .AsList() or .AsEnumerable() for that.
Variable ctx is database context. For example:
using (var ctx = new toxicEntities())
{
}

How to implement Entity Framework Code First join

I'm starting to use Entity Framework Code First.
Suppose to have such POCO's (ultimately simplified):
public class BortStructure
{
public Guid Id { get; set; }
public String Name { get; set; }
}
public class Slot
{
public Guid Id { get; set; }
public String Name { get; set; }
public BortStructure { get; set; }
}
public class SystemType
{
public Guid Id { get; set; }
public String Name {get; set; }
}
public class SlotSystemType
{
public Guid Id { get; set; }
public Slot Slot { get; set; }
public SystemType SystemType {get; set; }
}
and a context
public MyContext : DbContext
{
public DbSet<BortStructure> BortStructures { get; set; }
public DbSet<Slot> Slots{ get; set; }
public DbSet<SystemType> SystemTypes { get; set; }
public DbSet<SlotSystemType> SlotSystemTypes { get; set; }
}
I have a task to get BortStructure by Id with list of attached Slots, each one with list of systemTypes attached.
Using SQL allowed me to do that with some JOIN's:
SELECT BortStructures.Id, BortStructures.Name, Slots.Id,
Slots.Name, SystemType.Id, SystemType.Name FROM
((BortStructures LEFT JOIN Slots ON BortStructures.Id = Slots.BortStructureId)
LEFT JOIN SlotSystemTypes ON SlotSystemTypes.SlotId = Slots.Id)
LEFT JOIN SystemTypes ON SystemTypes.Id = SlotSystemTypes.SystemTypeId
WHERE BortStructures.Id='XXXXXX' ORDER BY Slots.Id, SystemType.Id
But with Entity Framework Code First I don't have any idea howto do that.
If I use
var slotSystemTypes = from sl in MyContext.SlotSystemTypes
where sl.Slot.BortStructure.Id = XXXXXX
orderby sl.Slot.Id, sl.SystemType.Id
select sl;
i, of course, will receive nothing if BortStructure consists of no Slots/Slots without any SystemTypes attached.
Instead of getting BortStructure with empty list of Slots/with Slots, each one with empty list of SystemTypes attached as I expect to get.
Is there any way to archive that with single LINQ query for my database configuration?
You can use join operator example:
string[] categories = new string[]{
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood" };
List<Product> products = GetProductList();
var q =
from c in categories
join p in products on c equals p.Category
select new { Category = c, p.ProductName };
foreach (var v in q)
{
Console.WriteLine(v.ProductName + ": " + v.Category);
}
more samples in: http://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9

Linq Find Item by Primary Key and display Name(that lives in a different table)

I am displaying a record from my database. The record pulls data from other tables and uses a Int in the main table to represent the value so Item table has a Division equal to 1 and the Division table 1 = ALL . Now that i am displaying the records i am trying to turn the 1 into all. All the ID fields show the int. Which is what my code is telling it to do. But i am trying to display the name and when i do that i get a lot of red. It cannot find the name. CatagoryID should be CategoryName.
Hope that makes sense.
if (!IsPostBack)
{
string v = Request.QueryString["ContactID"];
int itemid;
int.TryParse(v, out itemid);
var customerInfo = GetCustomerInfo(itemid);
CONTACTID.Text = customerInfo[0].ContactID.ToString();
ContactTitle.Text = customerInfo[0].ContactTitlesID.ToString();
ContactNameB.Text = customerInfo[0].ContactName;
DropDownAddCategory.Text = customerInfo[0].CategoryID.ToString();
DDLAddDivision.Text = customerInfo[0].DivisionID.ToString();
ContactPhoneBox.Text = customerInfo[0].ContactOPhone;
ContactCellBox.Text = customerInfo[0].ContactCell;
ContactEmailBox.Text = customerInfo[0].ContactEmail;
CextB.Text = customerInfo[0].Ext;
}
private List<Solutions.Models.Contact> GetCustomerInfo(int itemid)
{
using (ItemContext context = new ItemContext())
{
return (from c in context.Contacts
where c.ContactID == itemid
select c).ToList();
}
}
This is the model
public class Contact
{
[ScaffoldColumn(false)]
public int ContactID { get; set; }
public System.DateTime ContactCreated { get; set; }
public string ContactName { get; set; }
public int? ContactTitlesID { get; set; }
public string ContactOPhone { get; set; }
public bool cApproved { get; set; }
public string User { get; set; }
public string ContactCell { get; set; }
public string ContactEmail { get; set; }
public int? DivisionID { get; set; }
public int? CategoryID { get; set; }
[StringLength(5)]
public string CExt { get; set; }
public virtual Division Division { get; set; }
public virtual Category Category { get; set; }
public virtual ContactTitle ContactTitle { get; set; }
public string Ext { get; set; }
}
With Entity Framework you can include related entities in query results:
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).ToList();
This will return contacts with Catagory objects: customerInfo.Catagory.CategoryName
BTW instead of returning list of contacts and selecting first one by index (thus possibly having index out of range exception), modify your method to return first contact (or default, if not found):
private Solutions.Models.Contact GetCustomerInfo(int itemid)
{
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).FirstOrDefault();
}
And use it this way:
var customerInfo = GetCustomerInfo(itemid);
if (customerInfo != null)
{
CONTACTID.Text = customerInfo.ContactID.ToString();
// etc
}
Are you using LINQ to SQL or Entity Framework? Check your model again and make sure the relationship between the two tables are setup correctly. The relationship may be missing from the model, and causing this problem.

Categories