Accessing a record from an association set - c#

I am developing a web application with ASP.net MVC and I have a database which I'm connecting with an ADO.NET Entity Framework.
In this database I have a table Group with GroupId as a primary key, another table UserInfo with UserId as its primary key and another table GroupUser which is not considered as an Entity but rather as an Association Set since it is used as a mean to have a many to many relationship between Group and User.
GroupUser contains GroupId and UserId as a composite key and both are foreign keys to the respective tables.
These are the Group and User classes generated (regarding this relationship)
// Group
public Group()
{
this.UserInfo1 = new HashSet<UserInfo>();
}
public virtual UserInfo UserInfo { get; set; }
public virtual ICollection<UserInfo> UserInfo1 { get; set; }
// UserInfo
public UserInfo()
{
this.Group = new HashSet<Group>();
this.Group1 = new HashSet<Group>();
}
public virtual ICollection<Group> Group { get; set; }
public virtual ICollection<Group> Group1 { get; set; }
To add a record to this GroupUser table I am doing this
int ownerId = Convert.ToInt32(WebSecurity.CurrentUserId);
group.UserInfo1.Add(conn.UserInfo.Find(ownerId));
However I am stuck on how to find a record in this table. How can I check if a particular user belongs to this group by having groupId and userId provided here?
Group group = conn.Group.Find(id);
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
Thanks for any help :)

With the starting point you have provided in order to test if the user is in that group you can use:
Group group = conn.Group.Find(id);
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
bool isUserInGroup = group.UserInfo1.Any(u => u.UserId == userId);
It will work because when you access group.UserInfo1 (with the Any extension method in this case) Entity Framework will run a second query (the first one was Find) to load all related UserInfo entities of the given group into the group.UserInfo1 collection. This query is based in lazy loading which is enabled by default if the navigation collection is declared as virtual (which it is in your example). After loading the collection the Any call is a check in memory (no database query here anymore) if the group.UserInfo1 collection contains at least one entity that fulfills the supplied condition, i.e. contains a user with that userId.
However, this is not the best solution because - as said - it will cause two queries (Find and lazy loading of the collection). Actually you can test if the user is in the group by a single database query alone and you don't even need to load any entities for that test, just directly return the bool result from the database:
int userId = Convert.ToInt32(WebSecurity.CurrentUserId);
bool isUserInGroup = conn.Group
.Any(g => g.GroupId == id && g.UserInfo1.Any(u => u.UserId == userId));
The result will be false if the group with the id does not exist or if it doesn't have a related user with userId.

Related

Why I can't add item to the database?

