Adding a class to an ICollection List - c#

I have a C# project in which users can log in and create boards. The user class has an ICollection for listing all the boards made by a specific user and I am trying to figure out how to add that board to the list.
These are the two classes, the User class:
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string UserType { get; set; }
public DateTime LastLoginDate { get; set; }
public virtual ICollection<Board> Boards { get; set; }
public virtual ICollection<Post> Posts { get; set; }
And this is the Board class:
public class Board
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<Post> Posts { get; set; }
The aim is that each User would have a list of all the boards that have been added they have created so it is necessary for the board to be added to that list when it is created. This the webform where a board can be added:
public partial class AddBoard : System.Web.UI.Page
{
Board boardToAdd = new Board();
User user = new User();
Utility utility = new Utility();
static User loggedInUser;
protected void CreateButton_Click(object sender, EventArgs e)
{
string name = NameTextBox.Text;
loggedInUser = (User)Session["loggedInUser"];
string checkName = utility.CheckBoardName(name);
if (checkName == "OK")
{
boardToAdd.Name = name;
boardToAdd.DateCreated = DateTime.Now;
user.AddBoard(boardToAdd, loggedInUser);
boardToAdd.AddBoard(boardToAdd);
}
else
{
CreateLabel.Text = checkName;
}
}
And this is the method in the user class I've been trying to use to add the board to the list:
public User AddBoard(Board board, User user)
{
BulletinContext _context = new BulletinContext();
user.Boards.Add(board);
return null;
}
}
For added reference this is the method in the board class I am calling in order to add the board:
public bool AddBoard(Board board)
{
BulletinContext _context = new BulletinContext();
try
{
_context.Boards.Add(board);
_context.SaveChanges();
return true;
}
catch
{
return false;
}
}
}

In your AddBoard, you are adding to a local variable.
After function exit, this is destroyed.
Promote your _context to a class member

As Pieter21 said, the variable _context in Board.AddBoard() is recreated each time you call Board.AddBoard(). As far as I can see, _context should be static and declared outside of the AddBoard() method (since you probably need to access it later).

I've managed to sort out the problem. I just added in code to my method to call the user that the board was being added to and the board to be added.It now looks like this:
public User AddBoard(Board board, User user)
{
BulletinContext _context = new BulletinContext();
User _user = _context.Users.Find(user.ID);
Board _board = _context.Boards.Find(board.ID);
_user.Boards.Add(_board);
_context.SaveChanges();
return null;
}
It's necessary to call this method from the other method that adds the board to the Board class so that it occurs afterwards.

Related

How to correctly save an object that has list of objects in database using ef

