0..1 to N Referencing Relation -> Error Message I don't understand - ef-code-first

The issue is a that I am getting an error message reading:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Users_dbo.People_Id". The conflict occurred in database "TrialDb", table "dbo.People", column 'Id'
I try to implment the following:
User may have a Person assigned and Person may have many User assigned
each entity, i.e. Person or any other, will have a creator and a changing user set.
The relationship shold one-way, meaning that User does not need a navigation property for Person.
What I am doing is trying to add a User to my database which results in the aforewritten error.
Here is the User and Person class definition (abreviated)
public class User
{
public int Id {get; set;}
public int? PersonId {get; set; }
public virtual Person Person {get; set;}
// two self-refencing properties
public int CreatorId {get; set; }
public virtual Person Creator{get; set;}
public int ChangingUserId {get; set; }
public virtual Person ChangingUser {get; set;}
public byte[] RowVersion {get; set;}
}
public class Person
{
public int Id {get; set;}
public virtual ICollection<User> Users {get; set;}
public byte[] RowVersion {get; set;}
}
My configuration classes for the two entities are as follows:
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap ()
{
HasRequired (p => p.Creator).WithRequiredPrincipal ().WillCascadeOnDelete (false);
HasRequired (p => p.ChangingUser).WithRequiredPrincipal ().WillCascadeOnDelete (false);
// 0..1 : N relation
HasOptional (p => p.Person).WithMany (p => p.Users).HasForeignKey (p => p.PersonId);
Property (p => p.RowVersion).IsRowVersion ();
}
}
public class PersonMap : EntityTypeConfiguration<Person>
{
public PersonMap ()
{
HasRequired (p => p.Creator).WithRequiredPrincipal ().WillCascadeOnDelete (false);
HasRequired (p => p.ChangingUser).WithRequiredPrincipal ().WillCascadeOnDelete (false);
// 0..1 : N relations - tried this as well - same result
//HasOptional (p => p.Company).WithMany (p => p.Employees).HasForeignKey (p => p.CompanyId);
//HasOptional (p => p.Facility).WithMany (p => p.People).HasForeignKey (p => p.FacilityId);
Property (p => p.RowVersion).IsRowVersion ();
}
}
Can anybody help me with this? It would be utmost apprciated!

The People_Id, which isn't in your model, indicates EF has inserted it because your fluent code is wrong. Your user table is pointing to Person 3 times, but your fluent code only tells EF how to map one FK (PersonId). You need to change the other 2 to indicate the ForeignKey assignment using either HasForeignKey or MapKey (sorry no test project readily available).
Try something like this for UserMap:
HasRequired(p => p.Creator).WithMany().HasForeignKey(p => p.CreatorId).WillCascadeOnDelete (false);
HasRequired(p => p.ChangingUser).WithMany().HasForeignKey(p => p.ChangingUserId).WillCascadeOnDelete (false);
I'm not sure what you are trying to do in your PersonMap since your fluent code references 2 navigation properties not in the model. Assuming you want 2 self references you would need similar code.

Related

Entity Frameworok generate guid as id and save it

I'm trying to save to my table Users let's say, string ID, string email, and string password. The problem is that ID must be a guid that I have to create it and save it and not SQL server. Any ideas how?
I searched but I only found how to make SQL server to create the guid.
First of all, tell Entity framework that you will generate the value of the primary key:
Use DatabaseGenerated Attribute
public class School
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name {get; set;}
...
}
The None option prevents values from being generated by the database automatically in cases where they would otherwise be created.
Furthermore, consider to overwrite DbContext.SaveChanges(). In this procedure ask the ChangeTracker for all elements that are Added. Generate an Id for every Added element. It might be dangerous to let others generate an Id, because they might be adding a constant value or just an auto-increment.
Another possibility would be to generate it within the Add function, but if you do that, then users could change your generated Id. So the proper place is within SaveChanges:
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
// This will fail: the added element doesn't have a property Id:
addedElement.Id = GenerateId();
}
return base.SaveChanges();
}
For this you have to be certain that every added element has a property Id. The simplest way is to create an interface and let all your tables implement this interface:
public interface IID
{
string Id {get; set;}
}
public class School : IID {...}
public class Student : IID {...}
public class Teacher : IID {...}
public class DbContext
{
public DbSet<School> Schools {get; set;}
public DbSet<Student> Students{get; set;}
public DbSet<Teacher> Teachers {get; set;}
public override int SaveChanges()
{
var addedElements = this.ChangeTracker.Entries.Cast<IID>
.Where(entry => entry.State == EntityState.Added);
foreach(var addedElement in addedElements)
{
addedElement.Id = GenerateId(); // Every Added element implements IId
}
return base.SaveChanges();
}
private string GenerateId()
{
... // TODO: return unique ID, for instance a GUID
}
}

