EF - Conditionally allow saving a property value (which has a backing field) - .net-core

I have a User entity and an EmailRecipient entity with a -one-to-one relationship. Email recipients can also be created without a related User entity.
My EmailRecipient entity takes the Name, Surname and EmailAddress from the User entity if it is attached/included. I have set up the properties to return the backing-field value if it isn't null before it returns the User properties so I can specify an alternative name if I want.
When saving the EmailRecipient, EF looks at the Name, Surname and EmailAddress properties and populates the EmailRecipient table with the values from the User entity instead of using the backing field values. I would like these fields to remain NULL in the database providing I haven't set a value explicitly i.e. recipient.Name = "new name";
My Question: How can I make EF populate selected database fields based on the backing-field value instead of the property values? Or is there a completely different approach to solving this problem?
EmailRecipient Entity:
public class EmailRecipient
{
private string _name;
public virtual string Name
{
get { return User == null ? _name : _name ?? User.Name; }
set { _name = value; }
}
private string _surname;
public virtual string Surname
{
get { return User == null ? _surname : _surname ?? User.Surname; }
set { _surname = value; }
}
private string _emailAddress;
public virtual string EmailAddress
{
get { return User == null ? _emailAddress : _emailAddress ?? User.EmailAddress; }
set { _emailAddress = value; }
}
[Key, ForeignKey(nameof(UserId))]
public User User { get; protected set; }
public long? UserId { get; set; }
}

After some further digging, I think I have found the solution. Carefully reading Microsoft's EF Core backing fields documentation, it seems that we can explicitly ask EF to read and write to the backing field instead of using the property.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EmailRecipient>().Property(x => x.Name)
.UsePropertyAccessMode(PropertyAccessMode.Field);
}
The backing field remains null until a new Name is set i.e. recipient.Name = "new name"; and because of this, I can use the recipient.Name property to return the recipient._name (as stored in the database) or if it is null, the recipient.User.Name

Related

How do you add a new user and claim in a single transaction using ASP.NET Core 2.1 Identity?

