How to delete records from database correctly - c#

While I was trying to delete records from databse in wep api, occurred with error "System.InvalidOperationException: There is already an open DataReader associated with this Connection which must be closed first".
Method body:
[HttpDelete]
[Route("/DeleteUrls")]
public async Task<IActionResult> DeleteUrls(string userName)
{
var user = _dataContext.Users.FirstOrDefault(x => x.UserName == userName);
if (user==null)
{
return BadRequest("No user with such Username");
}
switch (user.IsAdmin)
{
case true:
var longUrls2Remove = _dataContext.Urls.AsQueryable();
foreach (var item in longUrls2Remove)
{
var shortUrl2Remove = _dataContext.ShortedUrls.FirstOrDefault(x => x.Url == item);
_dataContext.ShortedUrls.Remove(shortUrl2Remove);
_dataContext.Urls.Remove(item);
}
await _dataContext.SaveChangesAsync();
return Ok();
case false:
var longUrlsToRemove = _dataContext.Urls.Where(x => x.UserCreatedBy == user);
foreach (var item in longUrlsToRemove)
{
var shortUrlToRemove = _dataContext.ShortedUrls.FirstOrDefault(x => x.Url == item);
_dataContext.ShortedUrls.Remove(shortUrlToRemove);
_dataContext.Urls.Remove(item);
await _dataContext.SaveChangesAsync();
return Ok();
}
return BadRequest();
}
}
Logic is to delete all records if user is admin, and only his own if he isnt.
Database structure:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public bool IsAdmin { get; set; }
public List<Url> Urls { get; set; }
}
public class Url
{
public int Id { get; set; }
public string OriginalUrl { get; set; }
public DateTime CreatedAt { get; set; }
public int UserId { get; set; }
public User UserCreatedBy { get; set; }
public ShortUrl ShortUrl { get; set; }
}
public class ShortUrl
{
public int Id { get; set; }
public string ShortedUrl { get; set; }
public int UrlId { get; set; }
public Url Url { get; set; }
}
Obviously the problem is with another open connetion, but I dont get any idea how to implement this method correctly.

Reason : you are executing two or more queries at the same time.
Solution : add ToList() after query.
var longUrlsToRemove = _dataContext.Urls.Where(x => x.UserCreatedBy == user).ToList()
IQueryable is not a collection of entities in memory.
ToList() stores records in memory. So you don't connect to database in every loop.

Related

Creating and updating entities with a many to many relationship