How to create a CRUD for multi-level child Model with ASP.Core & EF?

I'm trying to create a CRUD that can edit a model and all its children, but each time the parent model ends up empty, how to properly do a scaffolded CRUD with ASP.Core 2.2?
//Models
class Book {
int IdBook {get; set;}
string Name {getl set;}
ICollection<Page> PageList {get; set;}
}
class Page {
int IdPage {get; set;}
string Name {get; set;}
ICollection<Line> LineList {get; set;}
}
class Line{
int IdLine {get;set;}
string Content {get; set;}
}
Here's my controller
//Controller
public async Task<IActionResult> Edit(int? id)
{
var book = _context.Book
.Include(b => b.PageList)
.ThenInclude(p => p.LineList)
.First();
return View(book);
}
Here's what I'm trying to do
#model Book
#Model.Name
#for(var indexPage = 0; indexPage < Model.PageList.Count; indexPage++)
{
#Model.PageList[indexPage].Name
#for(var indexLine = 0; indexLine < Model.PageList[indexPage].LineList.Count)
{
Html.EditorFor(x => x.PageList[indexPage].LineList[indexLine].Content)
}
}
But when I post my form, I only get the properties of Book, and Book.PageList is null, what is the proper way do that? Is there any tutorial I would have missed?
UPDATE
The problem seems to be the type, the controller receives the post parameter
(My code is a bit different, but the same, the books were an example)
Can you post entire code for the action method on the controller that processes your request?
From your code on the razor view page, in the inner loop where you are iterating through PageList, your are not incrementing your indexLine. Shouldn't this line
#for(var indexLine = 0; indexLine < Model.PageList[indexPage].LineList.Count)
be
#for(var indexLine = 0; indexLine < Model.PageList[indexPage].LineList.Count, indexLine++)?
Again, On the controller, if the Request.Form property has the entire 'supposed' payload but model binding isn't working, try annotating the Submission parameter with [FromBody] Annotation to clearly inform ASP.NET to bind Submission from bofy of the request - like so
public async Task<IActionResult> Edit(int id, [FromBody] Submission submission) {}
Look through these tiny fixes and let me know if you still have any issues

EF many to many, when update it always insert new row in bridge table

class Student{
public string Name {get; set;}
public EntityCollection<Info> Infos {get; set;}
}
class Info{
public string Title {get; set;}
public EntityCollection<Student> Students {get; set;}
}
//----------------
modelBuilder.Entity<Student>()
.HasMany( u => u.Infos)
.WithMany( c => c.Students)
.Map( wt =>
{
wt.MapLeftKey( "StudentId" );
wt.MapRightKey( "InfoId" );
wt.ToTable( "StudentInfo" );
} );
In db I have three tables (Student, Info and bridge table StudentInfo). It work as expect when insert. But when I tried to get Student entity and it Infos, I set new Student Name and save change db context. It always insert new record into bridge table (duplicate StudentId and InfoId). How to avoid that?

How to sort data in an asp.net gridview by properties of sub-objects?

