User defined order using EF and Postgresql - c#

I have a category model class with category name, Id and admin order properties.
I want to provide an API endpoint to admin to reorder the categories as per his need.
I am not sure what is the most efficient way to tackle this using the EF Core and Postgresql (Core 2.2) - I'm a newbie to C# and .NET Core. Please correct me if my approach is wrong.
Here is what I tried:
My model class:
public class Category
{
public string Name { get; set; }
public int Id { get; set; }
public int adminOrder { get; set; }
}
I have my service repository as below with GetCategories and UpdateCategories methods:
public class CategoryRepository: ICategoryRepository
{
private ApplicationDbContext _categoryContext
public CategoryRepository(ApplicationDbContext categoryContext)
{
__categoryContext = _categoryContext
}
public ICollection<Category> GetCategories()
{
return _categoryContext.Categories.OrderBy(c=> c.Name).ToList();
}
public bool UpdateCategoryOrder(Category category)
{
_categoryContext.Update(category);
return Save();
}
public bool Save()
{
var changesMade = _categoryContext.SaveChanges();
return changesMade >= 0 ? true : false;
}
}
In my controller I am taking the list of categories in request body and comparing them with existing categories on field adminOrder.
If they are not equal I am updating the database
public IActionResult UpdateCategory([FromBody] List < Category > categoriesList)
{
var existingList = _categoryRepository.GetCategories();
foreach(var cat in categoriesList)
{
if (existingList.Where(c => c.Id == cat.Id && c.adminOrder != cat.adminOrder)
{
_categoryRepository.UpdateCategory(cat) // since it returns true I can use if statement to check if it fails
}
}
}
I am not sure it is the right way to do or not.
I went through few docs like
https://dba.stackexchange.com/questions/201898/user-defined-ordering-in-sql
https://begriffs.com/posts/2018-03-20-user-defined-order.html
Not sure how to implement them in EF

Related

Copying data between models and saving children without entities duplicating themselves in Entity Framework

I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.

EntityCommandExecutionException - Trying to link 2 models in 1 database [duplicate]

This question already has answers here:
There is already an open DataReader associated with this Command which must be closed first
(19 answers)
Closed 6 years ago.
Pretty much I have 2 models - A announcement where some can post an announcement and a seen model which determins if someone has seen the announcement. her eis the models:
Announce:
public class Announcement
{
public int AnnouncementId { get; set; }
public string AnnouncementContent { get; set; }
public virtual ApplicationUser User { get; set; }
}
and Seen:
public class Seen
{
public int SeenId { get; set; }
public virtual Announcement Announcement { get; set; }
public virtual ApplicationUser User { get; set; }
}
in my AnnouncementController.Index I have this code which pretty much supposed to be, if you view this page, mark off every announcement as seen bbut am getting errors at the "new seen" part:
public ActionResult Index()
{
string currentUserId = User.Identity.GetUserId();
var currentUser = db.Users.FirstOrDefault(x => x.Id == currentUserId);
if(db.Announcements != null)
{
foreach (Announcement anoun in db.Announcements)
{
new Seen
{
User = db.Users.Add(currentUser),
Announcement = db.Announcements.FirstOrDefault(x => x.AnnouncementId == anoun.AnnouncementId),
};
}
}
return View(db.Announcements.ToList());
}
There is already an open DataReader associated with this Command which must be closed first.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public DbSet<Announcement> Announcements { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Seen> Seens { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
Event if you did not have that error, you still have other issues. See below:
public ActionResult Index() {
string currentUserId = User.Identity.GetUserId();
var currentUser = db.Users.FirstOrDefault( x => x.Id == currentUserId );
List<Seen> seens = new List<Seen>();
if( db.Announcements != null ) {
foreach( Announcement anoun in db.Announcements ) {
seens.Add(
new Seen
{
User = currentUser, // You have this already so why go to the database again?
Announcement = anoun, // Same with this.
});
}
}
// Save seens to the database
return View( db.Announcements.ToList() );
The error is because you are actively iterating over a collection streaming from the database and then trying to re-issue another select to the database inside that loop. It can be fixed by forcing the materialization of the collection before you enter the loop using ToList() (other options are also available like AsEnumerable or ToArray).
foreach (Announcement anoun in db.Announcements.ToList())
{
new Seen
{
User = db.Users.Add(currentUser),
Announcement = db.Announcements.FirstOrDefault(x => x.AnnouncementId == anoun.AnnouncementId),
};
}
That being said I am not sure why you are doing it this way. Why not attach the instance anoun directly as you are only using a single (or same in the code shown) DbContext instance (variable named db).
foreach (Announcement anoun in db.Announcements)
{
new Seen
{
User = currentUser, // also this seemed wrong before, just assign the reference directly
Announcement = anoun
};
}
Or make it even more simple:
var newSeens = db.Announcements.Select(x => new Seen(){User = currentUser, Announcement = x}).ToList();
db.Seens.AddRange(newSeens);
db.SaveChanges();
This assumes that the user has not seen any announcement. If the user has seen some then you need to filter db.Announcements on existing Seen records for that user.

How can I send a query to an IQuerable method on an ASP.NET controller?

I am using Entity Framework 6 and trying to get some data from my controller. The data is from a table of words which has a key of WordId (the actual words).
My controller has this method:
[Route("Get")]
public IQueryable<Word> Get()
{
return db.Words;
}
Here’s my Word object:
public class Word
    {
        public string WordId { get; set; } // WordId (Primary key) (length: 20)
        public int CategoryId { get; set; }
    }
Is there a way that I can use this method to just get those words that have a first character between A and E ?
Never return Entity or IQueryables from the controller, instead create a model and return it as mentioned below:
string matchStr = "abcde";
[Route("Get")]
public List<WordModel> Get()
{
return db.Words.Where(p=>matchStr.ToUpper().Contains(p.UniqueID.ToUpper().FirstOrDefault())).Select(p=>new WordModel(){
WordId = p.WordId,
CategoryId = p.CategoryId
}).ToList();
}
Create a model class WordModel, as
public class WordModel
{
public string WordId { get; set; }
public int CategoryId { get; set; }
}
I agree with Sophia. It is a good idea to expose view models (designed based on the view) rather than the actual entities. Lets assume WordVm is that in your case. The return type form the controller action will be a List. Add a reporsitory or a manager class that will be called from your action method that returns your result. I am creating a manager that has access to the db context class with the Word entity.
[Route("Get")]
public List<WordVm> Get()
{
var manager = new WordsManager()
return manager.GetWords();
}
public class WordsManager
{
public List<WordVm> GetWords()
{
return repo.Words.Where(a => {
var t = a.Trim().ToUpper().Substring(0, 1)[0];
return t >= 'A' && t <= 'E';
}).ToList();
}
}
Thank you,
Soma.

Dynamically add a linked field to an ASP.NET MVC EF model

I have the following database table:
ID ParentID LinksTo
---------------------
0 null "1,2"
1 0 "0,2"
2 0 "1"
It's a hierarchical design, where each records points to a parent record from the same table. I know the LinksTo field is a very bad design, but the table is given, I cannot change that. (Note that the LinksTo field creates a sort of many-to-many relationship.)
Using Code-First EF, I have the following model class:
public class Item
{
public int ID { get; set; }
public int ParentID { get; set; }
public string LinksTo { get; set; }
public virtual Item Parent { get; set; }
[InverseProperty("Parent")]
public virtual ICollection<Item> Children { get; set; }
}
Now, how can I add a dynamic property to the model so that I can access the Item collections Item.LinksTo and Item.LinksFrom (the reverse) in code?
I guess I'd do something like this:
private List<Item> _linkedItems;
private void UpdateLinksTo() {
this.LinksTo = string.Join<string>(_linkedItems.Select(i => i.ID.ToString()));
}
[NotMapped]
public ReadOnlyCollection<Item> LinkedItems {
get {
if(_linkedItems == null) {
_linkedItems = db.Items.Where(i => this.LinksTo.Split(',').Select(x => int.Parse(x)).Contains(i.ID)).ToList();
}
return _linkedItems.AsReadOnly();
}
}
[NotMapped]
public void AddLinkedItem(Item item) {
if(!_linkedItems.Select(i => i.ID).Contains(item.ID)) {
_linkedItems.Add(item);
UpdateLinksTo();
}
}
That will give you access to a read-only collection with methods to alter it (you can also make a DeleteLinkedItem method) which is about as good as you're going to do I think.
I haven't checked to even see if this compiles, btw.

mongodb-c# create document with unique fields

I'm new to mongo so i dont have a clue how to do this.
I'm working on a mvc project with repository pattern, and mongo database, using mongodb-C# driver. I cant figure out how to ensure that some fields are unique when created in database, for example when user registers i want the UserName or email to be unique.
According to mongodb docs this is how you create the unique index
db.accounts.ensureIndex( { "tax-id": 1 }, { unique: true } )
How can i do that with repository pattern and mongodb-C# driver?
This is a sample of the generic repository:
public class GenericRepository<T> : IGenericRepository<T>
{
public static MongoCollection Repository
{
get { return MongoHelpers.GetCollection<T>(); }
}
public virtual T Save(T pobject)
{
//should i modify something here?
Repository.Save(pobject);
return pobject;
}
public virtual T GetByID(BsonValue id)
{
return Repository.FindOneByIdAs<T>(id);
}
public virtual T GetByCondition(Expression<Func<T, bool>> condition)
{
return Repository.AsQueryable<T>().Where(condition).FirstOrDefault();
}
... more code ...
}
Here is a sample model:
public class Membership
{
[BsonId]
public ObjectId UserId { get; set; }
public string UserName { get; set; }
//can it be solved by adding data annotations? - if so, how?
public string Email{ get; set; }
public MembershipStatus Status { get; set; }
...
}
Here is a sample of a service layer that calls the methods mentioned above:
private readonly IGenericRepository<Membership> _membershipRepository;
public RegisterService(IGenericRepository<Membership> membershipRepository)
{
_membershipRepository = membershipRepository;
}
public Membership CreateNewUser(RegisterModel registerModel, Role role, bool requireConfirmationToken)
{
...
var membership = AutoMapper.Mapper.Map<RegisterModel, Membership>(registerModel);
...mode code...
_membershipRepository.Save(membership);
return membership;
}
I can always search the database to see if a value exists, but isnt there something better/cleaner?
Thanks
I've figure out how to handle this one. These 2 sources on stackoverflow helped me doing it:
How to create indexes in MongoDB via .NET
Creating MongoDB Unique Key with C#
This is the method that I've added in the repository:
public virtual void CreateIndex(string[] fields)
{
foreach (string field in fields)
{
if (!Repository.IndexExists(field))
{
Repository.EnsureIndex(new IndexKeysBuilder().Ascending(field), IndexOptions.SetUnique(true).SetSparse(true));
}
}
}
And then i have another class where i initialize the database indexes, and i call it in global.asax for example:
public static class DatabaseConfig
{
private static IGenericRepository<Membership> _membershipRepository = new GenericRepository<Membership>();
private static IGenericRepository<Profile> _profileRepository = new GenericRepository<Profile>();
public static void CreateIndex()
{
_membershipRepository.CreateIndex(new string[] { "UserName" });
_profileRepository.CreateIndex(new string[] { "Email" });
... more code ...
}
}

Categories