I'm battling to retrieve a single Model/Entity using EntityFramework and Linq.
I have a Business with Members, I'm trying to retrieve the users' business based on the BusinessMembers table/entity.
I have the following entities/models:
public partial class Business
{
public Business()
{
BusinessMembers = new HashSet<BusinessMember>();
}
public int ID { get; set; }
public int ID_BusinessStatus { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Phone { get; set; }
public virtual BusinessStatus BusinessStatus { get; set; }
public virtual ICollection<BusinessMember> BusinessMembers { get; set; }
}
and
public partial class BusinessStatus
{
public BusinessStatus()
{
Businesses = new HashSet<Business>();
}
public int ID { get; set; }
[Required]
[StringLength(3)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual ICollection<Business> Businesses { get; set; }
}
I then have the following method to return a Single Business Instance:
public Business GetBusinessForUser(string userId)
{
using (var db = new MyContext(_connectionString))
{
var q =
from b in db.Businesses
join bm in db.BusinessMembers on b.ID equals bm.ID_Business
where bm.UserId == userId
select b;
return q.FirstOrDefault();
}
}
Problem I'm having is I want to 'Include' the BusinessStatus for that single Business entity and don't know how to do this.
I need to be able to do:
Business businessEntity = _dataServices.GetBusinessForUser(userId);
if (businessEntity.BusinessStatus.Code == "ACT")
{
// Whatever
}
First, add this to the list of usings
using System.Data.Entity;
Then you can use the .Include() method to load additional children in your query
public Business GetBusinessForUser(string userId)
{
using (var db = new MyContext(_connectionString))
{
var q =
(from b in db.Businesses
join bm in db.BusinessMembers on b.ID equals bm.ID_Business
where bm.UserId == userId
select b).Include(business => business.BusinessStatus);
return q.FirstOrDefault();
}
}
I would also avoid using the join method explicitly. If your model has correct relationships (e.g. foreign keys), you should be able to just do this:
var q = db.Businesses
.Where(b => b.BusinessMembers.Any(bm => bm.UserId == userId))
.Include(b => b.BusinessStatus);
return q.FirstOrDefault();
or even
var q = db.BusinessMembers
.Where(bm => bm.UserId == userId)
.Select(bm => bm.Business)
.Include(b => b.BusinessStatus);
Related
I want to use Linq to duplicate this T-SQL query on a sports teams database, to look up the experienced players in handball teams:
Select TE.TeamName, PL.FirstName, PL.LastName
From T_Team as TE
Inner Join T_Player As PL
On PL.Team_ID = TE.Team_ID
And PL.ExpLevel = 'Experienced'
Where TE.SportName = 'Handball'
I've tried creating two entities for my two tables:
public class TTeam
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string SportName { get; set; }
public virtual List<TPlayer> TeamPlayers { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TTeam> p_ebpTable)
{
p_etbTable.ToTable("T_TEAM");
p_etbTable.HasKey(t => new { t.TeamId }).HasName("PK_TEAMID_T_TEAM");
// Column definitions
// Foreign Keys
p_etbTable.HasMany(t => t.TeamPlayers).
WithOne(p => p.CurrentTeam).
HasPrincipalKey(t => t.TeamId).
HasForeignKey(p => p.TeamId);
}
}
and
public class TPlayer
{
public int PlayerId { get; set; }
public int TeamId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ExpLevel { get; set; }
public virtual TTeam CurrentTeam { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TPlayer> p_ebpTable)
{
p_etbTable.ToTable("T_PLAYER");
p_etbTable.HasKey(t => new { t.PlayerId }).HasName("PK_PLAYERID_T_PLAYER");
// Column definitions
// Foreign Keys
p_etbTable.HasOne(p => p.CurrentTeam).
WithMany(t => t.TeamPlayers).
HasForeignKey(p => p.TeamId).
HasPrincipalKey(t => t.TeamId);
}
}
then use them in
using Microsoft.EntityFrameworkCore;
IEnumerable<TTeam> z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Include(te => te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced"));
but looping through z_enbHandballTeams in a foreach, throws an InvalidOperationException with the message "Lambda expression used inside Include is not valid".
(I guess it goes without saying that ExpLevel is a number and SportName is actually SportId, but I felt it would look easier to read that way.)
What am I doing wrong?
EF Core 3.1.x do not support filtered Include. Workaround is to do that via Select
var z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Select(te => new TTeam
{
TeamId = te.TeamId,
TeamName = te.TeamName,
SportName = te.SportName,
TeamPlayers = te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced")
.ToList()
});
I'm having an issue with a series of Include/ThenInclude in a query.
Here is my EntityFrameworkCore Query :
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions.Where(compo => compo.Date == compositionDate))
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(token)
?? throw new NotFoundException(nameof(Fund), fundId);
I recieve a 'CompositionDate does not exists' error.
As you can see the CompositionDate property is at the Compositions Level.
When I check the SQL generated I get this in a subquery Select statement :
SELECT f1."CompositionFundId", f1."CompositionDate", f1."AssetId", f1."Amount", a."Id", a."CountryCode", a."Currency", a."FundCompositionDate", a."FundCompositionFundId", a."Isin", a."Name", a."SecurityType", a."Ticker", a."Coupon", a."GicsSector", a."InvestmentCase", a."IpoDate", a."Theme"
FROM "FundCompositionItem" AS f1
INNER JOIN "Asset" AS a ON f1."AssetId" = a."Id"
Those 2 properties a."FundCompositionDate", a."FundCompositionFundId" doesn't exists at the 'Asset' level.
They exists in the parent (at the 'Where' level on the first Include).
I'm using Postgres provider for EFcore. Could this be the issue?
Should I be using the select anonymous type .Select(x => new { Fund = x, Compo = x.Compo.Where(...), etc... }?
I would like to preserve the navigation properties if possible. (accessing assets from compositionItems)
Any help would be much appreciated.
Edit:
Models as requested by Atiyar:
public class Portfolio : AuditableEntity
{
public Guid Id { get; set; }
public Guid Name{ get; set; }
}
public class Fund : Portfolio
{
// Irrelevant properties
public IList<FundComposition> Compositions { get; } = new List<FundComposition>();
}
public class FundComposition
{
public Fund Fund { get; set; }
// Primary key / Foreign key
public Guid FundId { get; set; }
// Primary Key
public DateTime Date { get; set; }
public List<FundCompositionItem> CompositionItems { get; set; } = new();
}
public class FundCompositionItem
{
public FundComposition Composition { get; set; }
// Primary Key
public Guid CompositionFundId { get; set; }
// Primary Key
public DateTime CompositionDate { get; set; }
public Asset Asset { get; set; }
// Primary Key
public Guid AssetId { get; set; }
public double Amount { get; set; }
}
public class Asset : BaseEntity
{
// Primary Key
public Guid Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
// more properties
}
In my experience, I've applied the Include() and ThenInclude() first and then applied the any conditional clauses afterwards. I'm also not sure if using Where inside of an include method does what you expect it to.
You can also apply your conditional in the first parameter of .FirstOrDefaultAsync().
var fund = await funds.Where(x => x.Id == fundId)
.Include(f => f.Compositions)
.ThenInclude(c => c.CompositionItems)
.ThenInclude(item => item.Asset)
.FirstOrDefaultAsync(x =>
x.Id == fundId && x.Compositions.Any(compo => compo.Date == compositionDate),
token
)
I would like to select a where statement that adds items to a list where only product codes match. I have it so it gets all of the products sold in the sale but I would like there were statement to get only products in this sale.
PS: This is really hard to explain
Model
public class userSales
{
public string Name { get; set; }
public string UserName { get; set; }
public int Sale_Id { get; set; }
public int CostumerID { get; set; }
public string Sale_Date { get; set; }
public string Paid { get; set; }
public Nullable<int> Sale_Cost { get; set; }
public string Discount_Code { get; set; }
public List<SaleProduct> saleProductsList { get; set; }
}
public class SaleProduct
{
public int SaleID { get; set; }
public string ProductCode { get; set; }
public int ProductCount { get; set; }
public string Image_Path { get; set; }
public string Shoot_Date { get; set; }
public string Shoot_Info { get; set; }
}
Linq statement where I'm having trouble:
var test = (from _ClientData in db.ClientDatas
join _salesInfo in db.Sales_Infoes
on _ClientData.CostumerID
equals _salesInfo.CostumerID
where _ClientData.UserName == _userName
select new userSales()
{
CostumerID = _ClientData.CostumerID,
Name = _ClientData.Name,
UserName = _ClientData.UserName,
Sale_Id = _salesInfo.Sale_Id, // This is the item i would like to use in my were statement
Sale_Date = _salesInfo.Sale_Date,
Sale_Cost = _salesInfo.Sale_Cost,
Discount_Code = _salesInfo.Discount_Code,
Paid = _salesInfo.Paid,
// Problem here
saleProductsList = db.SaleProducts.Where()
}).ToList();
Got to this based on the answer:
var reult = db.ClientDatas.Where(a => a.UserName == _userName)
.Join(db.Sales_Infoes,
a => a.CostumerID,
b => b.CostumerID,
(a, b) => new userSales()
{
CostumerID = a.CostumerID,
Discount_Code = b.Discount_Code,
Sale_Cost = b.Sale_Cost,
Sale_Id= b.Sale_Id,
Name = a.Name,
Sale_Date = b.Sale_Date,
UserName = a.UserName,
Paid = b.Paid,
saleProductsList = db.SaleProducts.Where(c => c.SaleID == b.Sale_Id).ToList()
}).ToList();
You're not looking for a where, you're looking for a join. Where filters the results on a single table, join intersects two tables which is actually what you want here.
var result = db.Sales_Infoes.Where(x => x.UserName == _userName)
.Join(db.ClientDatas,
x => x.Sale_Id,
y => y.Sale_id,
(x, y) => new userSales() {
// x is SalesInfo obj y is ClientDatas obj do assignement here
Name = y.Name,
Sale_Date = y.Sale_date
}).ToList();
Just fyi I haven't had a chance to test that but it's the basic idea. You don't need a select like in your statement because the last argument I'm passing into join is the lambda (x, y) => ... in that case x and y are the current row from each table (that we've gotten from applying our where to the user sales table then joining those results into the salesproduct table) so whatever projections you want to do occur there. The other two method args above that are the telling join which fields to compare, it's the 'key selector' lambda expression for each table.
I'm having some difficulty populating some columns in a POCO using OrmLite. I have three tables named Dog, Bowl and DogBowl. DogBowl is a junction table and holds the id of Dog and Bowl.
Dogs
PK Id: int, not null
Breed: varchar(20), not null
Name: varchar(20), not null
Bowls
PK Id: int, not null
Type: varchar(20), not null
Color: varchar(20), not null
Dogs_Bowls
PK: DogId, not null
PK: BowlId, not null
Here are the POCOs I have mapped
public class Dog : IHasId<int>
{
[AutoIncrement]
public int Id { get; set; }
[Required]
public string Breed { get; set; }
[Required]
public string Name { get; set; }
}
public class Bowl : IHasId<int>
{
[AutoIncrement]
public int Id { get; set; }
[Required]
public string Type { get; set; }
[Required]
public string Color { get; set; }
}
public class DogBowl
{
[Required]
public int DogId { get; set; }
[Required]
public int BowlId { get; set; }
[Ignore]
public string DogName { get;set; }
[Ignore]
public string BowlColor { get;set; }
}
This is the c# code I'm running.
var dogBowl = db.Select<DogBowl>(db
.From<Dog>()
.Join<Dog, DogBowl>((d, db) => d.Id == db.DogId)
.Join<DogBowl, Bowl>((db, b) => db.BowlId == b.Id)
.Where<Dog>(d => d.Id == 5))
.ToList();
The SQL I would like to produce is this:
select
db.DogId,
db.BowlId,
d.Name AS DogName,
b.Color as BowlColor
from DogBowl db
join dog d on db.DogId = d.Id
join bowl b on db.BowlId = b.Id
where d.Id = 5
My problem is that the DogBowl.DogName and DogBowl.BowlColor properties are null after the code executes. I'm using the instructions provided on https://github.com/ServiceStack/ServiceStack.OrmLite from the section entitled "Selecting multiple columns across joined tables" but it's not working. How can I get the DogBowl.DogName and DogBowl.BowlColor properties populated?
The SQL generated may be correct. You can verify the generated SQL after execution by checking the property db.GetLastSql().
The problem is that by assigning the result as
db.Select<DogBowl>
, you are creating a List of DogBowl objects. The DogBowl properties DogName and BowlColor would always be null because there is no field in the SQL statement which matches those names exactly. OrmLite will not magically figure out what goes there - you have to have them match by name.
If you want to assign the result to a "flat" object with fields from Dog and Bowl, you could define a new DTO and assign the result, like so:
public class FullDogBowl
{
public int DogId { get; set; }
public int BowlId { get; set; }
public string Breed { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Color { get; set; }
}
var dogBowl = db.Select<FullDogBowl>(db
.From<Dog>()
.Join<Dog, DogBowl>((d, db) => d.Id == db.DogId)
.Join<DogBowl, Bowl>((db, b) => db.BowlId == b.Id)
.Where<Dog>(d => d.Id == 5))
.ToList();
Alternatively, if you know exactly the SQL you want to use, just use it:
string sql = #"select
db.DogId,
db.BowlId,
d.Name AS DogName,
b.Color as BowlColor
from DogBowl db
join dog d on db.DogId = d.Id
join bowl b on db.BowlId = b.Id
where d.Id = #dog_id ";
var dogBowlList = db.SqlList<DogBowl>(sql, new { dog_id = 5, });
Wanted to add to Raul's answer that [Ignore] tells OrmLite to ignore the property completely so your approach of re-using a table as the merged POCO "view" wont work. I recommend instead splitting the resultset POCO out into a separate POCO with all the fields you want returned:
public class DogBowl
{
[Required]
public int DogId { get; set; }
[Required]
public int BowlId { get; set; }
}
public class DogBowlInfo
{
public int DogId { get; set; }
public int BowlId { get; set; }
public string DogName { get; set; }
public string BowlColor { get; set; }
}
Which now returns a populated resultset with:
using (var db = OpenDbConnection())
{
db.DropAndCreateTable<Dog>();
db.DropAndCreateTable<Bowl>();
db.DropAndCreateTable<DogBowl>();
var dog = new Dog { Breed = "Breed", Name = "Name" };
var bowl = new Bowl { Color = "Color", Type = "Type" };
db.Save(dog);
db.Save(bowl);
db.Insert(new DogBowl { DogId = dog.Id, BowlId = bowl.Id });
var dogBowl = db.Select<DogBowlInfo>(
db.From<Dog>()
.Join<Dog, DogBowl>((d, b) => d.Id == b.DogId)
.Join<DogBowl, Bowl>((d, b) => d.BowlId == b.Id)
.Where<Dog>(d => d.Id == dog.Id));
dogBowl.PrintDump();
}
I'm still learning Entity Framework and Linq-To-Entities, and I was wondering if a statement of this kind is possible:
using (var context = new MyEntities())
{
return (
from a in context.ModelSetA.Include("ModelB")
join c in context.ModelSetC on a.Id equals c.Id
join d in context.ModelSetD on a.Id equals d.Id
select new MyModelA()
{
Id = a.Id,
Name = a.Name,
ModelB = new MyModelB() { Id = a.ModelB.Id, Name = a.ModelB..Name },
ModelC = new MyModelC() { Id = c.Id, Name = c.Name },
ModelD = new MyModelD() { Id = d.Id, Name = d.Name }
}).FirstOrDefault();
}
I have to work with a pre-existing database structure, which is not very optimized, so I am unable to generate EF models without a lot of extra work. I thought it would be easy to simply create my own Models and map the data to them, but I keep getting the following error:
Unable to create a constant value of type 'MyNamespace.MyModelB'. Only
primitive types ('such as Int32, String, and Guid') are supported in
this context.
If I remove the mapping for ModelB, ModelC, and ModelD it runs correctly. Am I unable to create new nested classes with Linq-To-Entities? Or am I just writing this the wrong way?
What you have will work fine with POCOs (e.g., view models). Here's an example. You just can't construct entities this way.
Also, join is generally inappropriate for a L2E query. Use the entity navigation properties instead.
I have created your model (how I understand it) with EF 4.1 in a console application:
If you want to test it, add reference to EntityFramework.dll and paste the following into Program.cs (EF 4.1 creates DB automatically if you have SQL Server Express installed):
using System.Linq;
using System.Data.Entity;
namespace EFNestedProjection
{
// Entities
public class ModelA
{
public int Id { get; set; }
public string Name { get; set; }
public ModelB ModelB { get; set; }
}
public class ModelB
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ModelC
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ModelD
{
public int Id { get; set; }
public string Name { get; set; }
}
// Context
public class MyContext : DbContext
{
public DbSet<ModelA> ModelSetA { get; set; }
public DbSet<ModelB> ModelSetB { get; set; }
public DbSet<ModelC> ModelSetC { get; set; }
public DbSet<ModelD> ModelSetD { get; set; }
}
// ViewModels for projections, not entities
public class MyModelA
{
public int Id { get; set; }
public string Name { get; set; }
public MyModelB ModelB { get; set; }
public MyModelC ModelC { get; set; }
public MyModelD ModelD { get; set; }
}
public class MyModelB
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyModelC
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyModelD
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Create some entities in DB
using (var ctx = new MyContext())
{
var modelA = new ModelA { Name = "ModelA" };
var modelB = new ModelB { Name = "ModelB" };
var modelC = new ModelC { Name = "ModelC" };
var modelD = new ModelD { Name = "ModelD" };
modelA.ModelB = modelB;
ctx.ModelSetA.Add(modelA);
ctx.ModelSetB.Add(modelB);
ctx.ModelSetC.Add(modelC);
ctx.ModelSetD.Add(modelD);
ctx.SaveChanges();
}
// Run query
using (var ctx = new MyContext())
{
var result = (
from a in ctx.ModelSetA.Include("ModelB")
join c in ctx.ModelSetC on a.Id equals c.Id
join d in ctx.ModelSetD on a.Id equals d.Id
select new MyModelA()
{
Id = a.Id,
Name = a.Name,
ModelB = new MyModelB() {
Id = a.ModelB.Id, Name = a.ModelB.Name },
ModelC = new MyModelC() {
Id = c.Id, Name = c.Name },
ModelD = new MyModelD() {
Id = d.Id, Name = d.Name }
}).FirstOrDefault();
// No exception here
}
}
}
}
This works without problems. (I have also recreated the model from the database (which EF 4.1 had created) in EF 4.0: It works as well. Not surprising since EF 4.1 doesn't change anything in LINQ to Entities.)
Now the question is why you get an exception? My guess is that there is some important difference in your Models or ViewModels or your query compared to the simple model above which is not visible in your code example in the question.
But the general result is: Projections into nested (non-entity) classes work. (I'm using it in many situations, even with nested collections.) Answer to your question title is: Yes.
What Craig posted does not seem to work for nested entities. Craig, if I am misunderstood what you posted, please correct me.
Here is the workaround I came up with that does work:
using (var context = new MyEntities())
{
var x = (
from a in context.ModelSetA.Include("ModelB")
join c in context.ModelSetC on a.Id equals c.Id
join d in context.ModelSetD on a.Id equals d.Id
select new { a, b, c }).FirstOrDefault();
if (x == null)
return null;
return new MyModelA()
{
Id = x.a.Id,
Name = x.a.Name,
ModelB = new MyModelB() { Id = x.a.ModelB.Id, Name = x.a.ModelB..Name },
ModelC = new MyModelC() { Id = x.c.Id, Name = x.c.Name },
ModelD = new MyModelD() { Id = x.d.Id, Name = x.d.Name }
};
}
Since Entity Framework can't handle creating nested classes from within the query, I simply returned an anonymous object from my query containing the data I wanted, then mapped it to the Model