I have a List<Role> (see below) that I am binding to an asp.net gridview. I want to sort this data using SortExpression, such that it is sorted by two properties of sub-objects of the rows. Specifically, I want to sort by the Application's Name, then the ApplicationType's ApplicationTypeName.
How can I do this?
The classes here are:
public class Application
{
public string Name {get; set;}
public int Status {get; set;}
}
public class ApplicationType
{
public string ApplicationTypeName {get; set;}
public int ApplicationTypeStatus {get; set;}
}
public class Role
{
public Application oApplication {get; set;}
public ApplicationType oApplicationType {get; set;}
}
Edit: note that I was responding to the earlier verison of the question, before it related to gridview; still, this might be useful...
Worst case: you can use the approach here to pre-sort the list before binding it to the gridview.
Various options:
implement IComparable[<T>]
implement IComparer[<T>]
use an ad-hoc sort
I'm guessing you just need the last, so perhaps:
list.Sort((x,y) => {
int delta = string.Compare(x.Application.Name, y.Application.Name);
if (delta == 0) delta = string.Compare(
x.ApplicationType.ApplicationTypeName, y.ApplicationType.ApplicationTypeName);
return delta;
});
Alternatively, you can perhaps do it via LINQ in the source data - note however that this is done when creating a new list - it isn't an in-place sort of an existing list:
var list = source.OrderBy(x => x.Application.Name)
.ThenBy(x => x.ApplicationType.ApplicationTypeName)
.ToList();

Object Data Source with Reporting Services in Visual Studio

