I'm trying to apply a filter (ApplyFilter) on a column that is Join (and projected) from another table. I have the following entity:
public class User
{
public virtual int Id { get; private set; }
public virtual string EMail { get; set; }
...
public virtual bool IsActive { get; set; }
public virtual int CompanyId { get; set; }
}
With a UserMap:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
Id(x => x.Id, "UserId");
Map(x => x.EMail);
...
Join("CompanyUser", r =>
{
r.KeyColumn("UserId");
r.Map(x => x.IsActive);
r.Map(x => x.CompanyId);
r.Fetch.Join();
});
ApplyFilter<CompanyFilter>("this_1_.CompanyId = :companyId");
}
This actually works right now, but as you can see I'm having to include the alias name "this_1_" for the CompanyUser table in the filter... this doesn't sound correct, but if I leave the filter as is defined in the FilterDefinition class I get a Sql with a:
where this.CompanyId = ?p0
which is not mapped 'cos the CompanyId column comes from a different projection (CompanyUser as this_1_)
Is there a way to correct this and let nhibernate figure out the correct alias for the filter?
Thanks in advance for any help.
I think you must apply the filter on the join:
Join("CompanyUser", r =>
{
r.KeyColumn("UserId");
r.Map(x => x.IsActive);
r.Map(x => x.CompanyId);
r.Fetch.Join();
}).ApplyFilter<CompanyFilter>("CompanyId = :companyId");
Related
I have 2 tables in my ASP.NET MVC project: store and product.
The relationship between these tables is many-to-many, so I also have a table StoreProduct.
I want to do CRUD operations on table product, but I need column StoreID from table StoreProduct.
How can I get the storeid from StoreProduct to do CRUD operation on product table?
here is how you'd do it if you want a many-to-many relationship with enums i.e. you want many user roles and work with enums moreover, you can't store it as a flag because you need to know about the roles/privileges without source code(on db side).
TLDR ;) You'd have to create a join table which contains about about who has what privilege(or roles if you want).
There is a Users table which has a list of privileges, a privilege table which has privilege definition i.e. Id, name. And a Join table which will have User and Privilege as it's key. If an entry against this user/privilege combination is present that means this user has this privilege/role.
The code:
//for enum
public enum UserPrivilegeId : int
{
AddProject = 0,
ModifyProject = 1,
DeleteProject = 2,
AddUser = 3,
ModifyUser = 4,
DeleteUser = 5
}
//User class
public record User
{
public User()
{
Privileges = new HashSet<Privilege>();
}
public int Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; }
public virtual ICollection<Privilege> Privileges { get; set; }
public virtual List<UserPrivilege> UserPrivileges { get; set; }
}
//Privilege Class
public record Privilege //note record is IMPORTANT here, because this forces it to compare by value, if you want to *use a class*, then make sure to override GetHashCode and Equals
{
public Privilege()
{
Users = new HashSet<User>();
}
public Privilege(UserPrivilegeId privilegeId, string privilegeName)
{
PrivilegeId = privilegeId;
PrivilegeName = privilegeName;
Users = new HashSet<User>();
}
[Key]
public UserPrivilegeId PrivilegeId { get; set; }
public string PrivilegeName { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual List<UserPrivilege> UserPrivileges { get; set; }
}
//and finally the UserPrivilege join class
public record UserPrivilege
{
public UserPrivilegeId PrivilageId { get; set; }
public Privilege Privilage { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
//The set-up in dbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Privilege>()
.HasKey(p => p.PrivilegeId);
modelBuilder.Entity<Privilege>()
.Property(p => p.PrivilegeId)
.HasConversion<int>();
modelBuilder.Entity<User>()
.HasMany(user => user.Privileges)
.WithMany(privilege => privilege.Users)
.UsingEntity<UserPrivilege>(
j => j
.HasOne(up => up.Privilage)
.WithMany(u => u.UserPrivileges)
.HasForeignKey(up => up.PrivilageId),
j => j
.HasOne(up => up.User)
.WithMany(p => p.UserPrivileges)
.HasForeignKey(up => up.UserId),
j =>
{
j.Property(u => u.PrivilageId).HasConversion<int>();
j.HasKey(u => new { u.PrivilageId, u.UserId });
});
//this adds definitions of privileges to the table
modelBuilder.Entity<Privilege>()
.HasData(
Enum.GetValues(typeof(UserPrivilegeId))
.Cast<UserPrivilegeId>()
.Select(p => new Privilege(p, p.ToString())));
base.OnModelCreating(modelBuilder);
}
Use it by creating a wrapper around it with a boolean on IsActive like this:
public class UserPrivelegesDTO
{
public UserPrivelegesDTO(UserPrivilegeId privilege, bool isActive)
{
this.PrivilegeId = privilege;
this.PrivilegeName = privilege.ToString();
this.IsActive = isActive;
}
public UserPrivilegeId PrivilegeId { get; set; }
public string PrivilegeName { get; set; }
public bool IsActive { get; set; }
}
If you want to convert from List<Privileges> to List<UserPrivilegeDTO>, you can
return await _context.Privileges.OrderBy(x => x.PrivilegeId).ToListAsync(cancellationToken);
To Convert back to List<Privileges>, simply
var privileges = _userPrivilegesViewModel.Privileges.Where(x => x.IsActive).Select(x => new Privilege(x.PrivilegeId, x.PrivilegeName));
If you want to check if the user has privilege
var user = _context.Users.Include(x => x.Privileges).FirstAsync(x => x.Id == 1);
if (request.Editor.Privileges.Any(p => p.PrivilegeId == UserPrivilegeId.ModifyUser))
return true;
When you want to update privileges
var PrivilegeChangeUser = await
_context.Users
.Include(user => user.Privileges)
.Include(user => user.UserPrivileges)
.FirstOrDefaultAsync(user => user.Id == request.UserId);
//**NOTE**you *need* to include the join table i.e. UserPrivileges in order replace the privileges, if you do not include it EF will try to add the privileges which already exist :(
//To update the privileges from an IEnumerable<UserPrivilegeIdEnum>
//first get the privileges objects and add that to users
var AllPrivileges =
await _context.Privileges
.Include(x => x.UserPrivileges)
.Include(x => x.Users)
.Where(x =>
request.Privileges
.Contains(x.PrivilegeId)
).ToListAsync(cancellationToken);
PrivilegeChangeUser.Privileges = AllPrivileges;
I have used an enum as the primary key, but you can of course just use a standard int too. For more information read this about how to configure a many-to-many relationship in efcore
I have a Quote object, with a collection of QuoteAnswer objects. I also want a shortcut to the latest QuoteAnswer. So I modeled (irrelevant code ommitted for brevity):
public class Quote
{
public int Id { get; set; }
public ICollection<QuoteAnswer> Answers { get; set; }
public QuoteAnswer LatestAnswer { get; set; }
public int LatestAnswerId { get; set; }
}
public class QuoteAnswer
{
public int Id { get; set; }
public Quote Quote { get; set; }
public int QuoteId { get; set; }
/* Did not map this, not interested/not needed
* public Quote LastAnswerFor { get; set; }
* public int LastAnswerForId { get; set; }
*/
}
That's beacuse I want to be able to do this:
var quotes = context.Quotes
.Include(x => x.LatestAnswer)
.ToList();
Instead of this:
var quotes = context.Quotes
.Include(x => x.Answers)
.ToList();
foreach (var q in quotes)
{
var latestAnswer = q.Answers.OrderByDescending(x => x.Date).FirstOrDefault();
}
Which would obviously force me to load unecessary data.
The Problem
When I try to map this do database code (add a migration), I get a new property I don't know where it's comming from.
Generated migration code (parts ommitted for brevity):
CreateTable(
"dbo.QuoteAnswer",
c => new
{
Id = c.Int(nullable: false),
QuoteId = c.Int(nullable: false),
QuoteId1 = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Quote", t => t.QuoteId)
.ForeignKey("dbo.Quote", t => t.QuoteId1)
.Index(t => t.QuoteId)
.Index(t => t.QuoteId1);
AddColumn("dbo.Quote", "LatestAnswerId", c => c.Int());
CreateIndex("dbo.Quote", "LatestAnswerId");
AddForeignKey("dbo.Quote", "LatestAnswerId", "dbo.QuoteAnswer", "Id");
What's that QuoteId1 thing? I get the QuoteId, but I don't recognize QuoteId1.
How can I achive this mapping? Is this even supported in EF6?
First, it's possible. The explicit FK property should be removed:
public class Quote
{
public int Id { get; set; }
public string Data { get; set; }
public ICollection<QuoteAnswer> Answers { get; set; }
public QuoteAnswer LatestAnswer { get; set; }
}
and the new relationship should be mapped with fluent API:
modelBuilder.Entity<Quote>()
.HasOptional(e => e.LatestAnswer)
.WithOptionalDependent()
.Map(m => m.MapKey("LatestAnswerId"));
But I won't recommend you doing it because it would introduce a lot of maintenance problems - aside of the obvious need to keep it up-to-date, it would create circular FK dependency, so basically all CUD operations would be problematic (if working at all).
I think you are trying to solve the "loading unnecessary data" problem is a wrong way. You can achieve the same goal by using simple projection:
var quotesWithLatestAnswer = context.Quotes
.Select(q => new { Quote = q, LatestAnswer = q.Answers.OrderByDescending(a => a.Date).FirstOrDefault() })
.ToList();
Note that the code inside Select will be translated to SQL and executed in the database, returning only the data needed.
To return the latest answer as part of your entity, you can make mark it as unmapped:
public class Quote
{
public int Id { get; set; }
public string Data { get; set; }
public ICollection<QuoteAnswer> Answers { get; set; }
[NotMapped]
public QuoteAnswer LatestAnswer { get; set; }
}
and use a combination of LiNQ to Entities (SQL) and LINQ to Objects query:
var quotes = context.Quotes
.Select(q => new { Quote = q, LatestAnswer = q.Answers.OrderByDescending(a => a.Date).FirstOrDefault() })
.AsEnumerable() // end of db query
.Select(qi => { qi.Quote.LatestAnswer = qi.LatestAnswer; return qi.Quote; })
.ToList();
This way you'll have clean and easy to maintain relational database model as well as efficient retrieval of the data needed.
I have some tables to specify standard email messages and the addresses from which they should be sent:
Email.Message
-------------
MessageId
Name
FromAddressId
Email.Address
-------------
AddressId
These tables are modeled in code using these classes:
public class EmailAddress
{
public int AddressId { get; set; }
}
public class EmailMessage
{
public int MessageId { get; set; }
public string Name { get; set; }
public int FromAddressId { get; set; }
public virtual EmailAddress FromAddress { get; set; }
}
var message = Db.EmailMessages
.Where(m => m.Name == name)
.Include(m => m.FromAddress)
.SingleOrDefault();
I am using Fluent API to specify the relationship:
internal class EmailMessageMap : EntityTypeConfiguration<EmailMessage>
{
public EmailMessageMap()
{
ToTable("Message", "Email");
HasKey(p => p.MessageId);
// What do I put here?
}
}
How do I set up the join to use the FromAddressId of my EmailMessage class? I've tried these options:
HasRequired(p => p.FromAddress).WithRequiredPrincipal(); // uses LEFT OUTER JOIN
HasRequired(p => p.FromAddress).WithRequiredDependent(); // uses INNER JOIN
These cause the SQL that gets created to use either a LEFT OUTER JOIN or an INNER JOIN, but they always try to join Message.MessageId to Address.AddressId, which is incorrect. How do I make it use the correct column (FromAddressId)? I need to have something to replace the // What do I put here?, because otherwise I get the following exception:
Invalid column name 'FromAddress_AddressId'
EDIT: I can place a [ForeignKey("FromAddressId")] on top of the FromAddress property and it works:
[Extent1].[FromAddressId] = [Extent2].[AddressId]
Progress! Now, how do I do this in Fluent API?
Now, how do I do this in Fluent API?
HasRequired(p => p.FromAddress)
.WithMany()
.HasForeignKey(p => p.FromAddressId);
I have two tables. Parent table and link table that stores ids. Parent table have related data from itself. Here is Models:
public class People
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Relation> Relations { get; set; }
}
public class Relation
{
public int Id { get; set; }
public int ParentPeopleID { get; set; }
public int ChildPeopleID { get; set; }
public People People { get; set; }
}
Some test data
And mapped them like this
HasRequired(p => p.People).WithMany(p => p.Relations).HasForeignKey(p => p.ParentPeopleID);
When I'm calling
var Peoplelist = MyDbContext.People.Include(p=>p.Relations.Select(r=>r.People)).Where(p=>p.Id==1).ToList();
It returns itself not related People. In my case it should returns People with ids: 2,3,4 but returns three People with id 1
I can get what I need by MyDbContext.Relation but need by MyDbContext.People
What i'm doing wrong?
Is there another way to do that?
Ivan is correct. You should use join keyword to join the both tables as below. You will get the desired result (People with ids: 2,3,4) by using this query:
var Peoplelist = MyDbContext.Peoples.Join(MyDbContext.Relations, p => p.Id, r => r.ChildPeopleID,
(p, r) => new {p, r})
.Where(j => j.r.ParentPeopleID == 1).ToList();
Hope this will help.
Say, I have two entities, Movement (id, #fromLocationId, #toLocationId), fromLocationId and toLocationId being two foreign keys of the second entity Location (id, name). I would like to write a Breeze query that retrieves all movements with location names related to fromLocationId and toLocationId. This is what I got thus far:
var query = breeze.EntityQuery('movement').expand('location');
When I debug it and check the first record, for example, I find out that it has a location() and location1() properties. I can retrieve the location name of fromLocationId from data[0].location().name() but cannot do the same with that of toLocationId, as location1() is null. I even tried var query = breeze.EntityQuery('movement').expand('location, location1'); but it is still not working.
Any ideas on how to solve this? Thanks in advance.
EDIT
Here are the .NET classes:
[Table("Location")]
public partial class Location
{
public Location()
{
Movements = new HashSet<Movement>();
Movements1 = new HashSet<Movement>();
}
public int Id { get; set; }
[StringLength(250)]
public string Name { get; set; }
public virtual ICollection<Movement> Movements { get; set; }
public virtual ICollection<Movement> Movements1 { get; set; }
}
[Table("Movement")]
public partial class Movement
{
public int Id { get; set; }
public int FromLocationId { get; set; }
public int ToLocationId { get; set; }
public virtual Location Location { get; set; }
public virtual Location Location1 { get; set; }
}
In the DbContext class, the relationships look like this:
modelBuilder.Entity<Location>()
.HasMany(e => e.Movements)
.WithRequired(e => e.Location)
.HasForeignKey(e => e.FromLocationId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Location>()
.HasMany(e => e.Movements1)
.WithRequired(e => e.Location1)
.HasForeignKey(e => e.ToLocationId)
.WillCascadeOnDelete(false);
Thanks.
OK, I found out what I needed to do. Here is the solution:
var query = breeze.EntityQuery('movement').expand('location, location')
I guess, Breeze understands this as, the first location in the expand() will be related to fromLocationId and the second location will be related to toLocationId. In other words, add as many entity as foreign keys related to that common entity.
Hope it helps someone else.