Entity Framework cancel added/deleted items at EntityCollection - c#

I have a class created by the entity framework (Document). It has a collection of another class (FileInfo).
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmEntityTypeAttribute(NamespaceName="DocumentManagerModel", Name="DocumentContainer")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class DocumentContainer : EntityObject
{
...
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[XmlIgnoreAttribute()]
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("DocumentManagerModel", "FK__FileInfo__00000000000018FA", "FileInfo")]
public EntityCollection<FileInfo> FileInfoes
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection<FileInfo>("DocumentManagerModel.FK__FileInfo__00000000000018FA", "FileInfo");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection<FileInfo>("DocumentManagerModel.FK__FileInfo__00000000000018FA", "FileInfo", value);
}
}
}
...
}
The user now can add or delete a item to the collection (FileInfos) through the gui. The gui form has a cancel button. How can I manage to reset all the changes at the collection?

As long as you didn't call dbcontext.SaveChanges() nothing is saved into the database. When they click the cancel button I'd just dispose the dbcontext and create a new one if I were you.
Calling
dbcontext.Entry(myEntity).CurrentValues.SetValues(dbcontext.Entry(myEntity).OriginalValues);
dbcontext.Entry(myEntity).State = EntityState.UnChanged;
should also do the trick.

Related

Swashbuckle XML Comments with Property Hiding

Is there a way how to make Swashbuckle take the XML Comments for properties which hide those in the base class?
class Base {
/// <summary>
/// This comment shows in swagger UI.
/// </summary>
public int Value { get; set; }
}
class Model: Base {
/// <summary>
/// This comment is not visible in swagger UI.
/// </summary>
public new int Value { get; set; }
}
Is there any workaround, e.g. using a kind of SchemaFilter?
UPDATED
The above simple example works as expected.
Our problem is probably caused by a wrong configuration of the IncludeXmlComments option as our models come from multiple assemblies.
I found a related discussion on GitHub

Updating records via api