I am trying to add a new user and some other associated entities including a claim as one transaction. My classes are basically defined as below. Note that I am using int primary keys on my User class and that it is an identity column (value is provided by the database on insertion).
public class User : IdentityUser<int>
{
// custom props here
}
public class UserClaim : IdentityUserClaim<int>
{
// i actually haven't added anything to this class, it's mainly here so
// i don't have to keep specifying the user's primary key type
}
public class OtherEntity
{
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
// other stuff
}
I then want to add the user etc. to the database something like this:
User user = new User(){ /* set props */ };
OtherEntity other = new OtherEntity()
{
User = user
};
UserClaim claim = new UserClaim()
{
/* this is where the problem is */
UserId = user.Id,
ClaimType = "my-claim-type",
ClaimValue = "my-claim-value"
};
context.AddRange(user, other, claim);
context.SaveChanges();
I can easily link the User to the OtherEntity because I have set up the navigation property so I can just add the User to it and entity framework takes care of the filling in the UserId column. I cannot do this with UserClaim because it doesn't have the navigation property. I could call context.SaveChanges() after adding the User and entity framework would get the User.Id created by the database for me which I could use to set UserId on the UserClaim, but that would mean two transactions.
I have tried adding the navigation property to my definition of UserClaim as follows:
public class UserClaim : IdentityUserClaim<int>
{
[ForeignKey(nameof(UserId))]
public User User { get; set; }
}
But I get following runtime error:
InvalidOperationException: The relationship from 'UserClaim.User' to 'User' with foreign key properties {'UserId' : int} cannot target the primary key {'Id' : int} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.
Is there a way of creating both the user, and the claim in the same transaction?
My question should have been: "How do I add navigation properties between ASP.NET Identity classes?"
If I had looked for the answer to that I would have found the microsoft docs explaining how to do it!
The docs linked above tell you how to add the User.UserClaims navigation property:
public class User : IdentityUser<int>
{
public virtual ICollection<UserClaim> Claims { get; set; }
}
public class DataContext : IdentityDbContext</* Identity classes */>
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>(e =>
{
e.HasMany(u => u.Claims)
.WithOne()
.HasForeignKey(c => c.UserId)
.IsRequired();
});
}
}
But it doesn't show how to make the reverse UserClaim.User navigation property. I worked out that this can be done like this:
public class UserClaim : IdentityUserClaim<int>
{
public virtual User User { get; set; }
}
public class DataContext : IdentityDbContext</* Identity classes */>
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>(e =>
{
e.HasMany(u => u.Claims)
.WithOne(c => c.User) // <- this line is different
.HasForeignKey(c => c.UserId)
.IsRequired();
});
}
}
You can then create a new user and claim at the same time as per my question:
User user = new User(){ /* set props */ };
UserClaim claim = new UserClaim()
{
User = user, // <- this is the bit you can't do without the nav. property
ClaimType = "my-claim-type",
ClaimValue = "my-claim-value"
};
context.AddRange(user, claim);
context.SaveChanges();
I guess it's common sense, though I didn't realise until I inspected the actual SQL hitting the database, but this will still require two trips to the database even though we are only calling .SaveChanges() once! Entity Framework will first save the User and get the ID for the inserted row which it will then use when inserting the UserClaim.
Inserting related data is documented in Entity Framework: https://learn.microsoft.com/pl-pl/ef/core/saving/related-data
And it is also well described in other topics, for example: Entity Framework Foreign Key Inserts with Auto-Id
Every of you entites need to be set correctly for relations (foreign keys) in your entites models (without them, EF don't know what to do) and when you are adding it, you need to start from beginning, so UserClaim must be set from your User entity, for example
var user = new User(){
//properites
OtherEntity = new OtherEntity()
{
Id = 0, /*will be set automatically*/
UserId = 0 /*will be set automatically*/
/* other properites */
};
Claim = new UserClaim(){
Id = 0, /*will be set automatically*/
UserId = 0 /*will be set automatically*/
/* other properites */
}
}
ontext.Add(user);
context.SaveChanges();
You didn't provide all the information about your relations, I've just assumed this from your code.
PS. AddRange has only one parameter.
EDIT:
To clarify my answer, for everything to work, AddRange/Add need to be called with your base entity and relations inside, in tree manner.
But first you need to configure your models, something like that.
public class User : IdentityUser<int>
{
[ForeignKey("UserClaimId")]
public virtual UserClaim Claim {get;set;}
}
public class UserClaim : IdentityUserClaim<int>
{
[ForeignKey("UserId")]
public virtual User User {get;set;}
}
public class OtherEntity
{
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
// other stuff
}
You can also use OnModelCreating to set up entites, as described in documentation: https://learn.microsoft.com/en-us/ef/core/modeling/relationships
Entity Framework right now don't know anything about relations in Database, data types etc.
Updated code example
Edit, don't have enough reputation to add comment to your answer
Well, EF still needs to track entities. I would need to write and run some examples to check if there is some way to improve trips to database. Maybe someone could tell you more about mechanism that is behind adding related-data and if there is a way to improve it.
There are some libraries that helps with improving performance of saving changes to database, for example: https://entityframework-extensions.net
You could try it.

ApplicationUser has a list of ApplicationUser

I have built a new Web Application that uses the template Visual Studio provides and included MVC and Web API. The default authorization mechanism is Identity and the database interaction is done using Entity Framework with Code-first method of creating the database.
I have three requirements:
A user can have a list of Children objects
I do not want to use a "relationship" object
All users already exist on the AspNetUsers table, because they all need to be able to login, so I do not want another table to maintain user data
In theory, multiple parents could have reference to multiple children, but for this example, we will just consider it a one-to-many relationship.
In my application, I need to have an ApplicationUser have a list of ChildUsers as a collection of ApplicationUser such as shown below.
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string ShirtSize { get; set; }
public ICollection<ApplicationUser> Children { get; set; }
}
I want these users to be accessible as shown above (a collection of ApplicationUser), not a collection of Relationship object that ties them together such as:
public class Relationship
{
public String ParentId { get;set; }
public String ChildId { get;set; }
}
Can a new table be created and exist on the database without having a code-first model for it to know how to create a relationship table?
What are available solutions to this problem?
After some research, and experimentation, I have found bits and pieces of guidance to arrive at a solution that works.
In order for an intermediate table to be created to maintain the relationship, the ApplicationDbContext OnModelCreating function needs to know what it should look like. I have told it to create a new table that is not bound to an object by using the modelBuilder shown in the code below. Unfortunately, I do not have the links to the articles that guided me to this.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base( "DefaultConnection", throwIfV1Schema: false )
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
base.OnModelCreating( modelBuilder );
modelBuilder.Entity<ApplicationUser>()
.HasMany( p => p.ChildUsers )
.WithMany()
.Map( m =>
{
m.MapLeftKey( "Father_Id" );
m.MapRightKey( "Son_Id" );
m.ToTable( "father_son_relation" );
} );
}
}
Additionally, when you need to add Children to the parent ApplicationUser, you will need to do some tweaking as you are about to insert so that it updates the database correctly. I definitely want the UserManager to do the creation of the user for me, but that means that when I go to add the user to my list of Children with the code below, it tries to add it again and throws an exception because it already exists.
var result = await UserManager.CreateAsync( user, model.Password );
var myUserId = User.Identity.GetUserId();
var users = AppDbContext.Users.Where( u => u.Id == myUserId ).Include( u => u.ChildUsers );
var u2 = users.First();
u2.ChildUsers.Add( user );
await AppDbContext.SaveChangesAsync();
After finding this question, I researched the EntityStates and found that adding the following line before calling SaveChanges resolved the exception and it no longer attempts to add it again.
AppDbContext.Entry( user ).State = EntityState.Unchanged;
TADA!!! Now to select them from the database using EF, you can then use the following code:
AppDbContext.Users.Where( u => u.Id == myUserId ).Include( u => u.Children ).First();
Since I am only getting one level of Children this will work ok, after that you risk circular references.
Comments and ideas to improve the code are welcome.

