I'm having some unexpected results when using code first and I know it has to do with how EF is creating relationships but I can't figure it out. Here is what I have.
Product class, KitComponent class (hold a product, and a qty, etc)
A Product has many KitComponents
A KitComponent has many Products
End result is to show a product, and this product has some other products included as a kit. It may contain 1 of a product or 2 pieces, thats why I need a KitComponent class / table, to hold the other info.
For the longest time I struggled with the ef relationships. The database would put the ParentProductId and Parent Product into the IncludedProductID and IncludedProduct. NOW i fixed that but don't know how but the IncludedProduct field is not being populated. The IncludedProductID is now correct in database. What Am I missing so that when I pull a product and cycle through the kitCOmponents that the IncludedProduct is not null.
AND on top of that, have I correctly described the relationships in the config?
Product:
public class Product
{
public Product()
{
this.KitComponents = new HashSet<KitComponent>();
}
public int Id { get; set; }
public string PartNumber { get; set; }
public virtual ICollection<KitComponent> KitComponents { get; set; }
}
KitComponent:
public class KitComponent
{
public int Id { get; set; }
public string UOM { get; set; }
public int QTY { get; set; }
public int ParentProductId { get; set; }
public Product ParentProduct { get; set; }
public int IncludedProductId { get; set; }
public Product IncludedProduct { get; set; }
}
DBContext With fluent API code:
public class ProductContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<KitComponent> KitComponents { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasMany(p => p.KitComponents)
.WithRequired(k => k.ParentProduct)
.HasForeignKey(p => p.ParentProductId);
modelBuilder.Entity<KitComponent>().HasRequired(k => k.IncludedProduct)
.WithMany()
.HasForeignKey(k => k.IncludedProductId)
.WillCascadeOnDelete(false);
}
}
Simple Seed Method to Demo Data:
public class ProductContextInitializer : DropCreateDatabaseAlways<ProductContext>
{
private ProductContext db = new ProductContext();
protected override void Seed(ProductContext context)
{
Product prod1 = new Product { PartNumber = "P1" };
Product prod2 = new Product { PartNumber = "P2" };
Product prod3 = new Product { PartNumber = "P3" };
Product prod4 = new Product { PartNumber = "P4" };
Product prod5 = new Product { PartNumber = "P5" };
Product prod6 = new Product { PartNumber = "P6" };
Product prod7 = new Product { PartNumber = "P7" };
db.Products.Add(prod1);
db.Products.Add(prod2);
db.Products.Add(prod3);
db.Products.Add(prod4);
db.Products.Add(prod5);
//db.Products.Add(prod6);
//db.Products.Add(prod7);
db.SaveChanges();
var kitComp = new KitComponent() { IncludedProduct = prod2, QTY = 1, UOM = "EA" };
prod1.KitComponents.Add(kitComp);
db.SaveChanges();
}
}
In order for the property to load, you have to make the navigation property virtual:
public virtual Product IncludedProduct { get; set; }
All the code first conventions can be found here: https://msdn.microsoft.com/en-us/data/jj679962.aspx
Related
I am assigned the implementation of a REST GET with a complex DB model and somewhat complex output layout. Although I am a REST beginner, I have lost "rest" on this for 2 weeks spinning my wheels, and Google was of no help as well.
Here's a simplification of the existing DB I am given to work with:
Table group : {
Column id Guid
Column name string
Primary key: {id}
}
Table account
{
Column id Guid
Column name string
Primary key: {id}
}
Table groupGroupMembership
{
Column parentGroupId Guid
Column childGroupId Guid
Primary key: {parentGroupId, childGroupId}
}
Table accountGroupMembership
{
Column parentGroupId Guid
Column childAccountId Guid
Primary key: {parentGroupId, childAccountId}
}
So clearly you guessed it: There is a many-to-many relationship between parent a child groups. Hence a group can have many parent and child groups. Similarly, an account can have many parent groups.
The DB model I came up with in C# (in namespace DBAccess.Models.Tables):
public class Group
{
// properties
public Guid id { get; set; }
public string? name { get; set; }
// navigation properties
public List<GroupMemberAccount>? childAccounts { get; set; }
public List<GroupMemberGroup>? childGroups { get; set; }
public List<GroupMemberGroup>? parentGroups { get; set; }
}
public class Account
{
// properties
public Guid id { get; set; }
public string? name { get; set; }
// navigation properties
public List<GroupMemberAccount>? parentGroups { get; set; }
}
public class GroupMemberAccount
{
// properties
public Guid parentGroupId { get; set; }
public Guid childAccountId { get; set; }
//navigation properties
public Group? parentGroup { get; set; }
public Account? childAccount { get; set; }
static internal void OnModelCreating( EntityTypeBuilder<GroupMemberAccount> modelBuilder )
{
modelBuilder.HasKey(gma => new { gma.parentGroupId, gma.childAccountId });
modelBuilder
.HasOne(gma => gma.parentGroup)
.WithMany(g => g.childAccounts)
.HasForeignKey(gma => gma.parentGroupId);
modelBuilder
.HasOne(gma => gma.childAccount)
.WithMany(a => a.parentGroups)
.HasForeignKey(gma => gma.childAccountId);
}
}
public class GroupMemberGroup
{
// properties
public Guid parentGroupId { get; set; }
public Guid childGroupId { get; set; }
//navigation properties
public Group? parentGroup { get; set; }
public Group? childGroup { get; set; }
static internal void OnModelCreating(EntityTypeBuilder<GroupMemberGroup> modelBuilder)
{
modelBuilder.HasKey(gmg => new { gmg.parentGroupId, gmg.childGroupId });
modelBuilder
.HasOne(gmg => gmg.parentGroup)
.WithMany(g => g.childGroups)
.HasForeignKey(gmg => gmg.parentGroupId);
modelBuilder
.HasOne(gmg => gmg.childGroup)
.WithMany(g => g.parentGroups)
.HasForeignKey(gmg => gmg.childGroupId);
}
}
The corresponding DTO model I created:
public class Account
{
public Guid id { get; set; }
public string? name { get; set; }
public List<GroupMemberAccount>? parentGroups { get; set; }
}
public class AccountMappingProfile : AutoMapper.Profile
{
public AccountMappingProfile()
{
CreateMap<DBAccess.Models.Tables.Account, Account>();
}
}
public class Group
{
public Guid id { get; set; }
public string? Name { get; set; }
public GroupChildren children { get; set; } = null!;
};
public class GroupChildren
{
public List<GroupMemberAccount>? childAccounts { get; set; } = null!;
public List<GroupMemberGroup>? childGroups { get; set; } = null!;
}
public class GroupMemberAccount
{
public Guid parentGroupId { get; set; }
public Guid childAccountId { get; set; }
//public Group? parentgroup { get; set; } // commented out because no need to output in a GET request
public Account? childAccount { get; set; }
}
public class GroupMemberGroup
{
public Guid parentGroupid { get; set; }
public Guid childGroupId { get; set; }
//public Group? parentGroup { get; set; }; // commented out because no need to output in a GET request
public Group? childGroup { get; set; };
}
What you need to spot here is the difference in classes Group between the DB and DTO models.
In the DB model, Group has 3 lists: childAccounts, childGroups and parentGroups.
In the DTO model, Group has 1 node children of type GroupChildren which is a class that contains 2 of those 3 lists.
Hence an additional difficulty when it comes to design the mapping. That difference is intentional because it matches the following desired output for an endpoint such as: GET .../api/rest/group({some group guid}) is something like:
{
"id": "some group guid",
"name": "some group name",
"children": {
"childAccounts":{
"account":{ "name": "some account name 1"}
"account":{ "name": "some account name 2"}
...
}
"childFroups":{
"group":{ "name": "some group name 1"}
"group":{ "name": "some group name 2"}
...
}
},
}
obtained from following typical controller code:
[HttpGet("Groups({key})")]
[ApiConventionMethod(typeof(ApiConventions),
nameof(ApiConventions.GetWithKey))]
public async Task<ActionResult<Group>> Get(Guid key, ODataQueryOptions<Group> options)
{
var g = await (await context.Group.Include(g => g.childAccounts)
.Include(g => g.childGroups)
.Where(g => g.id == key)
.GetQueryAsync(mapper, options) // note the mapper here is the mapping defined below
).FirstOrDefaultAsync();
if (g is null)
{
return ResourceNotFound();
}
return Ok(g);
}
So here's the missing part to all this. Unless there are major errors in all of the above, I have a very strong intuition that it is the mapping that is failing to get me the requested output above.
public class GroupMappingProfile : AutoMapper.Profile
{
public GroupMappingProfile()
{
// the rather straightforward.
CreateMap<DBAccess.Models.Tables.GroupMemberAccount, GroupMemberAccount>();
CreateMap<DBAccess.Models.Tables.GroupMemberGroup, GroupMemberGroup>();
//Attempt 1: the not so straightforward. An explicit exhaustive mapping of everything, down to every single primitive type
CreateMap<DBAccess.Models.Tables.Group, Group>()
.ForMember(g => g.children, opts => opts.MapFrom(src => new GroupMembers
{
childAccounts = src.childAccounts!.Select(x => new GroupMemberAccount { parentGroupId = x.parentGroupId,
childAccountId = x.childAccountId,
childAccount = new Account { id = x.childAccount!.id,
name = x.childAccount!.name
}
}
).ToList(),
//childGroups = src.childGroups!.Select(x => new GroupMemberGroup(x)).ToList(),
childGroups = src.childGroups!.Select(x => new GroupMemberGroup { parentGroupId = x.parentGroupId,
childGroupId = x.childGroupId,
childGroup = new Group { id = x.childGroup!.id,
name = x.childGroup!.name
}
}
).ToList(),
}));
//Attempt 2: mapper injection
IMapper mapper = null!;
CreateMap<DBAccess.Models.Tables.Group, Group>()
.BeforeMap((_, _, context) => mapper = (IMapper)context.Items["mapper"]) //ADDING THIS LINE CAUSES ALL QUERIES TO LOOK FOR A NON EXISTENT Group.Groupid column
.ForMember(g => g.children, opts => opts.MapFrom(src => new GroupMembers
{
childAccounts = mapper.Map<List<DBAccess.Models.Tables.GroupMemberAccount>, List<GroupMemberAccount>>(src.childAccounts!),
childGroups = mapper.Map<List<DBAccess.Models.Tables.GroupMemberGroup>, List<GroupMemberGroup>>(src.childGroups!)
}))
}
}
Attempt1 will yield:
{
"id": "some guid",
"name": "some name"
"children": {}
}
even though the generated SQL does fetch all the required data to fill "children"
Attempt2 (mapper injection) is a technique I was suggested and have no clue how it is supposed to work. From what I gather, the mapping functions creates a few maps for some basic types while it uses its "future" self to create the remaining mappings, whenever it will be invoked in the future. Looks somehow like a one-time recursion.
However, it crashes as the generated SQL will look for a non-existent view column group.Groupid
SELECT [t].[id], [t].[name],
[g0].[parentGroupId], [g0].[childAccountId],
[g1].[parentGroupId], [g1].[childGroupId], [g1].[Groupid] -- where does [g1].[Groupid] come from??
FROM (
SELECT TOP(1) [g].[id], [g].[name]
FROM [HID_Rest].[group] AS [g]
WHERE [g].[id] = #__key_0
) AS [t]
LEFT JOIN [HID_Rest].[groupMemberAccount] AS [g0] ON [t].[id] = [g0].[parentGroupId]
LEFT JOIN [HID_Rest].[groupMemberGroup] AS [g1] ON [t].[id] = [g1].[parentGroupId]
ORDER BY ...
So regardless of the mapping profile I experimented with, what is the right mapping profile I need (and what ever else) to get the expected JSON output above? Or is this desired JSON structure possible at all?
After further work, I have figured that there was nothing wrong with my models and mapping. There's still something wrong though as the output to my GET requests is still incomplete. Here's the current new issue I need to deal with to solve this problem:
Issue with REST controller function Microsoft.AspNetCore.Mvc.ControllerBase.OK()?
How to join multiple tables using group by. I have consulted some posts on stackoverflow, but it doesn't seem to work for me.
I have Model
public class Customer
{
public int ID { get; set; }
public string RadomID { get; set; }
public bool Active { get; set; }
}
public class RatingStore
{
public string RadomID { get; set; }
public string Content { get; set; }
}
public class Product
{
public int IDProduct { get; set; }
public string RadomID { get; set; }
}
This is how I count in CustomersController
public CustomersController(ApplicationDBContext context)
{
_context = context;
}
var qr = (from Customer c in _context.Customers
join o in _context.RatingStore on c.RadomID equals o.RadomID
join p in _context.Products on c.RadomID equals p.RadomID
where c.Active == true
group c by c.RadomID into g
select new
{
StoreId = g.Key,
CountRatingStore = g.Count(),
CountProdStore = g.Count(),
}).ToList();
However when I debug the results
CountRatingStore = g.Count() --> result: 2
CountProdStore = g.Count() --> result: 2
It has to be like this:
CountRatingStore = g.Count() --> result: 1
CountProdStore = g.Count() --> result: 2
Because of the data in my database:
Table Product:
Table RatingStore:
Table Customer:
Maybe my command is incorrect. How to get the correct result as I described above. Thank you
Update
public class ApplicationDBContext : DbContext
{
public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options)
{
}
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<RatingStore> RatingStore { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
}
I am trying to count the number of columns of Table Product and RatingStore with the same RandomID condition.
---> The problem I have not been able to solve. I ask for help from everyone.
I'm new to Azure Service Fabric, and watched Ivan Gavryliuk's "Understanding the Programming Models of Azure Service Fabric" course on Pluralsight. I've been following along and the basic data model in the reliable service and API work as explained in the course.
However, if I increase the complexity of the data model used I hit an error.
Product.cs from the ECommerce.ProductCatelog.Model
namespace ECommerce.ProductCatalog.Model
{
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public int Availability { get; set; }
public Supplier Suppliers { get; set; }
}
public class Supplier
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
ApiProduct.cs from ECommerce.API.Model
public class ApiProduct
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("price")]
public double Price { get; set; }
[JsonProperty("isAvailable")]
public bool IsAvailable { get; set; }
[JsonProperty("suppliers")]
public ApiSupplier suppliers { get; set; }
}
public class ApiSupplier
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
ProductController.cs from Ecommerce.API.Controlers
[HttpGet]
public async Task<IEnumerable<ApiProduct>> GetAsync()
{
IEnumerable<Product> allProducts = await _service.GetAllProductsAsync();
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = p.Suppliers
});
}
The last line in the above block triggers an intellisense error:
"Cannot implicitly convert type 'ECommerce.ProductCatelog.Model.Supplier' to 'ECommerce.API.Model.Supplier'"
Any suggestions on how to work around this welcome :)
Cheers,
Adam
Your problem is not specific to Service Fabric but C# in general. You are trying to set a variable with a value of a different type.
In this line:
IEnumerable<Product> allProducts = await _service.GetAllProductsAsync();
You get a collection of items of type ECommerce.ProductCatalog.Model.Product. In this class, you added the property Suppliers (which should be Supplier since it's not a collection) of type ECommerce.ProductCatalog.Model.Supplier.
Now, with the following line:
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = p.Suppliers
});
you are converting this collection to a collection of a new type ECommerce.API.Model.Product, to which you added a new property Suppliers of type ECommerce.API.Model.Supplier, but you set this property to the original value, without converting it. So, convert the original Suppliers property to the correct type:
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = new ApiSupplier
{
Id = p.Suppliers.Id,
Name = p.Suppliers.Name
}
});
Update: make Suppliers a collection
Make your Suppliers property a collection both in your data model and in your Api model:
public Collection<ApiSupplier> suppliers { get; set; }
Then convert the collection accordingly:
return allProducts.Select(p => new ApiProduct
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Price = p.Price,
IsAvailable = p.Availability > 0,
suppliers = p.Suppliers.Select(s => new ApiSupplier
{
Id = s.Id,
Name = s.Name
}
});
I'm tying to write an EntityFramework query to bring hospital name by hospital ID from Hospitals Context to Departments context.I tried couple of things like join tables etc. but I couldn't complete to write that correct query.Here my models and context below
Models
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
}
Context
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Hospital> Hospitals { get; set; }
public DbSet<Department> Departments { get; set; }
}
Above you can see that model Department has HospitalId to connect Hospital table.After join I want to get that Hospital Name where department belongs to.Result should be department ID,department Name and its Hospital Name .
My Final Try
public async Task<IEnumerable<Department>> GetDepartment(string input)
{
var departmentWithHospital = _context.Departments
.Where(d => d.Hospital.Id == d.HospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
});
return await departmentWithHospital;
// Compiler Error:doesnt contain a definition for GetAwaiter and no
//accesible extension for GetAwaiter....
}
Three points to note:
1.The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. like below:
var hospital =await _context.Hospitals.ToListAsync();
return hospital;
2.The relationships between Hospital and Department is one-to-many , you could refer to Relationships to design your model as follows:
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
3.You want to return a new object list which contains department ID,department Name and its Hospital Name, but your return type of the method is IEnumerable<Department> .So you could directly return a Department collection or define a ViewModel with the properties you want
Return type :IEnumerable<Department>
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId).ToListAsync();
return departmentWithHospital;
DepartmentWithHospital ViewModel
public class DepartmentWithHospital
{
public int departmentId { get; set; }
public string departmentName { get; set; }
public string hospitalName { get; set; }
}
public async Task<IEnumerable<DepartmentWithHospital>> GetDepartment(int hospitalId)
{
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId)
.Select(d => new DepartmentWithHospital
{
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
}).ToListAsync();
return departmentWithHospital;
}
You need a Hospital in your Departments class, and a collection of Departments in your Hospital class.
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public virtual ICollection<Department> Departments { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
For the query, try this (Been awhile since I messed with EF, and this is for EF6). I can't remember if you need the include or not, but this should get you an anonymous object with the properties you requested.
This code is not tested.
var departmentWithHospital = context.Departments
.Where(d => d.Hospital.Id == hospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.DepartmentName,
hospitalName = d.Hospital.HospitalName
})
.ToList();
If I understood your question correctly, you are looking for this:
var departmentId = "123";
var result = from department in _context.Departments
join hospital in _context.Hospitals
on hospital.Id equals department.HospitalId
where department.Id == departmentId
select new
{
DepartmentID = departmentId,
DepartmentName = department.Name,
HospitalName = hospital.Name
};
I've got a simple one-to-many db relationship defined by EF 6.1. The database that is generated appears correct and has the appropriate explicit relationship. However, when I try a query involving the child table, I get a NotSupportedException (see title of post). The failing code is in GetContractList (see below).
I did some digging and found some people having this problem, but those issues seemed related to attempting to include non-entity items in queries; I don't think that's what's happening here.
Anyone see what I'm doing wrong?
[Table("Contract")]
public class Contract : IContract
{
[Key,
DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ContractId { get; set; }
[StringLength(1023),
Required]
public string Name { get; set; }
public DateTime DateBegin { get; set; }
public DateTime DateEnd { get; set; }
public string PhoneNumber { get; set; }
public virtual ICollection<IMarket> Markets { get; set; }
public Contract()
{
Markets = new List<IMarket>();
}
}
[Table("Market")]
public class Market : IMarket
{
[Key, Column(Order = 0)]
public int ContractId { get; set; }
[Key, Column(Order = 1)]
public int MarketId { get; set; }
}
public IEnumerable<IIdName> GetContractList(IAffiliateContractSearchCriteria criteria)
{
var now = DateTime.UtcNow;
// This is the line throwing the exception.
return _repository.AsQueryable().Where(c => (criteria.IncludeOnlyActive
? c.DateBegin < now
&& (c.DateEnd > now || c.DateEnd <= SqlDateTime.MinValue.Value)
: true)
&& (c.Markets.Any()
? c.Markets.Select(m => m.MarketId).Any(x => criteria.MarketIds.Contains(x))
: true)).OrderBy(a => a.Name).Select(a => new IdName() { Id = a.AffiliateContractId, Name = a.Name });
}
public class ContractSearchCriteria : IContractSearchCriteria
{
public bool IncludeOnlyActive { get; set; }
public List<int> MarketIds { get; set; }
public ContractSearchCriteria()
{
IncludeOnlyActive = false;
MarketIds = new List<int>();
}
public ContractSearchCriteria(bool includeOnlyActive, int[] marketIds)
: this()
{
IncludeOnlyActive = includeOnlyActive;
MarketIds.AddRange(marketIds);
}
}
Ok, so the problem is that you can't use interfaces when defining EF Entity objects and relationships.
My bad.