In an ASP.NET core (dotnet 6) web application, we have a small user management functionality in which a user can be assigned to a specific site (physical location) with certain rights. A user can be assigned to one or more sites and a site can also be assigned to one or more users, thus the many to many relationship. We have the following classes:
public class User
{
public int ID { get; set; }
public string SamAccountName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int GroupID { get; set; }
public bool IsEnabled { get; set; }
public ICollection<UserSite> UserSites { get; set; }
}
public class Site
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public ICollection<UserSite> UserSites { get; set; }
}
The many to many relationship is described in a join table with fluent API:
public class UserSite
{
public int UserID { get; set; }
public User User { get; set; }
public int SiteID { get; set; }
public Site Site { get; set; }
}
// Fluent API for UserSite table
builder.HasKey(us => new { us.UserID, us.SiteID });
builder.HasOne(us => us.User)
.WithMany(us => us.UserSites)
.HasForeignKey(us => us.UserID)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne(us => us.Site)
.WithMany(us => us.UserSites)
.HasForeignKey(us => us.SiteID)
.OnDelete(DeleteBehavior.Cascade);
These classes result in the following DTO's:
public class UserDTO
{
public int ID { get; set; }
public string SamAccountName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int GroupID { get; set; }
public bool IsEnabled { get; set; }
public IList<SiteDTO> Sites { get; set; }
}
public class SiteDTO
{
public int ID { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
What we want to know is, what is the best way to create a new user or update an existing user? Just to clarify, the data in the Sites table exists already and is static. What I mean by that is that when creating a new user or updating an existing one, a user can be assigned to one or more existing sites, but no new site is created in the process.
We already tried some things that did work but we are not sure if this is really the best way to this or if there is a far better way to do it. Here is what we tried:
The create method
// Create method
public IActionResult CreateUser(UserDTO userDTO)
{
if (ModelState.IsValid)
{
User user = _mapper.Map<User>(userDTO);
user.IsEnabled = true;
AttachSitesToUser(user.ID, user.UserSites);
_context.Users.Add(user);
_context.SaveChanges();
userViewModel = _mapper.Map<UserViewModel>(user);
}
return new JsonResult(new[] { userDTO });
}
The update method:
// Update method
public IActionResult UpdateUser(UserDTO userDTO)
{
if (ModelState.IsValid)
{
User user = _context.Users
.Include(u => u.Sites)
.ThenInclude(s => s.Site)
.Single(u => u.ID == userDTO.ID);
_mapper.Map(userViewModel, user);
AttachSitesToUser(user.ID, user.UserSites);
_context.Entry(user).State = EntityState.Modified;
_context.SaveChanges();
}
return new JsonResult(new[] { userDTO });
}
The helper method AttachSitesToUser:
// Helper method that adds the user ID and attaches the site(s) to the context
private void AttachSitesToUser(int userID, IEnumerable<UserSite> userSites)
{
foreach (UserSite userSite in userSites)
{
userSite.UserID = userID;
if (Context.Entry<Site>(userSite.Site).State == EntityState.Detached)
{
Context.Sites.Attach(userSite.Site);
Context.Entry<Site>(userSite.Site).State = EntityState.Unchanged;
}
}
}
As you can see, for the moment we have to loop through the UserSites list in the User entity and attach the sites the user was assigned to manually to the context. Is there no better way to do this or is this the official best practice?

Ef Core filtering included query using uow and repository

I started creating a role-based security system in my WinForm application so I began with Form navigation (Permission Navigation) and this is my entity's
public partial class User
{
public User()
{
UsersToRoles = new HashSet<UsersToRole>();
}
public string Login { get; set; } = null!;
public string PasswordUser { get; set; } = null!;
public string? FullName { get; set; }
public string? Email { get; set; }
public int Id { get; set; }
public virtual ICollection<UsersToRole> UsersToRoles { get; set; }
}
public partial class Role
{
public Role()
{
UsersToRoles = new HashSet<UsersToRole>();
PermissionNavigations = new HashSet<PermissionNavigation>();
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public virtual ICollection<UsersToRole> UsersToRoles { get; set; }
public virtual ICollection<PermissionNavigation> PermissionNavigations { get; set; }
}
public partial class UsersToRole
{
public int Id { get; set; }
public int IdUser { get; set; }
public int IdRole { get; set; }
public virtual Role IdRoleNavigation { get; set; } = null!;
public virtual User IdUserNavigation { get; set; } = null!;
}
public partial class Navigation
{
public Navigation()
{
PermissionNavigations = new HashSet<PermissionNavigation>();
}
public int Id { get; set; }
public string Page { get; set; } = null!;
public string Forms { get; set; } = null!;
public virtual ICollection<PermissionNavigation> PermissionNavigations { get; set; }
}
public partial class PermissionNavigation
{
public int Id { get; set; }
public int IdRole { get; set; }
public int IdNavigation { get; set; }
public virtual Navigation IdNavigationNavigation { get; set; } = null!;
public virtual Role IdRoleNavigation { get; set; } = null!;
}
This is my geniric GetAllIncluding method
public async Task<IEnumerable<T>> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
try
{
IQueryable<T> query = dbSet;
foreach (Expression<Func<T, object>> includeProperty in includeProperties)
{
query = query.Include<T, object>(includeProperty);
}
return await query.ToListAsync();
}
catch (Exception ex)
{
throw new Exception($"{nameof(GetAllIncluding)} properties could not be included properly: {ex.Message}");
}
}
And this is how I use it in my PermissionNavigationService
public async Task<IEnumerable<PermissionNavigationDto?>>
GetAllPermissionNavigationDetailsByUserAsync(int idUser)
{
var permissionNavigation = await unitOfWork.PermissionNavigations.GetAllIncluding(
x => x.IdNavigationNavigation,
x => x.IdRoleNavigation,
x => x.IdRoleNavigation.UsersToRoles.Where(x=>x.IdUser== idUser));
return mapper.Map<IEnumerable<PermissionNavigationDto?>>(permissionNavigation);
}
I know that this line of code only filtering UsersToRoles entity not PermissionNavigation entity
x => x.IdRoleNavigation.UsersToRoles.Where(x=>x.IdUser== idUser)
The question is: What can be done to get all Permission Navigation related to specific user
Update1
I am looking for something like this but in ef core
SELECT PermissionNavigation.[Id]
,PermissionNavigation.[IdRole]
,Roles.Name
,Navigation.Forms
,[IdNavigation]
,UsersToRoles.IdUser
FROM [SIM].[dbo].[PermissionNavigation]
INNER JOIN Roles on Roles.Id=IdRole
INNER JOIN Navigation on Navigation.id=IdNavigation
INNER JOIN UsersToRoles on UsersToRoles.IdRole=PermissionNavigation.[IdRole]
WHERE UsersToRoles.IdUser=#IdUser
Update2
I appreciate all the help I received.
I decided to go this way:
When the user has successfully logged in, I catch the Id then I make a call to get all roles related to that user after that I make another call to get all permission navigation using role Id that I got earlier.
List<PermissionNavigationDto> navigationDtos = new();
var userRoles = await userToRoleService.GetUserRolesAsync(LoginUserDetails.Id);
foreach (var role in userRoles)
{
var navigation = await permissionNavigationService.GetPermissionNavigationByRoleIdAsync(role.IdRole);
navigationDtos.AddRange(navigation);
}
What you need is the SelectMany
//The userId concerned
int userId = 1;
//As an example, let's say you have your repository items below
List<User> users = new List<User>();
List<UsersToRole> userToRoles = new List<UsersToRole>();
List<Role> roles = new List<Role>();
//if you have to retrieve user
User myUser = users.SingleOrDefault(x => x.Id == userId);
//get all roleIds this user (userId = 1) belongs to
List<int> roleIds = userToRoles.Where(x => x.IdUser == userId).Select(us => us.IdRole).ToList();
//get the role objects, then the PermissionNavigations and flatten it out with SelectMany
List<PermissionNavigation> permissionNavigations = roles.Where(us => roleIds.Contains(us.Id)).Select(us => us.PermissionNavigations).SelectMany(x => x).ToList();
I hope this helps.

I am working on an API for a class I am in and I have a cannot convert error

I am working on an API and I have an error that says "cannot convert from "HolidayChallenge.Models.Ornament.Ornament2Edit to HolidayChallenge.Data.Ornament2". The code follows:
[Route("api/Ornament2/Update")]
public IHttpActionResult Put(Ornament2Edit ornament2)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var service = CreateOrnament2Service();
if (!service.UpdateOrnament(ornament2)) //This is the line the error is on
{
return InternalServerError();
}
return Ok("Your Ornament was updated!");
I am not sure how to fix this one. I have figured a lot of similar ones out but this one is fighting me. Any help would be much appreciated. Thank you in advance.
Update to question
OrnamentEdit:
namespace HolidayChallenge.Models.Ornament
{
public class Ornament2Edit
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
public int TreeId { get; set; }
}
}
Ornament2:
namespace HolidayChallenge.Data
{
public class Ornament2
{
[Key]
public int Id { get; set; }
public Guid UserId { get; set; }
public string Description { get; set; }
public virtual List<Ornament2> Ornaments { get; set; }
[ForeignKey("ChristmasTree")]
public int TreeId { get; set; }
[Required]
public virtual ChristmasTree ChristmasTree { get; set; }
}
}
Ornament2Service:
public bool UpdateOrnament(Ornament2 model)
{
using (var ctx = new ApplicationDbContext())
{
var entity =
ctx
.Ornaments
.Single(e => e.Id == model.Id && e.UserId == _userId);
entity.Id = model.Id;
entity.Description = model.Description;
entity.TreeId = model.TreeId;
return ctx.SaveChanges() == 1;
}
}
your Service input is Ornament2 object but you pass an Ornament2Edit object and this causes the error. you must pass an object with the correct type.

Xamarin forms SQLite relation

I am going to create an app in Xamarin forms and starting with SQLite. I need to have unique list items for each Main item in the app.
For example, I am having a list with items. When I am selecting an item in the list a new page will pop up and display the items of that item.
So from my point of view I am in need of two SQLite tables with relations between.
This is the Main table with all profiles
[Table("Profiles")]
public class ProfileItems
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string ProfileName { get; set; }
public string ProfileRace { get; set; }
public string iconn = "icon.png";
public string ProfileIcon { get; set; }
public DateTime BDay { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<LoggItems> Loggs { get; set; }
}
This is the logg table for each Profile, which should be unique for each profile
[Table("Loggs")]
public class LoggItems
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Title { get; set; }
public string Text { get; set; }
[ForeignKey(typeof(ProfileItems))]
public int ProfileId { get; set; }
}
Adding the items like this
public class ProfileDatabase
{
readonly SQLiteAsyncConnection database;
public ProfileDatabase(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
database.CreateTableAsync<ProfileItems>().Wait();
database.CreateTableAsync<LoggItems>().Wait();
}
//Profile
public Task<List<ProfileItems>> GetProfileAsync()
{
return database.Table<ProfileItems>().ToListAsync();
}
public Task<ProfileItems> GetProfileAsync(int id)
{
return database.Table<ProfileItems>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
public Task<int> SaveProfileAsync(ProfileItems profileItems)
{
if (profileItems.Id != 0)
{
return database.UpdateAsync(profileItems);
}
else
{
return database.InsertAsync(profileItems);
}
}
public Task<int> DeleteProfileAsync(ProfileItems profileItems)
{
return database.DeleteAsync(profileItems);
}
//Logg
public Task<List<LoggItems>> GetLoggAsync()
{
return database.Table<LoggItems>().ToListAsync();
}
public Task<LoggItems> GetLoggAsync(int id)
{
return database.Table<LoggItems>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
public Task<int> SaveLoggAsync(LoggItems loggItems)
{
if (loggItems.Id != 0)
{
return database.UpdateAsync(loggItems);
}
else
{
return database.InsertAsync(loggItems);
}
}
public Task<int> DeleteLoggAsync(LoggItems loggItems)
{
return database.DeleteAsync(loggItems);
}
}
Both Logg and Profile list/tables do work but they do not have any relations between so the loggs show the same in all profile.
How should I do this?
How about to use Linq and join the relationships.
1.- First you have to add the namespace:
using System.Linq;
2.- Change the property in the class ProfileItems to be a IEnumerable
[OneToMany(CascadeOperations = CascadeOperation.All)]
public virtual IEnumerable<LoggItems> Loggs { get; set; }
3.- This is the method to join the loggs with the profile items.
var profiles = await GetProfileAsync();
var loggs = await GetLoggAsync();
var query = from p in profiles
join l in loggs on p.Id equals l.ProfileId into list
select new ProfileItems
{
Id = p.Id,
ProfileIcon = p.ProfileIcon,
ProfileName = p.ProfileName,
ProfileRace = p.ProfileRace,
BDay = p.BDay,
Loggs = list
};
I think you must add "virtual" keyword for enabling lazy loading.
[OneToMany(CascadeOperations = CascadeOperation.All)]
public virtual List<LoggItems> Loggs { get; set; }
And there's the "[InverseProperty]" to specify their related navigation property.
public class LoggItems
{
*
[ForeignKey(typeof(ProfileItems))]
[InverseProperty("Loggs")]
public int ProfileId { get; set; }
*
}

WebAPI [FromBody] as EF Model behaves like it is immutable

I have an WebAPI Controller that uses complex types from Entity Framework. When I receive the object I check to see if it exists. If it doesn't I'd like to create a new. Before I create a new I'd like to add a couple additional values to the object. If I add a break point and a watch I can see the value and it appears like it has changed. But the value doesn't make it to the database.
[Authorize(Roles ="customerprofileuser")]
[Route("api/CustomerProfile/Save")]
[HttpPost]
public IHttpActionResult SaveCustomerProfile([FromBody] MERP.Customer _input)
{
Models.Message.Response _return = new Models.Message.Response();
_return.Message = "Profile Saved!";
_return.Now = DateTime.Now;
try {
ERPEntities ent = new ERPEntities();
var cust = ent.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
if (cust == null)
{
_input.ID = Guid.NewGuid();
_input.Alias = getCustomerNumberNext(_input.Type);
_input.CreatedOn = DateTime.Now;
ent.Customers.Add(_input);
}
else
{
ent.Customers.Attach(_input);
ent.Entry(_input).State = System.Data.Entity.EntityState.Modified;
}
_return.ResponseObject = _input.ID.ToString();
ent.SaveChanges();
}
catch (Exception ex)
{
_return.Message = ex.Message;
_return.Severity = 3;
}
return Ok(_return);
}
If I map the values to a new object like this, everything works as expected.
var val = new Customer();
val.ID = Guid.NewGuid();
val.Active = _input.Active;
val.Alias = getCustomerNumberNext(_input.Type);
val.CreatedOn = DateTime.Now;
ent.Customers.Add(val);
I'd rather not map every single property to the new object property. Is there a way around this behavior?
Here's a sample of the auto-generated Customer class from my Entity Model.
public partial class Customer
{
public System.Guid ID { get; set; }
public string Name { get; set; }
public Nullable<System.Guid> Type { get; set; }
public string Alias { get; set; }
public string Website { get; set; }
public string Note { get; set; }
public string Email { get; set; }
public Nullable<System.Guid> Salesman { get; set; }
public Nullable<System.Guid> SalesRegion { get; set; }
public Nullable<bool> Active { get; set; }
public string LinkedIn { get; set; }
public string Facebook { get; set; }
public string Twitter { get; set; }
public string GoldmineFK { get; set; }
public string SalesFK { get; set; }
public string InventoryFK { get; set; }
public Nullable<System.Guid> Industry { get; set; }
public Nullable<System.Guid> Lead { get; set; }
public Nullable<System.Guid> Show { get; set; }
public Nullable<System.Guid> Territory { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
}
Here's the getCustomerNumberNext function
private string getCustomerNumberNext(Guid? companyid)
{
ERPEntities ent = new ERPEntities();
var _prefix = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberPrefix"
select p.Value.ToString()).FirstOrDefault();
var _number = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberSequence"
select p.Value.ToString()).FirstOrDefault();
var _newNumber = Convert.ToInt32(_number) + 1;
try
{
var _update = (from p in ent.CompanyLookups
where p.Type == "CustomerNumberSequence"
select p).FirstOrDefault();
_update.Value = _newNumber.ToString();
ent.SaveChanges();
}
catch (Exception ex)
{ return ex.Message; }
return _prefix + _number;
}
EDIT: The C# code works as expected. The issue was with the data round tripping from the client and incompleteness.
I believe there is a typo in your question, which says "does make it to the database" but I believe you meant "does not make it to the database"
With that assumption I tried running similar code locally and was able to save the values as expected. The primary difference is that Alias is an integer in my code and I am assuming it is a complex class in your code. Here is the code that successfully saved the values to the database,
public class HomeController : ApiController
{
[HttpPost]
[Route("api/CustomerProfile/Save")]
public IHttpActionResult SaveCustomerProfile([FromBody] Customer _input)
{
masterEntities masterEntities = new masterEntities();
var cust = masterEntities.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
if (cust == null)
{
_input.ID = Guid.NewGuid();
_input.Alias = 0;
_input.CreatedOn = DateTime.Now;
masterEntities.Customers.Add(_input);
}
else
{
masterEntities.Customers.Attach(_input);
masterEntities.Entry(_input).State = System.Data.Entity.EntityState.Modified;
}
masterEntities.SaveChanges();
return Ok();
}
}
Here is what the generated Customer class like,
public partial class Customer
{
public System.Guid ID { get; set; }
public bool Active { get; set; }
public Nullable<int> Alias { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
}
Can you update your question with the Customer and Alias classes from your code and I can try reproducing that?
On a side note, I would suggest changing
var cust = ent.Customers.AsNoTracking().Where(w => w.ID == _input.ID).FirstOrDefault();
to
var cust = ent.Customers.AsNoTracking().FirstOrDefault(w => w.ID == _input.ID);

Categories

Resources