So I have this problem where I want to add a new FormQuestion to my form object and save it.
But when redirecting to another method my Form object doesn't have this new added FormQuestion.
When you press the add button it redirects you to:
public IActionResult CreateQuestion()
{
int id = int.Parse(HttpContext.Session.GetString("form-id"));
Form form = db.Forms.Find(id);
form.FormQuestions.Add(new FormQuestion());
db.FormQuestions.Add(form.FormQuestions[^1]);
db.Forms.Update(form);
db.SaveChanges();
var asd = db.Forms.Find(id); //a form with added question
return RedirectToAction("CreateForm");
}
But when redircting to "CreateForm":
public IActionResult CreateForm()
{
Form form = new Form();
if (HttpContext.Session.GetString("form-id") != null)
{
int id = int.Parse(HttpContext.Session.GetString("form-id"));
form = db.Forms.Find(id); // a form without new added question
}
else
{
db.Forms.Add(form);
db.SaveChanges();
HttpContext.Session.SetString("form-id", form.ID.ToString());
}
return View(form);
}
My OnModelCreating() is:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Form>().HasMany(form => form.FormQuestions).WithOne();
}
Form class:
public class Form
{
public List<FormQuestion> FormQuestions { get; set; }
public string Heading { get; private set; }
public Form()
{
FormQuestions = new List<FormQuestion>();
}
public Form(string Heading)
{
this.Heading = Heading;
FormQuestions = new List<FormQuestion>();
}
public void AddQuestion(string name, string answer)
{
FormQuestions.Add(new FormQuestion()
{
Name = name,
Answer = answer
});
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ID { get; set; }
}
FormQuestion class:
public class FormQuestion
{
public string Name { get; set; }
public string Answer { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ID { get; set; }
}
I dont know, maybe there are some problems with relationships.
Update
I finally got it working by just manually querying through all of the FormQuestion:
form.FormQuestions = db.FormQuestions.Where(i => i.Form == form).ToList();
I thought ef will do this automaticly.

Microsoft Entity Framework is not saving changes

Im currently trying to implement CRUD functionality with a dbfactory and generics with microsoft EF, but while listing entries is working, making changes to the db is currently not working.
public class AbstractDataModel
{
[Key]
public Guid gid { get; set; }
}
Model
class SalesOrder : AbstractDataModel
{
public int salesOrderID { get; set; }
public int productID { get; set; }
public int customerID { get; set; }
public Guid createdBy { get; set; }
public string dateCreated { get; set; }
public string orderDate { get; set; }
public string orderStatus { get; set; }
public string dateModified { get; set; }
}
A DBCore with some other functionality besides the ones listed here, which are not relevant for the factory
public class DBCore : DbContext
{
public static string connectionString = "myConnectionStringToDb";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
Data Service which calls factory
class SalesOrderService : DBCore
{
public DbSet<SalesOrder> SalesOrders { get; set; }
public OkObjectResult GetAllSalesOrders()
{
DBFactory factory = new DBFactory();
return new OkObjectResult(JsonConvert.SerializeObject(factory.GetAll(SalesOrders)));
}
public OkObjectResult AddSalesOrder(SalesOrder order)
{
order.gid = Guid.NewGuid();
return DBFactory.AddOne(order);
}
public OkObjectResult UpdateSalesOrder(SalesOrder order)
{
return DBFactory.UpdateOne(order);
}
public OkObjectResult DeleteSalesOrder(SalesOrder order)
{
return DBFactory.DeleteOne(order);
}
}
simple CRUD-Factory,
class DBFactory : DBCore
{
public DbSet<UserModel> Users { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
public List<T> GetAll<T>(DbSet<T> dbset) where T : class
{
using (this)
{
return dbset.ToList();
}
}
public static OkObjectResult AddOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Add(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully added");
}
}
public static OkObjectResult UpdateOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Update(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully updated");
}
}
public static OkObjectResult DeleteOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Attach(data);
factory.Remove(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully removed");
}
}
}
Edit: Following the advices i changed the code so it should SaveChanges for the Factory, which also contains the context as a property. But it still doesnt seem to work for all database operations except listing all entries
Editv2: Thanks for the adivces it seems i have solved that problem, but a new one appeared :D
I can now do database operations like deleting entries, but now i cant list the entries anymore because the following error occurs, although the code there didnt really change:
"Executed 'GetAllOrders' (Failed, Id=5fb95793-572a-4545-ac15-76dffaa7a0cf, Duration=74ms)
[2020-10-23T14:33:43.711] System.Private.CoreLib: Exception while executing function: GetAllOrders. Newtonsoft.Json: Self referencing loop detected for property 'Context' with type 'FicoTestApp.Models.SalesOrder'. Path '[0].ChangeTracker'."
try adding
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
to your
startup.cs
it should to the job

SQLite-Net-Extensions GetWithChildren() Method only reaches first Child Layer

currently I am working with the NuGet SQLite-Net-Extensions in Xamarin Forms and I have encountered a problem for which I can't find a solution.
The Problem: When calling GetWithChildren(primaryKey, recursive: true), the returned object only contains the first child layer. An example can be seen in the following.
The database is built up like this:
The equivalent code to this model is provided in the following:
User
namespace DatabaseTest
{
[Table("Users")]
public class User
{
[PrimaryKey,AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Contact> Contacts { get; set; }
}
}
Contact
namespace DatabaseTest
{
[Table("Contacts")]
public class Contact
{
[PrimaryKey,AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Entry> Entries { get; set; }
[ForeignKey(typeof(User))]
public int UserID { get; set; }
[ManyToOne]
public User User { get; set; }
}
}
Entry
namespace DatabaseTest
{
[Table("Entries")]
public class Entry
{
[PrimaryKey,AutoIncrement]
public int Id { get; set; }
public float Amount { get; set; }
[ForeignKey(typeof(Contact))]
public int ContactId { get; set; }
[ManyToOne]
public Contact Contact { get; set; }
}
}
In my App.cs I am creating the database and use the CreateTable() Method for all three classes. For the sake of this example, in MainPage.xaml.cs there is simply a button, which has a ButtonClicked Method.
In the real Application a process could look like this:
User logs in --> Adds Contact --> At some Point User creates Entry to one of his contacts
To accomplish this procedure in my example, the ButtonClicked Method looks like this:
void ButtonClicked(object sender, EventArgs e)
{
try
{
User user = new User()
{
Name = "Test user"
};
Contact contact = new Contact()
{
Name = "First contact"
};
Entry entry1 = new Entry()
{
Amount = 10F
};
Entry entry2 = new Entry()
{
Amount = 20F
};
App.Database.Insert(user);
if (user.Contacts==null)
{
user.Contacts = new List<Contact>();
}
App.Database.Insert(contact);
user.Contacts.Add(contact);
App.Database.UpdateWithChildren(user);
if (contact.Entries==null)
{
contact.Entries = new List<Entry>();
}
App.Database.Insert(entry1);
App.Database.Insert(entry2);
contact.Entries.Add(entry1);
contact.Entries.Add(entry2);
App.Database.UpdateWithChildren(contact);
App.Database.UpdateWithChildren(user);
var test = App.Database.GetWithChildren<User>(user.Id, recursive: true);
var test2 = App.Database.GetAllWithChildren<Contact>();
var test3 = App.Database.GetAllWithChildren<Entry>();
} catch (Exception ex)
{
Debug.Print(ex.Message);
}
}
I have set a breakpoint to the bracket that closes the try to inspect the result. In the End, my user looks like this:
Which is absolutely perfect.
However, when I try to get this user from my database, the result looks like this:
I don't know how to resolve this error and hope anyone can help me with this problem.
Since this post is very long already, I thank everyone who read this far in advance.
After many tries I finally solved my problem on my own.
Solution:
It could not have been any easier. In my User, Contact and Entry Class I provided my OneToMany and ManyToOne attributes with the CascadeOperation attribute. Example:
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Entry> Entries { get; set; }
[ManyToOne(CascadeOperations = CascadeOperation.All)]
public User User { get; set; }
Even though I marked my GetWithChildren() Method as recursive: true, only by applying CascadeOperations it will work properly. More information about SQLite-Net-Extensions and CascadeOperations can be found here:
Source: TwinCoders SQLite-Net-Extensions

C#, Android: Delete Singleton

I am working on a project with a lot of account management going on. Unfortunately, the guy who set all of this up is on vacation and something here needs to be done, but I cant really seem to understand what exactly is going on here ( I am kind of new to this...)
So basically, as far as I understand: When someone logs into our app, a singleton account is created. There are two classes that matter here:
namespace Accounts
{
//Generische und Lazy Singleton-Abstraktion
public abstract class AbstractAccount<T> where T : class
{
// Lazy Instanziierung
private static readonly Lazy<T> _instance = new Lazy<T>(() => CreateSingletonInstance());
public static T Instance
{
get
{
// throw new System.InvalidOperationException("out");
return _instance.Value;
}
}
private static T CreateSingletonInstance()
{
// Konstruktion des Singleton-Objekts
return Activator.CreateInstance(typeof(T), true) as T;
}
}
}
and:
class Account : AbstractAccount<Account>
{
// öffentliche Felder und Methoden
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Description { get; set; }
public List<string>Friendlist { get; set; }
public Bitmap ProfilePicutre { get; set; }
public int Experience { get; set; }
public int gender { get; set; }
public DateTime lastLogin { get; set; }
public DateTime dateCreated { get; set; }
public string Locality { get; set; }
public string Country { get; set; }
public string CountryCode { get; set; }
public int level { get; set; }
public void SetCurrentAccount(tblUsers user, DateTime lastLogin)
{
this.Username = user.getUsername();
this.Email = user.getEmail();
this.Password = user.getPassword();
this.Description = user.getdescription();
this.Experience = user.getexperience();
this.gender = user.getgender();
this.lastLogin = lastLogin;
this.dateCreated = user.getDateCreated();
this.level = CheckLevel(Experience);
}
}
Now here is the issue: When a user is login off and then creating a new account, he or she would still be set up with the properties of the user he just logged out off.
For instance: If he had 1000 xp points, then loggs off and creates a new account, that account would not start at 0 points but at 1000.
I know that his is pretty much (maybe even impossible) for you to handle from another computer but I really need help right here:
private void logoutClick(object sender, EventArgs e)
{
Context mContext = Android.App.Application.Context;
AppPreferences ap = new AppPreferences(mContext);
ap.deletePreferences();
this.FinishAffinity();
//Remove static variables. Just to be sure!
SaveAccountInfo.bpLandScapePicFull = null;
SaveAccountInfo.bpLandScapePicThumb = null;
SaveAccountInfo.bpProfilePicFull = null;
SaveAccountInfo.bpProfilePicThumb = null;
StartActivity(typeof(Activity_AcctCreationLogin));
Finish();
}
If the user was now to logout, the singleton needs to be completely destroyed and set up anew when a nother account is beeing created. I tried "Account.Instance.Dispose()"
but unfortunately, there was no such method as "dispose" after instance.
Is there any chance you guys could help me out a little? That me tremendous! Thanks so much! :)
You can set the value of your instance to a new one.
Create a method in your Account class that does this one upon logout.
_instance = new Lazy<T>(() => CreateSingletonInstance());
You should youse the Singleton pattern with these 2 methods:
public static T GetInstance
{
get
{
if (_instance == null)
_instance = new Lazy<T>(() => CreateSingletonInstance());
return _instance.Value;
}
}
public static void ReleaseInstance // called on logout
{
_instance = null;
}
also, as DavidG pointed out you should add a protected constructor.
Would it be possible for you to implement the IDisposable interface, then write your own dispose method. You could then use this method to clear the data you want cleared. Hope this helps.

EF adding duplicate records into lookup/reference table

I have 3 tables,
1. AttributeTypes (Columns: AttributeId (PK), AttributeName, ..)
2. Location (Columns: locationId (PK), LocationName, ...)
3. LocationAttributeType (Columns: locationId (FK), AttributeId (FK))
Whenever I am trying to insert new location record along with its attribute type from GUI, it should create new record for Table- Location and LocationAttributeType. But EF trying to add new record in Table- AttributeTypes as well, which is just used as reference table and should not add new/duplicate records in it. How can I prevent that?
here is my code,
The model which GUI sends is,
public class LocationDataModel
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Code { get; set; }
[DataMember]
public List<AttributeTypeDataModel> AssignedAttributes = new List<AttributeTypeDataModel>();
}
public class AttributeTypeDataModel
{
protected AttributeTypeDataModel() {}
public AttributeTypeDataModel(int id) { this.Id = id; }
public AttributeTypeDataModel(int id, string name)
: this(id)
{
this.Name = name;
}
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public virtual ICollection<LocationDataModel> Locations { get; set; }
}
The Entities created by EF are,
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<AttributeType>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<AttributeType> AttributeTypes { get; set; }
}
public partial class AttributeType
{
public AttributeType()
{
this.Locations = new List<Location>();
}
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
I have below code to Add these new location to database,
private IEnumerable<TEntity> AddEntities<TModel, TEntity, TIdentityType>
(IEnumerable<TModel> models, Func<TModel, TIdentityType> primaryKey,
IGenericRepository<TEntity, TIdentityType> repository)
{
var results = new List<TEntity>();
foreach (var model in models)
{
var merged = _mapper.Map<TModel, TEntity>(model);
var entity = repository.Upsert(merged);
results.Add(entity);
}
repository.Save();
return results.AsEnumerable();
}
I am using following generic repository to do entity related operations
public TEntity Upsert(TEntity entity)
{
if (Equals(PrimaryKey.Invoke(entity), default(TId)))
{
// New entity
return Context.Set<TEntity>().Add(entity);
}
else
{
// Existing entity
Context.Entry(entity).State = EntityState.Modified;
return entity;
}
}
public void Save()
{
Context.SaveChanges();
}
Whats wrong I am doing here?
The DbSet<T>.Add() method attaches an entire object graph as added. You need to indicate to EF that the 'reference' entity is actually already present. There are two easy ways to do this:
Don't set the navigation property to an object. Instead, just set the corresponding foreign key property to the right value.
You need to ensure that you don't load multiple instances of the same entity into your object context. After creating the context, load the full list of AttributeType entities into the context and create a Dictionary<> to store them. When you want to add an attribute to a Location retrieve the appropriate attribute from the dictionary. Before calling SaveChanges() iterate through the dictionary and mark each AttributeType as unchanged. Something like this:
using (MyContext c = new MyContext())
{
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Fish", AttributeTypeId = 1 });
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Face", AttributeTypeId = 2 });
c.SaveChanges();
}
using (MyContext c = new MyContext())
{
Dictionary<int, AttributeType> dictionary = new Dictionary<int, AttributeType>();
foreach (var t in c.AttributeTypes)
{
dictionary[t.AttributeTypeId] = t;
}
Location l1 = new Location(1, "Location1") { AttributeTypes = { dictionary[1], dictionary[2] } };
Location l2 = new Location(2, "Location2") { AttributeTypes = { dictionary[1] } };
// Because the LocationType is already attached to the context, it doesn't get re-added.
c.Locations.Add(l1);
c.Locations.Add(l2);
c.SaveChanges();
}
In this specific case you are using a many-to-many relationship, with EF automatically handling the intermediate table. This means that you don't actually have the FK properties exposed in the model, and my first suggestion above won't work.
Therefore, you either need to use the second suggestion, which still ought to work, or you need to forgo the automatic handling of the intermediate table and instead create an entity for it. This would allow you to apply the first suggestion. You would have the following model:
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<LocationAttribute>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<LocationAttribute> AttributeTypes { get; set; }
}
public partial class LocationAttribute
{
[ForeignKey("LocationId")]
public Location Location { get; set; }
public int LocationId { get; set; }
public int AttributeTypeId { get; set; }
}
public partial class AttributeType
{
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
}
With this approach you do lose functionality since you can't navigate from a Location to an AttributeType without making an intermediate lookup. If you really want to do that, you need to control the entity state explicitly instead. (Doing that is not so straightforward when you want to use a generic repository, which is why I've focused on this approach instead.)
Thank you all for your suggestions.
I have to get rid of my generic repository here to save my context changes and do it manually as below,
private IEnumerable<int> AddLocationEntities(IEnumerable<LocationDataModel> locations)
{
var results = new List<int>();
foreach (LocationDataModel l in locations)
{
var entity = _mapper.Map<LocationDataModel, Location>(l);//you can map manually also
var AttributeCode = l.AssignedAttributes.FirstOrDefault().AttributeTypeId;
using (MyContext c = new MyContext())
{
var attr = c.AttributeTypes.Where(a => a.Id == AttributeTypeId ).ToList();
entity.AttributeTypes = attr;
c.Locations.Add(entity);
c.SaveChanges();
var locid = entity.Id;
results.Add(locid);
}
}
return results;
}
In the else statement of yourUpsert you should add
context.TEntity.Attach(entity);

Categories

Resources