SaveChanges doesn't work for TPH entities - entity-framework

I use Entity framework with Generate T-SQL Via T4 (TPH).xaml (VS) and SSDLToSQL10.tt (VS) templates.
I have a table
TABLE [dbo].[Users](
[UserId] [int] IDENTITY(1,1) NOT NULL,
[UserName] [nvarchar](100) NOT NULL,
[Password] [nvarchar](50) NOT NULL,
[IsPaid] [bit] NOT NULL
Since I have 2 user types, field IsPaid is the discriminator. I created TPH in my model. Classes generated via .tt are
public abstract partial class User
{
public User()
{
}
public int UserId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public partial class RegularUser : User
{
}
public partial class PaidUser : User
{
}
public Container()
: base("name=Container")
{
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<User> Users { get; set; }
Let's say I have Regular user with id 3. I create a new paid user u with the same data and try to save it.
using (var entities = new Container())
{
entities.Entry(u).State = u.UserId == 0 ? EntityState.Added : EntityState.Modified;
entities.SaveChanges();
}
Nothing happens. And I can see from the profiler that the query doesn't use IsPaid column at all. Can anyone help?

Are you trying to change regular user with Id = 3 to paid user with Id = 3? That is not possible without executing direct SQL update. From perspective of EF the entity type is permanent. It is same like when you work with objects - you cannot "convert" regular user to paid user without creating whole new instance which will have different memory allocation (= different Id).
If your business logic expects that regular user can become paid user than EF inheritance is not a solution because it cannot do that. The only way to "change" regular user to paid user is creating whole new instance of paid user, inserting it to database and deleting old instance of regular user (sure you must also fix all relations from user to other entities).

Related

Why Entity Framework Core resets filled navigation property to null during AddRange only for first item in range?

I'm using the Asp Net Identity library and I've customized IdentityUser to have a custom navigation property called CompanyRole of type ApplicationRole here is rough structure bellow
public class ApplicationUser: IdentityUser
{
...
public ApplicationRole CompanyRole { get; set }
}
public class ApplicationRole : IdentityRole
{
public string Id{ get; set; }
public string Name { get; set; }
}
Fragment of ApplicationUser model configuration
builder
.HasOne(x => x.CompanyRole)
.WithOne()
.HasForeignKey<ApplicationUser>(au => au.CompanyRoleId)
.OnDelete(DeleteBehavior.Restrict);
So when I try to add multiple users with existing roles like this
var viewerRole = await _rolesService.GetViewerRole() // it queries role from dbcontex behind the scene, so it should be tracked
var usersToAdd = emails.Select(email => new ApplicationUser
{
FirstName = request.Name,
Email = email,
CompanyRole = viewerRole
}
)
_dbContext.Set<ApplicationUser>().AddRange(usersToAdd)
_dbContext.SaveChanges();
It complaints that cannot insert NULL in CompanyRoleId column, since it's a FK constraint.
The reason of this exception is that first user in a range gets CompanyRole as null, whereas others users are good. Why it's happening, since viewer role should have been tracked ?
I've tryed to play with Entities states, such as Added and Attach entity again - no luck
I've expected that all users are created with reference to existing ApplicationRole
BTW the workaround that worked, was if I split users adding one by one with slight changes and detach those immediately then it works, the drawback is that query per user...inefficient
var result = _dbContext.Set<ApplicationUser>().Add(user);
await _dbContext.SaveChangesAsync();
_dbContext.Entry(user).State = EntityState.Detached;
The issue is that you are defining the relationship between user and company role as One to One. This means any 1 company role can be assigned to only one user. So as it tries to associate the role to each user, it would de-associate it from the previous.
What it looks like you want is a Many-to-one, many users can hold the same role. Adjust your mapping to:
builder
.HasOne(x => x.CompanyRole)
.Withmany()
.HasForeignKey<ApplicationUser>(au => au.CompanyRoleId)
.OnDelete(DeleteBehavior.Restrict);
... and you should be sorted.

How to Find a record in Entity Framework without a Primary Key

I've got an entity:
public class Account
{
public int AccountId { get; set; }
public string Mnemonic { get; set; }
public decimal NetAssetValue { get; set; }
}
On this entity I have a primary key (AccountId) and an alternate unique index on the mnemonic.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Account
modelBuilder.Entity<Account>()
.HasKey(a => a.AccountId);
modelBuilder.Entity<Account>()
.HasIndex(a => a.Mnemonic)
.IsUnique();
}
When I store my test data in an XML file, I have no knowledge of the value assigned to the primary key, so I need to find this record by the Mnemonic if I want to use it.
I know I can use LINQ:
var accountId = (from a in account
where mnemonic = "Account1"
select AccountId).First();
But will this use the index or iterate over the entire collection. I could have thousands of accounts and don't want do be executing a table scan each time I want to find an account when I'm loading from my external files.
Provided account is the DbSet<Account> from your DbContext or an IQueryable<Account> derived from he same, it will query the database using the index. if you want to emulate Find (where the locally tracked entities are checked for the entity prior to querying the database) you can first check if dbContext.Set<Account>().Local contains the entity prior to querying the database.

ASP.NET MVC 4 error updating entity framework models with related entities

I feel like this should be a pretty common thing to do. I have a model with a related object on it. Let's say it's a User and a user has one Role.
public class User
{
public int Id { get; set; }
public virtual Role Role { get; set; }
/* other stuff that saves fine */
}
public class Role
{
public int Id {get;set;}
public string Name { get;set;}
}
So if I save a new user, or if I edit a user (but don't change his Role), I have no issues. If I have a user without a role, and add a role to him, again no problem (though I manually lookup the role and assign it). If I try and change a role, I get a modelstate error on the Role property that the ID is part of the object's key and can't be changed. So how do folks go about making updates like this? Whitelist the simple values and then manually update the Role?
My controller code in question is here:
[HttpPost]
public ActionResult Save(int id, FormCollection form)
{
var user = data.Users.FirstOrDefault(d=> d.Id == id);
if (user != null)
{
TryUpdateModel(user, form.ToValueProvider());
if (!ModelState.IsValid)
{
var messages = ModelState.Values.Where(m => m.Errors.Count() > 0).SelectMany(m=>m.Errors).Select(e => e.ErrorMessage);
if (Request.IsAjaxRequest())
return Json(new { message = "Error!", errors = messages });
return RedirectToAction("index"); // TODO: more robust Flash messaging
}
updateDependencies(user);
/* negotiate response */
}
}
I'll probably just do it manually for now, but it seems like a scenario that I would have expected to work out of the box, at least to some degree.
Your User model should have a foreign key:
public int? RoleId { get; set; }
public virtual Role Role { get; set; }
You can assign a Role.Id to this value, or make it null when the user does not have a role.
I'm also not sure if your Save function is correct. I'm always using this pattern (not sure if it is correct either...), but of course it depends on the data you post to the server:
[HttpPost]
public ActionResult Save(User model)
{
if (ModelState.IsValid)
{
// Save logic here, for updating an existing entry it is something like:
context.Entry(model).State = EntityState.Modified;
context.SaveChanges();
return View("Success");
}
return View("Edit", model);
}

Entity framework code first delete entities without parent

I have two entities Users and Phone. Relationship is one to one. Mapping is
public class User
{
public virtual Phone Phone { get; set;}
public virtual int PhoneId { get; set;}
}
public class Phone
{
public virtual string Number { get; set;}
}
My mapping for User is:
HasRequired(x => x.Phone).WithMany().HasForeignKey(x => x.PhoneId)
I have user with assigned phone.
f.e.
Phone oldPhone = new Phone();
Phone newPhone = new Phone();
User user = new User();
user.Phone = old;
///Save user with phone.
user.Phone = newPhone;
///Save user with phone.
Now I have user with assigned phone - newPhone and not assigned oldPhone row in the DB.
How can I setup mapping to delete entity without parent(oldPhone).
EDITED.
I have changed mapping according this article to
HasRequired(x => x.Phone).WithOptional();
For save I use this method:
public override void Save(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
if (entity.Id > 0)
{
DbContext.Set<TEntity>().Attach(entity);
DbContext.Entry(entity).State = System.Data.EntityState.Modified;
}
else
{
DbContext.Set<TEntity>().Add(entity);
}
}
When I attached new entity old ones doesn`t deleted from DB, so in my example I have two entities in DB oldPhone and newPhone.
I don't know a way to do this through mapping in EF. For one of my projects I have made a Parent attribute, which I could check on Save. I think you could use it here to. I have made a blog post on this with code; extending entity framework 4 with parentvalidator.
It would require to add the User property to the Phone entity.
[Parent]
public virtual User User { get; set;}

EF 4 Code First - Combine Views and Tables

I researched this question for days and cannot seem to find an option I feel good about; however, here is a link to a very similar question:
Add Calculated field to Model
Ultimately, I have the same question, but I am hoping for a better solution.
Consider the following DB Tables:
CREATE TABLE [Contact](
[ContactID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[ContactName] [varchar](80) NOT NULL,
[Email] [varchar](80) NOT NULL,
[Title] [varchar](120) NOT NULL,
[Address1] [varchar](80) NOT NULL,
[Address2] [varchar](80) NOT NULL,
[City] [varchar](80) NOT NULL,
[State_Province] [varchar](50) NOT NULL,
[ZIP_PostalCode] [varchar](30) NOT NULL,
[Country] [varchar](50) NOT NULL,
[OfficePhone] [varchar](30) NOT NULL,
[MobilePhone] [varchar](30) NOT NULL)
CREATE TABLE [Blog](
[BlogID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[BlogName] [varchar](80) NOT NULL,
[CreatedByID] [int] NOT NULL, -- FK to ContactTable
[ModifiedByID] [int] NOT NULL -- FK to ContactTable
)
CREATE TABLE [Post](
[PostID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[BlogID] [int] NOT NULL, -- FK to BlogTable
[Entry] [varchar](8000) NOT NULL,
[CreatedByID] [int] NOT NULL, -- FK to ContactTable
[ModifiedByID] [int] NOT NULL -- FK to ContactTable
)
I now would like to use views for loading "common" lookup/calculated info. Every time we display a post on the site, we want to know the name of the person who created the post and who last modified it. These are two fields that are stored in separate tables from the post table. I could easily use the following syntax (assuming Lazy/eager loading was applied and CreatedBy was a property, of type Contact, based on CreatedByID): currentPost.CreatedBy.Name;
The problem with that approach is the number of Db calls and also the large record retrieved for contact, but we are only using Name 99% in this situation. I realize the DB schema above is tiny, but this is just a simplified example and the real contact table has about 50 fields.
To manage this type of situation in the past (prior to using EF), I have typically built out "detail" views for the tables I will use. The "detail" views contain common lookup/calculated fields so that it only takes 1 call to the DB to efficiently get all the info I need (NOTE: We also use indexing on our SQL views to make this extremely efficient for reading) Here is a list of views that I will commonly use (as they will contain "look up" fields from related tables):
ALTER VIEW [icoprod].[BlogDetail]
AS
SELECT B.[BlogID],
B.[BlogName],
B.[BlogDescription],
B.[CreatedByID],
B.[ModifiedByID],
CREATEDBY.[ContactName] AS CreatedByName,
MODIFIEDBY.[ContactName] AS ModifiedByName,
(SELECT COUNT(*) FROM Post P WHERE P.BlogID = B.BlogID) AS PostCount
FROM Blog AS B
JOIN Contact AS CREATEDBY ON B.CreatedByID = CREATEDBY.ContactID
JOIN Contact AS MODIFIEDBY ON B.ModifiedByID = MODIFIEDBY.ContactID
ALTER VIEW [icoprod].[PostDetail]
AS
SELECT P.[PostID],
P.[BlogID],
P.[Entry],
P.[CreatedByID],
P.[ModifiedByID],
CREATEDBY.[ContactName] AS CreatedByName,
MODIFIEDBY.[ContactName] AS ModifiedByName,
B.Name AS BlogName
FROM Post AS P
JOIN Contact AS CREATEDBY ON P.CreatedByID = CREATEDBY.ContactID
JOIN Contact AS MODIFIEDBY ON P.ModifiedByID = MODIFIEDBY.ContactID
JOIN Blog AS B ON B.BlogID = P.BlogID
Here is an overview of my "POCO" objects:
public class Blog
{
public int ID { get; set; }
public string Name { get; set; }
public int CreatedByID { get; set; }
public DateTime ModifiedByID { get; set; }
}
public class Post
{
public int ID { get; set; }
public string Name { get; set; }
public int CreatedByID { get; set; }
public DateTime ModifiedByID { get; set; }
}
public class Contact
{
public int ID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Title { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string MobilePhone { get; set; }
}
public class BlogDetails : Blog
{
public string CreatedByName { get; set; }
public string ModifiedByName { get; set; }
public int PostsCount { get; set; }
}
public class PostDetails : Post
{
public string CreatedByName { get; set; }
public string ModifiedByName { get; set; }
public string BlogName { get; set; }
}
The reason I like this approach is that it allows me to retrieve information from the database based on tables or views AND if I load a view, the view contains all the "table" information which would allow me to load from a view but save to a table. IMO, this gives me the best of both worlds.
I have used this approach in the past, but typically, I just loaded information from the DB using datarows or info from stored procs or even used subsonic activerecord pattern and mapped fields after loading from the DB. I am really hoping I can do something in EF that lets me load these objects without creating another layer of abstraction.
Here is what I have tried to use for configuration (using Fluent API and code-first EF):
public class PostConfiguration : EntityTypeConfiguration<Post>
{
public PostConfiguration()
: base()
{
HasKey(obj => obj.ID);
Property(obj => obj.ID).
HasColumnName("PostID").
HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).
IsRequired();
Map(m =>
{
m.ToTable("Post");
});
}
}
public class BlogConfiguration : EntityTypeConfiguration<Blog>
{
public BlogConfiguration()
: base()
{
HasKey(obj => obj.ID);
Property(obj => obj.ID).
HasColumnName("BlogID").
HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).
IsRequired();
Map(m =>
{
m.ToTable("Blog");
});
}
}
public class ContactConfiguration : EntityTypeConfiguration<Contact>
{
public ContactConfiguration()
: base()
{
HasKey(obj => obj.ID);
Property(obj => obj.ID).
HasColumnName("ContactID").
HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).
IsRequired();
Map(m =>
{
m.ToTable("Contact");
});
}
}
public class PostDetailsConfiguration : EntityTypeConfiguration<PostDetails>
{
public PostDetailsConfiguration()
: base()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("icoprod.PostDetails");
});
}
}
public class BlogDetailsConfiguration : EntityTypeConfiguration<BlogDetails>
{
public BlogDetailsConfiguration()
: base()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("icoprod.BlogDetails");
});
}
}
At this point, I have tried to use a view containing all of the information from the table with "extended" information and when I try this I get the dreaded 3032 error (error sample here). Then I tried to have the view ONLY contain the Primary key of the table and the "extended" properties (e.g. [Entry] is not in PostDetails view). When I try this, I get the following error:
All objects in the EntitySet 'DBContext.Post' must have unique primary keys. However, an instance of type 'PostDetails' and an instance of type 'Post' both have the same primary key value, 'EntitySet=Post;ID=1'.
So I have played with leaving off MapInheritedProperties a bit, but with no luck. I continue to get a similar error.
Does anyone have a suggestion on how to "extend" a base/table object and load info from a view? Again, I believe there is a big performance gain by doing this. The article I referenced at the beginning of this question has 2 potential solutions, but 1 requires too many DB hits (just to get some common lookup info) and the other requires an additional layer of abstraction (and I would really like to go directly to my POCO's from the DB, without writing any mapping).
Lastly, thank you to everyone who answers these types of questions. I applaud everyone who has contributed to responses over the years. I think too many of us developers take this information for granted!!
Loading record from view and saving it to table will not work with code mapping - Blog entity will always be loaded from table and saved to table and BlogDetail entity will always be loaded from view and saved to view - so you must have updatable view or instead of trigger to support this scenario. If you use EDMX you can also map custom SQL / Stored procedure executed for insert, update and delete to force saving to table but this feature is not available in code mapping. Anyway it is not your biggest problem.
You can use your view and you can map it to class as you did but you must not map the inheritance. The reason is the way how inheritance works. Inheritance says that entity is either parent or child (which can act as parent). There can never be database record which can be be both parent (I mean only parent) or child. It is even not possible in .NET because to support this scenario you need two instances - on of parent type and one of child type. These two instances are not equivalent because pure parent cannot be cast to child (it is not a child). And here comes the biggest problem. Once you map inheritance the key must be unique in the whole inheritance hierarchy. So you can never have two instances (one for parent and one for child) with the same key.
As a workaround don't derive BlogDetail from mapped entity (Blog). Either use third not mapped class as parent for both or interface. Also don't use MapInheritedProperties to make your BlogDetail completely unrelated to Blog.
Another workaround is not mapping BlogDetail at all. In such case you can use your code as is and instead of using a view create simple reusable query with projection:
var blogDetails = from b in context.Blogs
where ...
select new BlogDetail
{
Name = b.Name,
CreatedByID = b.CreatedByID,
...
CreatedByName = b.CreatedBy.Name // You need navigation property
...
};
In both cases if you need to save Blog you must create new instance and fill it from BlogDetail. After that you attach it to context, set it to modified state and save changes.