I'm working on a site where we want to include a pie chart on a page. Right now I'm working on implementing that through Reporting Services (RDLC file) with an object data source.
The SelectMethod of my object data source is a method that returns a list of business objects; lets say a List<Alpha> and Alpha has a sub object Beta with a Name property. In my report definition I have set the Category groups to be: =Fields!Beta.Value.Name this means that Alpha.Beta.Name are my pie slices. I got the following error:
An error has occurred during report processing. The Group expression for the grouping 'chart1_CategoryGroup1' contains an error: Object variable or With block variable not set.
I was able to confirm this is because Beta is nullable and was able to fix the issue by updating the object Alpha to return a new Beta() if the Beta property is null. This solution is not ideal though because there are other places in my code where I need Beta to be null if it doesn't have a value yet.
Is there a way to update the report definition to accept a null property as valid? Ideally I would like to specify the value as "Not Set" if Beta is null.
I had similar problem as yours, and I solved it using Null Object Refactoring ( many thanks to Martin Fowler's book Refactoring :)).
Here you can find nice example Null object refactoring
So you could create class NullableBeta that inherits Beta, while properties Name and e.g. IsNullable are virtual on Beta entity.
public class Beta
{
public virtual Name{ get; set;}
public virtual IsSet{ get{return true;}}
}
public class NullableBeta:Beta
{
public override Name
{
get{
return "Not Set";
}
set{}
}
public override IsSet{ get{ return false;}}
}
Now, if Beta is not set on Alfa entity, you could return instance of NullableBeta entity. In reports, you would have "Not Set" instead of empty string, and on places where you are setting Beta entity to Alfa you can check IsSet property instead of checking if Beta is null.
Hope this was helpful
I had similar problem as yours, and I solved it using Null Object Refactoring ( many thanks to Martin Fowler's book Refactoring :)). Here you can find nice example Null object refactoring
I first deal with this shortcomings of SSRS/RDLC by implementing the Null object pattern as well.
Implementing this manually is of course too much effort when more then one or two domain objects are involved.
However, since I am already using AutoMapper, #LucianBargaoanu correctly pointed out in the comments that null objects are natively supported as an opt-in feature by AutoMapper, so there is no explicit implementation needed.
I therefore use AutoMapper with its AllowNullDestinationValues, AllowNullCollections, PreserveReferences(), NullSubstitute and ForAllPropertyMaps() features, to map all my domain classes to report specific classes and substitute all null references to either null objects (when mapping domain object null references to report objects) or reasonable default values (e.g. an empty string for null strings or the default value of the underlying primitive type for Nullable<PrimitiveType>).
Here is some sample code to demonstrate the approach:
namespace Domain
{
public class MyClass
{
public int Id {get; set;}
public string Name {get; set;} // could be null
public string Code {get; set;} // could be null
public decimal? SomeNullableValue {get; set;} // could be null
public MyOtherClass OptionalOtherClass {get; set;} // could be null
}
public class MyOtherClass
{
public int OtherId {get; set;}
public string OtherName {get; set;} // could be null
public decimal? SomeOtherNullableValue {get; set;} // could be null
}
}
namespace ReportViewModels
{
[Serializable]
public class MyClass
{
public int Id {get; set;}
public string Name {get; set;} // should not be null (but empty)
public string Code {get; set;} // should not be null (but empty)
public decimal? SomeNullableValue {get; set;} // should not be null (but default(decimal))
public string CommonName
=> (Name + " " + Code).Trim();
public MyOtherClass OptionalOtherClass {get; set;} // should not be null (but a MyOtherClass null object)
}
[Serializable]
public class MyOtherClass
{
public int OtherId {get; set;}
public string OtherName {get; set;} // should not be null (but empty)
public decimal? SomeOtherNullableValue {get; set;} // should not be null (but default(decimal))
}
}
public partial class Form1 : Form
{
private Context _context;
private ReportObjectGenerator _reportObjectGenerator;
public Form1(Context context, ReportObjectGenerator reportObjectGenerator)
{
_context = context;
_reportObjectGenerator = reportObjectGenerator;
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var myDomainObjects = context.MyClass
.Include(e => e.OptionalOtherClass)
.ToList();
var myReportViewModels = _reportObjectGenerator.GetReportObjects<Domain.MyClass, ReportViewModels.MyClass>(myDomainObjects);
components ??= new System.ComponentModel.Container();
//reportViewer1.LocalReport.ReportEmbeddedResource = "MyNamespace.Report1.rdlc";
reportViewer1.LocalReport.ReportPath = "Report1.rdlc";
reportViewer1.LocalReport.DataSources.Clear();
reportViewer1.LocalReport.DataSources.Add(
new ReportDataSource
{
Name = "MyClassDataSet",
Value = new BindingSource(components)
{
DataMember = "MyClass",
DataSource = myReportViewModels
}
});
reportViewer1.RefreshReport();
}
}
public class ReportObjectGenerator
{
public List<TDestination> GetReportObjects<TSource, TDestination>(
IEnumerable<TSource> sourceObjects)
{
var domainNamespace = typeof(TSource).Namespace ?? throw new InvalidOperationException();
var reportNamespace = typeof(TDestination).Namespace ?? throw new InvalidOperationException();
var mapper = new MapperConfiguration(
cfg =>
{
cfg.AllowNullDestinationValues = false;
cfg.AllowNullCollections = false;
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).ToList();
var allDomainTypes = allTypes.Where(t => t.Namespace?.StartsWith(domainNamespace) ?? false).ToList();
var allReportTypes = allTypes.Where(t => t.Namespace?.StartsWith(reportNamespace) ?? false).ToList();
foreach (var reportClassType in allReportTypes)
{
var domainClassType = allDomainTypes.Single(t => t.Name == reportClassType.Name);
cfg.CreateMap(domainClassType, reportClassType)
.PreserveReferences();
}
// If we want to set the default value of the underlying type of Nullable<UnderlyingType>
// properties in case they would be null, than AllowNullDestinationValues is not enough and we
// need to manually replace the null value here.
cfg.ForAllPropertyMaps(
pm => pm.SourceMember.GetMemberType().IsNullableType(),
(p, _) => p.NullSubstitute ??= Activator.CreateInstance(p.SourceMember.GetMemberType().GetTypeOfNullable()));
})
.CreateMapper();
return mapper.Map<IEnumerable<TSource>, List<TDestination>>(sourceObjects);
}
}

Resources