Ive just started upgrading an old .net framework platform to .net Core 5 using Razor Pages
The first issue i ran into is on updating records.
We have a frontend and a backend form to edit users. On frontend only few fields are visible and on backend we have more fields
The model could look like this
public class User
{
public int ID {get;set;}
public string Name {get;set;}
public int UserType {get;set;}
public DateTime TStamp {get;set;}
}
On frontend the user can update the name, and i don't want to expose the value of UserType(or TStamp) using a hidden field.
But this means that the Usertype and TStamp always are reset
I have read the the best way is to send the model to the server and then update(and validate) the record serverside like :
Model recordToUpdate = GetRecordFromDB(id)
recordToUpdate.Name = postedRecord.Name;
UpdateRecord(recordToUpdate);
return recordToUpdate
Is there anyway else to accomplish update only few fields ?
08-02-2021 11:57
I have found this script which iterates through a model and a viewmodel and then transfer data.
https://www.codeproject.com/Tips/5163606/Generic-MVVM-Data-Exchange-between-Model-and-ViewM
public enum MVVMDirection { FROM, TO };
/// <summary>
/// ViewModel base class
/// </summary>
public class VMBase
{
/// <summary>
/// Move the data from the model to the viewmodel, using reflection.
/// Property names in both objects MUST be the same (both name and type)
/// </summary>
/// <typeparam name="TModel">The model's type</typeparam>
/// <param name="model">The model object the data will be moved from</param>
public void UpdateFromModel<TModel>(TModel model)
{
this.Update<TModel>(model, MVVMDirection.FROM);
}
/// <summary>
/// Move the data from the viewmodel to the model, using reflection.
/// Property names in both objects MUST be the same (both name and type)
/// </summary>
/// <typeparam name="TModel">The model's type</typeparam>
/// <param name="model">The model object the data will be moved from</param>
public void UpdateToModel<TModel>(TModel model)
{
this.Update<TModel>(model, MVVMDirection.TO);
}
/// <summary>
/// Update to or from the model based on the specified direction. Property names in both
/// objects MUST be the same (both name and type), but properties used just for the view
/// model aren't affected/used.
/// </summary>
/// <typeparam name="TModel">The model's type</typeparam>
/// <param name="model">The model object the data will be moved to/from</param>
/// <param name="direction">The direction in which the update will be performed</param>
public void Update<TModel>(TModel model, MVVMDirection direction)
{
PropertyInfo[] mProperties = model.GetType().GetProperties();
PropertyInfo[] vmProperties = this.GetType().GetProperties();
foreach (PropertyInfo mProperty in mProperties)
{
PropertyInfo vmProperty = this.GetType().GetProperty(mProperty.Name);
if (vmProperty != null)
{
if (vmProperty.PropertyType.Equals(mProperty.PropertyType))
{
if (direction == MVVMDirection.FROM)
{
vmProperty.SetValue(this, mProperty.GetValue(model));
}
else
{
vmProperty.SetValue(model, mProperty.GetValue(this));
}
}
}
}
}
If you are using EF Core, you can tell it which properties have to be updated in this way:
var user = new User{ ID = id, Name = postedRecord.Name};
_dbContext.Employee.Attach(user);
_dbContext.Entry(user).Property(x => x.Name).IsModified = true;
_dbContext.SaveChanges();

EF mapping: unwanted column with a 1 at the end of the name

I've got a class CurrentPage which has a Page property, and it's an enum (of type int):
namespace App.Model.Application
{
using System.Runtime.Serialization;
/// <summary>
/// Represents user the current page of a user in the application
/// </summary>
[DataContract]
public class CurrentPage
{
/// <summary>
/// Gets or sets the unique identifier for this current page.
/// </summary>
[DataMember]
public int Id { get; set; }
/// <summary>
/// Gets or sets the description
/// </summary>
[DataMember]
public string Description { get; set; }
/// <summary>
/// Gets or sets the identifier of the current page to which the user has navigated.
/// </summary>
[DataMember]
public Page Page { get; set; }
/// <summary>
/// Gets or sets the current page as an integer; this is to support Entity Framework limitations regarding enumerations.
/// </summary>
public int PageId
{
get
{
return (int)this.Page;
}
set
{
this.Page = (Page)value;
}
}
}
}
It's mapped like this:
this.Property(cp => cp.Page).IsRequired();
If I try to run Add-Migration in VisualStudio, I end up getting this in my migration code
RenameColumn(table: "dbo.CurrentPage", name: "Page1", newName: "Page");
I don't understand where this 'Page1' name is coming from. There's no Page1 column in the database.
Any ideas? Could it be creating the Page1 column because it somehow thinks the Page column that's already there isn't suitable for being mapped to that property?
Not 100% sure what the cause of this was, but I deleted the PageId property and removed all existing migrations I had. Then I restarted Visual Studio (fwiw). The ran an update-database -script, and finally added a new migration (Add-Migration MyNewMigration) and it all started working again. Not a very satisfying answer I know, but it is working now. I also got a fresh copy of the database, untouched by my model changes before doing all that.

Loading large amount of data in session after logging in

I have some data I need to store somewhere after a user logs in, what I need are:
1. A list of customers
2. US States list
for now that's all. Now Customer's list can continue to grow constantly and even after it has been loaded, obviously US States don't.
Currently what I do in my login.aspx page after the user logs in and everything is validated then I go to my SQL database and load all customers and US states into a Session variable. Right now this works although it does take a little bit of time but since there's only about 3 users at any time testing the site it doesn't affect. But as I understand this could be a problem when the company of about 50+ users start using it or even customers aswell.. So what would be the best way to work this and why? Thanks in advance.
ps. if this is not a valid question, please let me know and I'll take it down, I just didn't know where to ask this and get some useful feedback.
Andres,
Is it possible to (instead of using session) to cache the data. This can be done in multiple ways through various caching techniques (IE System.Web.Caching, MemoryCahce).
As your users grow \ shrink you can modify the DB and the cached instance at the same time. When the user requests the list of users (states) the cached list is evaluated. If the cached list isnt set then you re-build the cache list.
You could do something like. (Basic example)
public class UserCache : IEnumerable<User>
{
/// <summary>
/// const cache string
/// </summary>
const string userCacheString = "_userCacheList";
/// <summary>
/// current list of users
/// </summary>
public static UserCache Current
{
get
{
if (HttpContext.Current == null)
throw new Exception("NO CONTEXT");
var userList = HttpContext.Current.Cache[userCacheString] as UserCache;
if (userList == null)
{
userList = new UserCache();
HttpContext.Current.Cache[userCacheString] = new UserCache();
}
return userList;
}
}
/// <summary>
/// default constructor
/// </summary>
public UserCache()
{
}
/// <summary>
/// the list of users
/// </summary>
List<User> users;
/// <summary>
/// adds a user
/// </summary>
/// <param name="user"></param>
public void Add(User user)
{
if (this.Contains(user))
return;
this.users.Add(user);
}
/// <summary>
/// removes a user
/// </summary>
/// <param name="user"></param>
public void Remove(User user)
{
if (this.Contains(user))
return;
this.users.Remove(user);
}
/// <summary>
/// clears a user
/// </summary>
public void Clear()
{
this.users = null;
}
/// <summary>
/// fills the users from the database
/// </summary>
void fillUsers()
{
this.users = new List<User>();
//TODO: Get from DB
}
/// <summary>
/// gets the enumerator
/// </summary>
/// <returns></returns>
public IEnumerator<User> GetEnumerator()
{
if (this.users == null)
fillUsers();
foreach (var user in users)
yield return user;
}
/// <summary>
/// gets the enumerator
/// </summary>
/// <returns></returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public class User
{
public string UserName { get; set; }
public Guid UserID { get; set; }
public string Email { get; set; }
}
From here you user management (where you add \ remove users) can call the UserCache to modify its collection accordinly. Such as (psudo code)
public class UserManager
{
public void Register(string userName, string email)
{
//TODO: Register in DB.
UserCache.Current.Add(new User
{
UserID = Guid.NewGuid(),
Email = email,
UserName = userName
});
}
}
From here you can always call UserCache.Current to get the current user list.
Just a thought.
EDIT: Response to Simon Halsey comment.
In the example I did inherit from the IEnumerable<> interface and not the List<> interface. This was a personal preference and to support "how" the class was defined. Now before I explain, this is not the only way but just a conceptual way of achieving the result.
In the example the method Clear() clears the inner list by setting the inner list user to null. In the IEnumerable<> implentation GetEnumerator() method the first check is if the inner list is null. If the inner list is null then the fillUsers() method is called to retrieve all users from the database.
If this example inherited from List<> then the Clear() method of List<> would be called and clears the list (removing all items) however the list is not null. Therefore enumerating the list after the Clear() method has been called will result in no users. Now this could be re-written using a List<> implentation as follows. Where the only thing you will have to do is override the Clear() method and the constructor to load the users. Such as.
public class UserListCache : List<User>
{
/// <summary>
/// const cache string
/// </summary>
const string userCacheString = "_userCacheList";
/// <summary>
/// current list of users
/// </summary>
public static UserListCache Current
{
get
{
if (HttpContext.Current == null)
throw new Exception("NO CONTEXT");
var userList = HttpContext.Current.Cache[userCacheString] as UserListCache;
if (userList == null)
{
userList = new UserListCache();
HttpContext.Current.Cache[userCacheString] = new UserListCache();
}
return userList;
}
}
/// <summary>
/// default constructor
/// </summary>
public UserListCache()
{
this.fillUsers();
}
/// <summary>
/// clear the list
/// </summary>
public new void Clear()
{
base.Clear();
this.fillUsers();
}
/// <summary>
/// fills the users from the database
/// </summary>
void fillUsers()
{
//TODO: Get from DB
}
}
Now neither method is better than the other (and the solution may not be adequate).

How can I write a method that returns all records from a Matisse table in a list structure?

Very new to using Matisse so I don't know if I'm just flat out doing this wrong or something. I have a table containing usernames and passwords for the system, along with the following method...
public List<Users> FindUsers()
{
List<Users> users = new List<Users>();
//Users obj;
executeCmd("SELECT * FROM Users");
while (Reader.Read())
{
Users obj = new Users(db, 0x10ff);
users.Add(obj);
}
reader.Close();
return users;
}
Which I had hoped would simply pull all records from the table, drop them in a list and return that list. The problem however lies with this line of code here.
Users obj = new Users(db, 0x10ff);
I can only seem to pull one record, entering '0x10ff' as a parameter(the records OID) will return that record, which works with any record I try. Leaving it blank(i.e. '(db)' ) throws a Matisse exception(InvalidOperation) and entering anything else other than a specific records OID returns an ObjectNotFound exception. Am I just doing something totally stupid here? What am I missing?
Thanks
EDIT:
db refers to the database connection, shown below, DBAcess being a class with connectivity & query functionality.
private MtDatabase db;
public DBAccess()
{
db = new MtDatabase(Properties.Settings.Default.Host, Properties.Settings.Default.Database);
db.Open();
}
And as for the constructers, I left them as the default ones when I imported the classes from Matisse
// Generated constructor, do not modify
/// <summary>
/// The factory constructor
/// </summary>
/// <param name="db">a database</param>
/// <param name="mtOid">an existing object ID in the db</param>
public Users(MtDatabase db, int mtOid) :
base(db, mtOid) {
}
// Generated constructor, do not modify
/// <summary>
/// Cascaded constructor, used by subclasses to create a new object in the database
/// </summary>
/// <param name="clsObj">the class descriptor of the class to instantiate</param>
protected Users(MtClass clsObj) :
base(clsObj) {
}
#endregion
// GEN_END: Matisse Generated Code - Do not modify
// Generated constructor
/// <summary>
/// Default constructor provided as an example. NOTE: You may modify or delete this constructor
/// </summary>
/// <param name="db">a database</param>
public Users(MtDatabase db) :
base(GetClass(db)) {
}
Have now sorted this out, turns out I was just doing it wrong.
Instead of trying to retrieve the values with the reader, if you instead return the object and then use that to create a new User containing the values of said object, that works. Not sure if that really explains it all that well so here is the updated, working code.
public List<Users> FindUsers()
{
List<Users> users = new List<Users>();
executeCmd("SELECT REF(Users) FROM Users c");
while (Reader.Read())
{
MtObject obj = Reader.GetObject(0);
users.Add(new Users(db, obj.MtOid));
}
Reader.Close();
return users;
}

Categories