Orchard CMS 1.x [Document Storage] - Mixing Record Backed & Record Less Properties

I am following Bertrand Le Roy's guide to migrate OrchardCMS to Document Storage (1.x branch pre release)
http://weblogs.asp.net/bleroy/archive/2013/11/04/the-shift-how-orchard-painlessly-shifted-to-document-storage-and-how-it-ll-affect-you.aspx
Here are some extracts from my sample code in an attempt to mix record backed and recordless properties in a ContentPart.
Migration
public int Create() {
SchemaBuilder.CreateTable("CustomerPartRecord",
table => table
.ContentPartRecord()
.Column<string>("Name")
);
ContentDefinitionManager.AlterPartDefinition("CustomerPart", builder => builder
.Attachable());
ContentDefinitionManager.AlterTypeDefinition("Customer",
type => type
.WithPart("CommonPart")
.WithPart("Title")
.WithPart("CustomerPart")
.Creatable());
return 1;
}
ContentPartRecord
public class CustomerPartRecord : ContentPartRecord {
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
}
ContentPart
public class CustomerPart : ContentPart<CustomerPartRecord>
{
public string Name
{
get { return Record.Name; } // tried "return Retrieve(x=>x.Name);"
set { Record.Name = value; } // tried "return Store(x=>x.Name, value);"
}
public string Phone
{
get { return this.Retrieve(x => x.Phone); } //Recordless property
set { this.Store(x => x.Phone, value); }
}
public string Email
{
get { return this.Retrieve(x => x.Email); } //Recordless property
set { this.Store(x => x.Email, value); }
}
}
Throws the following error when trying to add a new record
could not insert: [Customers.Models.CustomerPartRecord#28][SQL: INSERT INTO Teknorix_Customers_CustomerPartRecord (Name, Phone, Email, Id) VALUES (?, ?, ?, ?)] ---> System.Data.SqlServerCe.SqlCeException: The column name is not valid. [ Node name (if any) = ,Column name = Phone ]
This works absolutely fine if i add a phone & email column in the DB. but then it writes the data in 2 places. in the xml it will only insert the phone and email fields. and in the table it will insert the entire record.
I am aware if i use Retrieve() in place of this.Retrieve() then it will store the entire record in both places, etc etc...
But Im interested in having just some fields ONLY in the XML infoset and some fields ONLY in the record table. What am I doing wrong here?
If your part has a record, you can still target properties at the infoset only, without requiring the existence of a record property. To do this, get the infoset using this.As<InfosetPart>(). Then, use of of the extension methods from InfosetHelper, such as infosetPart.Retrieve<string>("Phone") and infosetPart.Store<string>("Phone", value).

Edit model with virtual field

I have a Shop model which contains several fields. One of which is a virtual User one. Whenever I try to edit one entry I get an error saying that User field is required.
public class Shop
{
//..
public virtual ApplicationUser User { get; set; }
//..
}
My workaround is this:
shop.User = shop.User; //re-set the value
shop.Active = true;
db.Entry(restaurant).State = EntityState.Modified;
db.SaveChanges();
And I have to do this for all the fields. Is this the standard approach for this or is there a better way?
Change your model to this:
public class Shop
{
//..
public int UserId {get; set; }
public virtual ApplicationUser User { get; set; }
//..
}
Entity Framework will automatically detect that UserId is the foreign key for object User. You had this problem because User is virtual (lazy loaded). When changing the model without accessing or setting this property EF thinks it's empty (I assume). The foreign key UserId is not virtual, and will be fetched together with the other properties of model Shop, so you don't have to re-set the value when saving the model.
To set a new user, you now have to do for example:
myShop.UserId = 1; // instead of setting myShop.User
For more information, see this article: http://msdn.microsoft.com/en-us/data/jj713564.aspx

Why entity framework does not delete record when setting navigation property to null

I am new with EF Code First and have some troubles with it.
Here my Model
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public string Id { get; set; }
public string Street { get; set; }
public string Nr { get; set; }
}
What I want is, if I write the following code:
user.Address = null;
the related Address (record in database) should be deleted, but it is not! It removes only the foreign key in Users table. Also when I assign a new Address:
user.Address = new Address() { ... };
it is created a second record in database, why?
I want:
if assign null to user.Address the database record should be deleted
if assign a new Address object to user.Address it should replace the existing records data with the new one
How can I solve this?
If you need to delete an entity you need to mark it as deleted. Setting a navigation property to null will not delete the related entity. Note that there may be many navigation properties (different entities) pointing to the related entity and they all would be affected in the scenario you are describing.
Because you create a new Address a new entity is created. Then you set your navigation property to a newly created property this breaks the old relationship and creates a new one. Since the entity you created is not in the database it will be added. You also did not delete the old entity so it was not deleted from the database. If you just want to change property values of an entity just set properties to new values and invoke SaveChanges. This should update data in the database.

Resources