I have a class with corresponding mapping as below:
public class Customer
{
public virtual int CustomerId { get; private set; }
//...
public virtual List<int> Orders { get; set; }
}
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.PatientId)
.GeneratedBy.Native();
HasMany(x => x.Orders)
.Element("OrderId", t => t.Type<int>())
.Table("CustomerOrder")
.KeyColumn("CustomerId")
.ForeignKeyConstraintName("FK_Customer_Order")
.Cascade.All();
}
}
Assume class Order is in another database, so I can't map it in this assembly. (I'm not sure this is the best way of doing this, please feel free to comment on the mapping too.)
So I would like to be able to find Customers with more than N orders, SQL query would look like this:
select * from Customer c where
(select count(*) from orders where CutsomerId = c.CustomerId) > N
What would be Criteria API equivalent?
As another option could you not just add an OrderCount property to your Customer class so you don't need the join to the other DB.
Anything you do which joins cross DB or joins to unmapped classes feels a bit wrong.
Related
The code below, is fine. It works. Things are inserted, stuff appear on the right side on the left side etc. However, there's an issue. Using a program such as LinQPad (https://www.linqpad.net), I am able to query the data and see it listed.
But, trying to do the same in C#, says that the "auto" generated linking table does not exists in the context.
Relationship: Many-to-Many.
I am using Entity Framework 6.
EDIT: What's the purpose of autogenerated linking table if you cannot use it from your C# Windows Application? Using Linq?
My Code First tables:
public class Student
{
[Key, Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int StudentID { get; set; }
public string StudentName { get; set; }
public DateTime? DateOfBirth { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
[Key, Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CourseId { get; set; }
[Index("CourseName", 2, IsUnique = true)]
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
DBContext.cs
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("FK_StudentID");
cs.MapRightKey("FK_CourseID");
cs.ToTable("StudentCourse");
});
base.OnModelCreating(modelBuilder);
}
Querying from within the LinqPad, works perfectly fine:
void Main()
{
var data = (from s in Students
join sc in StudentCourse on s.StudentID equals sc.FK_StudentID
join c in Courses on c.CourseId equals sc.FK_CourseID
select new { s,c });
Console.WriteLine(data);
}
There is nothing stopping you from declaring it as an entity, but where the linking table is kept to just the required keys, it is an optimization not to have to declare it when using the navigation properties.
The key issue I think you are facing here is you're writing Linq like SQL and completely missing the navigation properties.
For instance, if I want all Courses for a particular student: Without a linking table I can do:
var studentCourses = context.Students
.Where(x => x.StudentId == studentId)
.SelectMany(x => x.Courses)
.ToList();
To get a list of each student/course combination as you outlined:
var studentCourses = context.Students
.SelectMany(s => s.Courses.Select(c => new {s, c}))
.ToList();
This gives me the list of courses for that student. I don't have to manually join up Student to Course through StudentCourse like writing an SQL statement.
Now you can certainly declare a StudentCourse entity, but this changes the relationships slightly. Rather than a Many-to-Many, you have a Many-to-One-to-Many:
public class Student
{
// ...
public virtual ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}
public class Course
{
// ...
public virtual ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}
public class StudentCourse
{
[Key, Column(Order=0), ForeignKey(Student)]
public int StudentId { get; set; }
[Key, Column(Order=1), ForeignKey(Course)]
public int CourseId { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course { get; set; }
}
It can be tempting to name the StudentCourses property "Courses" and "Students" in their respective counterparts, but IMO this gets misleading when diving trough the navigation as I'll point out in the example below.
For those using EFCore, I believe this is still the only supported option for many-to-many. This is also a required option if you want to make any alterations to the joining table such as using a dedicated PK column, or other columns such as CreatedAt etc. to track edits etc. for the relationships.
Then to do the first example query in Linq you'd need to change it slightly to:
var studentCourses = context.Students
.Where(x => x.StudentId == studentId)
.SelectMany(x => x.StudentCourses.Course)
.ToList();
To get a list of each student/course combination with this linking entity:
var studentCourses = context.StudentCourses.ToList();
// or, if there is other data in the linking entity and you just want student and course reference:
var studentCourses = context.StudentCourses
.Select(sc => new {sc.Student, sc.Course})
.ToList();
Alternatively you could write the Linq QL statement like you did in Linqpad now that the entity is declared and can be added as a DbSet in the context. (DbSet is not required if you are using navigation properties)
Edit: added example below: (I can't vouch for it's correctness as I pretty much exclusively use the Fluent Linq syntax, not Linq QL)
var data = (from s in context.Students
join sc in context.StudentCourse on s.StudentID equals sc.StudentID
join c in context.Courses on c.CourseId equals sc.CourseID
select new { s,c });
This is why I recommend naming the property on Student as StudentCourses rather than Courses. The alternative would read:
.SelectMany(x => x.Courses.Course)
Where Courses implies I should be getting courses (as the optimization to avoid the linking entity can give you) but you're getting StudentCourses so you're left with it looking rather weird as .Courses.Course to get the actual Course.
I want to combine these two linq queries to single query
is it possible?
var chestProducts = (from w in WareHouse
join c in Chests on w.Id equals c.WareHouseId
join p in Products on c.Id equals p.ContainerId
where (p.IsContainerChest == true && w.Id == 1)
select p
).ToList();
var boxProducts = (from w in WareHouse
join b in Boxes on w.Id equals b.WareHouseId
join p in Products on b.Id equals p.ContainerId
where (p.IsContainerChest != true && w.Id == 1)
select p
).ToList();
var allProducts = chestProducts.AddRange(boxProducts);
Should I use two queries?
And is this relation is healty?
Edit: Boxes and Chests tables are simplifed they have different fields
OK, from your comments I can see that you are using EF6 with code first. In that case I would make use of Table per Hierarchy and put both Box and Chest into one table (they will be separate classes still). One (big) caveat: I have been working exclusively with EF Core for a while now, and I haven't tested this. But I have used this pattern repeatedly and it works nicely.
Your entities should look something like this:
public class WareHouse
{
[Key]
public int Id { get;set; }
public string Name {get;set;}
public ICollection<Container> Containers {get;set;}
}
public abstract class Container
{
[Key]
public int Id {set;set;}
public int WareHouseId {get;set;}
[ForeignKey(nameof(WareHouseId))]
public WareHouse WareHouse {get;set;}
public string Name {get;set;}
public ICollection<Product> Products {get;set;}
}
public class Box : Container
{
// box specific stuff here
}
public class Chest : Container
{
// chest specific stuff here
}
public class Product
{
[Key]
public int Id {set;set;}
public int ContainerId {get;set;}
[ForeignKey(nameof(ContainerId))]
public Container Container {get;set;}
}
And your context something like this:
public class MyContext : DbContext
{
public virtual DbSet<WareHouse> WareHouses {get;set;}
public virtual DbSet<Container> Containers {get;set;}
public virtual DbSet<Product> Products {get;set;}
protected override void OnModelCreating(ModelBuilder builder)
{
// puts the class name in a column, makes it human readable
builder.Entity<Container>().Hasdiscriminator<string>("Type");
// i don't think you need to do this, but if it doesn't work try this
// builder.Entity<Box>().HasBaseType(typeof(Container));
// builder.Entity<Chest>().HasBaseType(typeof(Container));
}
}
Then you can get all the products from the warehouse with id=1 like this:
int warehouseId = 1;
Product[] allProducts = myContext.WareHouses
.Where(wh => wh.Id == warehouseId)
.SelectMany(wh => wh.Container)
//.OfType<Box>() if you only want products in boxes
.SelectMany(wh => wh.Products)
.ToArray();
I know you said in your comment that you tend to use linq's lambda syntax, but I feel I should point out that you are doing a lot of unnecessary joins in your query syntax example. linq to entities will take care of all that for you if you have set things up correctly.
Try this:
var allProducts = chestProducts.Concat(boxProducts);
Or you can also use Union
var allProducts = Enumerable.Union(chestProducts, boxProducts);
This question already has answers here:
EF Core returns null relations until direct access
(2 answers)
Closed 5 years ago.
I'm using .net core 2 mvc, I tried to build many-to-many relationship between Users and Steps.
the relationship is doen but when I query for the record I get user = null.
Hier is my code:
(applicationUser model):
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
(Steps model):
public class Steps
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
StepsUsers model:
public class StepsUsers : IAuditable
{
public int StepId { get; set; }
public Steps Step { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
In DbContext I did this :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<StepsUsers>()
.HasKey(s => new { s.StepId, s.UserId });
builder.Entity<StepsUsers>()
.HasOne(su => su.Step)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.StepId);
builder.Entity<StepsUsers>()
.HasOne(su => su.User)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.UserId);
}
public DbSet<MyApp.Models.StepsUsers> StepsUsers { get; set; }
Now, when I query for an instance of StepsUsers with specific StepId I get all de fields correct except the User field is null
var stepUsers = await _context.StepsUsers.Where(s => s.StepId == id).ToListAsync();
I did the same code for another two tables and it works fine, I don't know why it is like this, any suggestion 1?
The cause of your problems is that your forgot to declare your To-many relations as virtual. Another improvement would be to declare them as virtual ICollection instead of List. After all, what would ApplicationUser.StepUser[4] mean?
If you configure a many-to-many relationship according to the entity framework conventions for many-to-many, you don't need to mention the junction table (StepsUsers). Entity framework will recognize the many-to-many and will create the junction table for you. If you stick to the code first conventions you won't even need the fluent API to configure the many-to-many.
In your design every ApplicationUser has zero or more Steps and every Step is done by zero or more ApplicationUsers.
class ApplicationUser
{
public int Id {get; set;}
// every ApplicationUser has zero or more Steps:
public virtual ICollection<Step> Steps {get; set;}
public string Name {get; set;}
...
}
class Step
{
public int Id {get; set;}
// every Step is performed by zero or more ApplicationUsers:
public virtual ICollection<ApplicationUser> ApplicationUsers {get; set;}
public string Name {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<ApplicationUser ApplictionUsers {get; set;}
public DbSet<Step> Steps {get; set;}
}
This is all entity framework needs to know to recognize that you configured a many-to-many relationship. Entity framework will create the junction table for you and the foreign keys to the junction table. You don't need to declare the junction table.
But how am I suppose to do a join if I don't have the junction table?
The answer is: Don't do the join. Use the collections instead.
If you want all ApplicationUsers that ... with all their Steps that ... you would normally do an inner join with the junction table, and do some group by to get the Application users. Ever tried method syntax to join three tables? They look hideous, difficult to understand, error prone and difficult to maintain.
Using the collections in entity framework your query would be much simpler:
var result = myDbContext.ApplicationUsers
.Where(applicationUser => applicationUser.Name == ...)
.Select(applicationUser => new
{
// select only the properties you plan to use:
Name = applicationUser.Name,
Steps = applicationUser.Steps
.Where(step => step.Name == ...)
.Select(step => new
{
// again fetch only Step properties you plan to use
Name = step.Name,
...
})
.ToList(),
});
Entity framework will recognize that joins with the junction table is needed and perform them for you.
If you want Steps that ... with their ApplicationUsers who ... you'll do something similar:
var result = myDbContext.Steps
.Where(step => ...)
.Select(step => new
{
Name = step.Name,
... // other properties
ApplicationUsers = step.ApplicationUsers
.Where(applicationUser => ...)
.Select(applicationUser => new
{
...
})
.ToList(),
});
In my experience, whenever I think of performing a query with a of DbSets using entity framework, whether it is in a many-to-many, a one-to-many or a one-to-one relation, the query can almost always be created using the collections instead of a join. They look simpler, they are better to understand and thus better to maintain.
What is the best way to do a many to many join ant the entity framework.
I have a tag class
favorite
I have a Tag class
[Table("tblTags")]
public class Tag
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
[Column("fld_int_id")]
public virtual int Id { get; set; }
[Column("fld_str_name")]
public virtual string Name { get; set; }
public virtual ICollection<DocumentUploadEntity> Documents { get; set; }
}
I have a documents class
[Table("tblUploadDocument")]
public class DocumentUploadEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("fld_int_ID")]
public int ID { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
I map them like this:
modelBuilder.Entity<DocumentUploadEntity>()
.HasMany(x => x.Tags)
.WithMany(x => x.Documents)
.Map(x =>
{
x.ToTable("tblUploadDocumentsTags");
x.MapLeftKey("fld_int_document_id");
x.MapRightKey("fld_int_tag_id");
});
I want to search for any documents contain a tag name in a like expression. If I was to do this in sql I would do this:
SELECT * FROM tblUploadDocument d
INNER JOIN tblUploadDocumentsTags ud
ON fld_int_document_id = d.fld_int_id
INNER JOIN tbltags t
ON ud.fld_int_tag_id = t.fld_int_id
WHERE t.fld_str_name like 'foo%';
Please excuse the table names and field names, this was not my doing.
How can I do this with linq and entity framework.
var documents = DbContext.Tags.Where(x => x.Name.StartsWith("foo"))
.SelectMany(y => y.Documents).ToList()
The beauty of the EF is that you can start from either side and use the navigation property to get to the other side of the many-to-many relationship. Behind the scenes EF will use the link table and necessary joins.
For instance, you can start from DocumentUploadEntity:
var documents =
from document in db.DocumentUploadEntities
where document.Tags.Any(tag => tag.Name.Contains("foo"))
select document;
or you can start from Tag:
var documents =
from tags in db.Tags
where tag.Name.Contains("foo")
from document in tag.Documents
select document;
UPDATE:: As #James Dev correctly stated in the comments, the equivalent of SQL LIKE 'foo% is Name.StartsWith("foo").
I am working with Entity Framework code first.
I have the following tables :
Companies : PK ID int, Name, ...
Customers : PK ID int, Name, ...
CustomersCompanies : CustomerID, CompanyID
I can create customers and companies without problems.
But I don't know how to get all the companies a customer has.
I tried that :
_customer = ...
var companies = from c in _db.Companies
where c.Customers.Contains(_customer)
select c;
But companies does not contains anything...
Try to compare by ID's of customers, like:
_customer = ...
var companies = from c in _db.Companies
where c.Customers.Where(x => x.CustomerID == c.CompanyID)
select c;
Or shorter:
var comapnies = _db.Companies.Select(x => x.CustomerID == c.CompanyID);
With properly created Entities you should be able to just call:
var companies = _customer.Companies;
you have to have ICollection<Company> within your Customer class, and ICollection<Customer> within Company class.
Check out this tutorial: Creating a Many To Many Mapping Using Code First.
If you are using code first you can just add a virtual collection of Companies to your Customer class, and a virtual collection of Customers to your Company class :
public class Customer
{
public int Id { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
public class Company
{
public int Id { get; set; }
public virtual ICollection<Customer> Customers { get; set; }
}
then to fetch customers and include their companies you can do :
var customers = _db.Customers.Include(x => x.Companies);
I'm not sure what your _db class looks like so I don't know if you just have your entity collections as properties on it. Typically I use the entity framework DbContext which has a GetDbSet method. So I would do something like :
var customers = _dbContext.GetDbSet<Customer>().Include(x => x.Companies);
Hope that helps!