im working on my web app. Im using .NET and EntityFrameworkCore. I have created migration and database. Simply I just want to add item to the database but I encounter an error when sending a request to this particular endpoint. I attach some of my code below. I also attach link to my github repo https://github.com/szymi-dev/TestRepo
[HttpPost("add-product")]
public async Task<ActionResult<Product>> AddProduct(ProductDto productDto)
{
var product = new Product
{
Name = productDto.Name,
Price = productDto.Price,
Descripiton = productDto.Descripiton,
PictureUrl = productDto.PictureUrl
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
return product;
}
Here is productDTO class
public class ProductDto
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Descripiton { get; set; }
public string PictureUrl { get; set; }
}
I have also added some Entity Configurations
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.Property(p => p.Id).IsRequired();
builder.Property(p => p.Name).IsRequired().HasMaxLength(50);
builder.Property(p => p.PictureUrl).IsRequired();
builder.Property(p => p.Descripiton).HasMaxLength(180).IsRequired();
builder.Property(p => p.Price).HasColumnType("decimal(18, 2)");
builder.HasOne(p => p.ProductBrand).WithMany().HasForeignKey(k => k.ProductBrandId);
builder.HasOne(p => p.ProductType).WithMany().HasForeignKey(k => k.ProductTypeId);
builder.HasOne(p => p.User).WithMany(p => p.Products).HasForeignKey(k => k.UserId);
}
}
Finally im getting " SQLite Error 19: 'FOREIGN KEY constraint failed'" error in Postman and
Microsoft.EntityFrameworkCore.Database.Command[20102]
Failed executing DbCommand (6ms) [Parameters=[#p0='?' (Size = 14), #p1='?' (Size = 6), #p2='?' (Size = 5), #p3='?', #p4='?', #p5='?', #p6='?'], CommandType='Text', CommandTimeout='30']
INSERT INTO "Products" ("Descripiton", "Name", "PictureUrl", "Price", "ProductBrandId", "ProductTypeId", "UserId")
VALUES (#p0, #p1, #p2, #p3, #p4, #p5, #p6);
SELECT "Id"
FROM "Products"
WHERE changes() = 1 AND "rowid" = last_insert_rowid(); ~ in VScode
There are a couple of issues I see with your example and your schema. Firstly, as mentioned your Product has a ProductType and ProductBrand reference that you will need to set. Typically in your UI you would have something like a drop-down list populated with Types and Brands to select from, so your Product DTO would need a ProductTypeId and ProductBrandId to represent these selections.
Entities can have a FK exposed (Which your entities do appear to have based on the HasForeignKey property, so you can set these when creating your product. Otherwise it is generally better to set navigation property references as this validates that the provided IDs are actually a valid product type and brand:
var productType = _context.ProductTypes.Single(x => x.ProductTypeId == productDto.ProductTypeId);
var productBrand = _context.ProductBrands.Single(x => x.ProductBrandId == productDto.ProductBrandId);
var product = new Product
{
Name = productDto.Name,
Price = productDto.Price,
Descripiton = productDto.Descripiton,
PictureUrl = productDto.PictureUrl,
ProductType = productType,
ProductBrand = productBrand
};
I don't generally recommend having FK properties exposed when you have navigation properties as this forms 2 sources of truth for the relationship. This is generally only an issue when updating, not creating though.
Lastly you have a User reference, which I think may be a bit of a problem depending on what this relationship is actually intended for. A common case for this would be something like tracking a CreatedBy type relationship, though this would be a many-to-one scenario where the user would not bother maintaining a relationship back to the products (and other entities) that they created. Your User has a Products collection defined, so I would take a close look at what this relationship should be. It seems odd that a product would be associated to a single user in a relationship under a user. Where I wanted to associate users to products like this I would typically expect to see a many-to-many relationship where a User has products associated to them, but those products could be associated to many users. (Rather than distinct products assigned exclusively to one user)
As it stands your product has a required User reference, so if this needs to be associated to the current user then I would fetch the current user ID from your Authentication state or session state and assign that to the Product on creation as well. Again either setting the FK or loading a User entity and setting the User navigation property
You should fill ProductBrandId, ProductTypeId and UserId fields in Product object with existing (or freshly added) values to avoid Foreign key constraint violations.

I need to do a query on a returned list of users from another query, by Id

Currently, I have a service that grabs user information from the User table. The users can be created by admins or an employee and all of these employees have their own Id. So with that in mind, there is a column called CreatedBy which holds the id of this admin, or the user, that of which's name I have to return. So far I've pulled the user model but now I need to create the part where I pull the user's name with the user.Id in the CreatedBy
This is what I have pulling from my database tables Users and Company and the query parameters are just a name or company name
public async Task<List<ApplicationUser>> SearchUsers(UserSearchDto userSearchDto)
{
userSearchDto.FirstName ??= string.Empty;
userSearchDto.LastName ??= string.Empty;
userSearchDto.CompanyName ??= string.Empty;
return await _locationDbContext.Users
.Include(nameof(Company))
.Where(user => user.FirstName.Contains(userSearchDto.FirstName)
&& user.LastName.Contains(userSearchDto.LastName)
&& user.Company.Company_Name.Contains(userSearchDto.CompanyName))
.ToListAsync();
}
So within this list that I am returning I'm trying to do another query to grab more user information based on the CreatedBy id's returned in the first service to bring back the name of those users with the id's in CreatedBy.
var userDtos = _mapper.Map<List<ApplicationUser>, List<UserDetailsDto>>(users);
foreach (UserDetailsDto user in userDtos)
{
user.CreatedByName = await _userService
.SearchUsers(userDtos.Where(user.Id == user.CreatedBy))
}
I feel like this is a possible solution so far but I'm not sure where or how I would pull that because this solution is giving me an error at the point where I use the ".Where" statement. Maybe if I could create another service that would return the user by Id instead and use the createdby Id to pull the name but nothing like that exists yet. The model I'd like to return is also a bit different from the model representing the Users table as ApplicationUser has the CreatedBy which is an Id but the returned model, userDetailsDto will have a name string property as well that I will try and assign here in automapper. If I can think of how I can assign the name by the Id.
CreateMap<ApplicationUser, UserDetailsDto>()
.ForMember(dest => dest.CompanyName,
opts => opts.MapFrom(src => src.Company.Company_Name));
Ideally this is something that you should be able to resolve using navigation properties. If your User table uses CreatedBy to represent the CreatedBy User ID then you could adjust your mapping to facilitate a CreatedBy navigation property:
public class User
{
public class UserId { get; set; }
// ...
public virtual User CreatedBy { get; set; }
}
Then in the mapping use a shadow property for the FK association: (in OnModelCreating or using an EntityTypeConfiguration)
EF Core
.HasOne(x => x.CreatedBy)
.WithMany()
.HasForeignHey("CreatedBy") // Property on Users table
.Required();
EF 6
.HasRequired(x => x.CreatedBy)
.WithMany()
.Map(x => x.MapKey("CreatedBy")) // Property on Users table
Alternatively if you want the CreatedBy FK accessible in the User table, map it as something like CreatedByUserId:
public class User
{
public int UserId { get; set; }
// ...
[ForeignKey("CreatedBy"), Column("CreatedBy")]
public int CreatedByUserId { get; set; }
public virtual User CreatedBy { get; set; }
}
Now when you go to search for your users, you can project your CreatedBy user ID and Name in one go.
When it comes to optional search parameters you should keep the conditionals (if/else/ null checks etc ) outside of the Linq wherever possible. This helps compose more efficient queries rather than embedding conditional logic into the SQL.
public async Task<List<ApplicationUserViewModel>> SearchUsers(UserSearchDto userSearchDto)
{
var query = _locationDbContext.Users.AsQueryable();
if (!string.IsNullOrEmpty(userSearchDto.FirstName))
query = query.Where(x => x.FirstName.Contains(userSearchDto.FirstName));
if (!string.IsNullOrEmpty(userSearchDto.LastName))
query = query.Where(x => x.LastName.Contains(userSearchDto.LastName));
if (!string.IsNullOrEmpty(userSearchDto.CompanyName))
query = query.Where(x => x.Company.Name.Contains(userSearchDto.CompanyName));
return await query.ProjectTo<ApplicationUserViewModel>(_config)
.ToListAsync();
}
Where _config reflects an automapper MapperConfiguration containing the details on how to map a User to your desired view model. If you're not leveraging Automapper you can accomplish this using Select to project the values. Other considerations there would be to consider using StartsWith instead of Contains, perhaps offering an option to perform a more expensive Contains search... Also adding things like minimum search length checks (I.e. 3+ characters) and pagination or result row limits (I.e. Take(50)) to avoid outrageous search requests from hammering your system. (I.e. searching for users with "e" in the first name)
That view model might have UserId, UserName, CompanyName, then things like CreatedByUserId, CreatedByName. To resolve the CreatedBy details you just reference u.CreatedBy.UserId and u.CreatedBy.Name either in the Automapper config or within your Select(u => new ApplicationUserViewModel { ... }) statement.

Model relationship based on compound key

I have two entities in an EF Core project: Store and Employee
Employee has a key that references Store, and also has an active flag.
When I pull back Stores from the DbContext, I want to see only those Employees that have the key that references the store in question and has an active flag.
I am stuck as to how to restrict based on the active flag.
The Minimal example looks like:
public class Employee
{
public Guid Id {get; set;}
[ForeignKey("Store")]
public Guid StoreId{ get; set; }
public bool IsActive {get; set; }
}
public class Store
{
public Guid Id {get; set;
public List<Employee> Employees{get; set;}
}
How can I generate the behavior I want? At the moment, this will pull back every Employee, whether active or not.
You can setup a Global Query Filter.
Simply add the following to your OnModelCreating override:
modelBuilder.Entity<Employee>()
.HasQueryFilter(e => e.IsActive);
Unfortunately EF Core does not support query level filters. All you can do is to Disable Filters. So if you want this to be per specific query or want to query for Active == false, I'm afraid you have to use projection as suggested in another answer.
Something like this?
using( MyDBContext db = new MyDBContext()){
var activeEmployees = (from em in db.Employees
join s in db.Store on em.StoreId == s.Id
where em.IsActive == true
select em).ToList();
}

Entity Framework inheritance

SQL Layer:
I have a table
Entity Framwork Layer:
I have the following rule: all Offers, which have State is null, are Outstanding offers, State is true are Accepted offers, State is false are Declined offers. Also, part of fields used only for Outstanding, part - only for Accepted etc... I use Database first approach, so, I updated EF model from DB and renamed Offer entity to OfferBase and created 3 child classes:
it works fine for add/select entities/records. Right now I want to "move" offer from outstanding to accept offer, so, I need to set Status=true (from Status is null) for appropriate record. But how to do it by Entity Framework? If I try to select outstanding offer as Accept offer I get an null reference (and clearly why)
// record with ID=1 exists, but State is null, so, EF can not find this record and offer will be null after the following string
var offer = (from i in _db.OfferBases.OfType<EFModels.OfferAccepted>() where i.ID == 1 select i).FirstOrDefault();
if I try to select as OfferBase entity I get the following error:
Unable to cast object of type
'System.Data.Entity.DynamicProxies.OfferOutstanding_9DD3E4A5D716F158C6875FA0EDF5D0E52150A406416D4D641148F9AFE2B5A16A'
to type 'VTS.EFModels.OfferAccepted'.
var offerB = (from i in _db.OfferBases where i.ID == 1 select i).FirstOrDefault();
var offer = (EFModels.OfferAccepted)offerB;
ADDED NOTES ABOUT ARCHITECTURE:
I have 3 types of Offer entity. There are: AcceptOffer, DeclineOffer and OutstandingOffer.
AcceptOffer:
UserID
ContactID
Notes
FirstContactDate
LastContactDate
[... and 5-10 the unique fields...]
DeclineOffer:
UserID
ContactID
Notes
[... and 5-10 the unique fields...]
OutstandingOffer:
UserID
ContactID
FirstContactDate
LastContactDate
[... and 5-10 the unique fields...]
How to do it correctly? Of course, I can select a record, remove from DB and add new with appropriate state value, but how to do it normally?
You can't change the type of an object once it's created. Your object model seems wrong.
Either you delete the outstanding offer and create an accepted offer from it (looks like what you are currently doing) but you may lose relations as you created a new object with a new identity (you can also copy them before removing the old object). Or you want to keep the same object and change its state.
If you want to keep the same identity then preffer composition over inheritance.
Your code could look like this :
public class Offer
{
public int Id { get; set; }
public virtual OfferState State { get; set }
}
public class OfferState
{
public int OfferId { get; set; }
public string Notes { get; set; }
}
public class AcceptedOfferState : OfferState
{
public DateTimeOffset AcceptDate { get; set; }
}
public class DeclinedOfferState : OfferState
{
public DateTimeOffset DeclinedDate { get; set; }
}
If you still want to change the type of the object and keep its identity then you may use stored procedures ; as stated by Noam Ben-Ami (PM owner for EF) : Changing the type of an entity.
Rather than trying to add these custom classes to your entity framework model, just create them as normal c# classes and then use a projection to convert from the entity framework generated class to your own class e.g.
var accepteOffers= from i in _db.Offers
where i.ID == 1 && i.Status == true
select new OfferAccepted { AcceptDate = i.AcceptDate, StartTime = i.StartTime /* map all releaveant fields here */};

fast/right way to create Domain-Objects out of database

i have in the database typical tables like user, usergroup, and they have relations.
in my domain-model i want that if you request users, you will get the users and each user has the property belongsto, which means these are the groups he belongs to. in the property i want to have the list of groups (typesafe as groups)
the same should be done on the other hand, that means each group should know which users belong to the group.
i have this classes (example):
public class User
{
int Id { get; set; }
string Name { get; set; }
List<Usergroup> BelongsTo { get; set; }
User()
{
BelongsTo = new List<Usergroup>();
}
}
public class Usergroup
{
int Id { get; set; }
string Name { get; set; }
List<User> Users { get; set; }
Usergroup()
{
Users = new List<User>();
}
}
internal class Relation
{
int userId;
int groupId;
}
now, whats the best way to implement it, and whats the fastest way to get all objects filled.
btw: i'm using subsonic to get all the database-records (for each table one select). and there are at least 1500 user and 30 groups.
my current code is based on foreach over the groups, relations.where for the groups, and then users.singleordefault to find the user and then add the user to the group and the group to the user. but it takes appx 30secs to do this.
my client-app is a webapp, so i store the results after all in a cached repository, thats why i'm doing it in this way!!
my current code is based on foreach over the groups, relations.where for the groups, and then users.singleordefault to find the user and then add the user to the group and the group to the user.
I don't understand what you're saying here.
it takes appx 30secs to do this
Whatever it is you're doing seems too slow, given there's only 1500 users and 30 groups.
now, whats the best way to implement it, and whats the fastest way to get all objects filled.
If you wanted ...
Either, all users, with the groups for each user
Or, all groups, with the users for each group
... then I might suggest selecting joined records from the database.
However, because ...
You want all data (joined in both directions)
You only have 1500 records
... then I suggest that you select everything as simply as you can, and join in it your application.
//select users
Dictionary<int, User> users = new Dictionary<int, User>();
foreach (User user in selectUsersFromDatabase())
{
users.Add(user.Id, user);
}
//select groups
Dictionary<int, Group> groups = new Dictionary<int, Group>();
foreach (Group group in selectGroupsFromDatabase())
{
groups.Add(group.Id, group);
}
//select relations
//and join groups to users
//and join users to groups
foreach (Relation relation in selectRelationsFromDatabase())
{
//find user in dictionary
User user = users[relation.userId];
//find group in dictionary
Group group = groups[relation.groupId];
//add group to user and add user to group
user.BelongsTo.Add(group);
group.Users.Add(user);
}
The best thing is to only retrieve the entity that was requested by the client but allow that entity to expose a method for retrieving all its related entities from the database. You shouldn't attempt to pull everything back at once from the database as you are not certain if the client even needs this data.
Do something like this:
public class User
{
int Id { get; set; }
string Name { get; set; }
public List<Usergroup> GetUserGroups()
{
// go and get the user groups
}
}
If you wanted to you could create a property that would hold this value as a member of the User class and use the GetUserGroups method to lazy-load the value for you.

Categories