EntityFrameworkCore, insert records in a table containing a foreign key - foreign-keys

I'm a newbie for IdentityFramework (both the Core Version and the older ones...) and I really don't get this:
I'm in a "database first" situation, with these 2 linked table in the db: the items table has a foreign key towards the colours table.
Both of them have and Id IDENTITY PRIMARY KEY
CREATE TABLE [dbo].[colours](
[id] [int] IDENTITY(1,1) NOT NULL,
[description] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_colours] PRIMARY KEY
CREATE TABLE [dbo].[items](
[id] [int] IDENTITY(1,1) NOT NULL,
[description] [nvarchar](50) NULL,
[colour_id] [int] NOT NULL,
CONSTRAINT [PK_items] PRIMARY KEY
ALTER TABLE [dbo].[items] WITH CHECK ADD CONSTRAINT [FK_items_colours] FOREIGN KEY([colour_id])
REFERENCES [dbo].[colours] ([id])
ON UPDATE CASCADE
I generated the model and the dbContext class with the Scaffold-DbContext utility, and that's what I obtained:
namespace GeaCollection.Data.Models
{
public partial class Colours
{
public Colours()
{
Items = new HashSet<Items>();
}
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Items> Items { get; set; }
}
}
namespace GeaCollection.Data.Models
{
public partial class Items
{
public int Id { get; set; }
public string Description { get; set; }
public int ColourId { get; set; }
public virtual Colours Colour { get; set; }
}
}
And that's the code snippet of the dbContext class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Colours>(entity =>
{
entity.ToTable("colours");
entity.Property(e => e.Id)
.HasColumnName("id");
entity.Property(e => e.Description)
.IsRequired()
.HasColumnName("description")
.HasMaxLength(50);
});
modelBuilder.Entity<Items>(entity =>
{
entity.ToTable("items");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.ColourId).HasColumnName("colour_id");
entity.Property(e => e.Description)
.HasColumnName("description")
.HasMaxLength(50);
entity.HasOne(d => d.Colour)
.WithMany(p => p.Items)
.HasForeignKey(d => d.ColourId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_items_colours");
});
Now say a method in a Web API controller gets a serialized JSON containing an Item (complete of the colour, one that I already have in the db),
and I have to insert it in the db.
{
"id": 0,
"description": "item 1",
"colour": {
"id": 2,
"description": "colour 2",
}
}
private void test()
{
// the model remapped object
var item = new Items
{
Description = "Item 1",
Colour = new Colours { Id = 2, Description = "Colour 2" }
};
dbContext.Add(item);
dbContext.SaveChanges();
}
After the Add method the item ColourId properties is properly valorized, but when I invoke the SaveChanges I get the following exception:
'Microsoft.EntityFrameworkCore.DbUpdateException':
Quando IDENTITY_INSERT è OFF non è possibile inserire un valore esplicito per la colonna Identity nella tabella 'colours'.
That is "when IDENTITY_INSERT is OFF you can't insert an explicit value for the Identity column of the 'colours' table".
For sure, I don't want to INSERT anything but the item record, and I thought the framework could match the existing colours in the linked table by itself.
If I set directly the ColourId properties, and let NULL the Colour object, I can insert the record, but to me it looks quite weird.
What am I doing wrong?
Thank you
Giovanni

Related

How to retrieve list from DB to front-end with AutoMapper

After changing the mapping to Automapper, only an empty list is sent through the endpoint.
Initially I had an endpoint that retrieved all employees with info including a list with every course each employee had taken. This was with manual mapping between entities & Dto.
//From startup.cs in Configure
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Employee, Models.EmployeeCoursesDto>();
cfg.CreateMap<Employee, Models.EmployeeDto>();
cfg.CreateMap<EmployeeCourses, Models.EmployeeCoursesDto>();
});
//From Employee entity
public class Employee
{
[Key]
//Gen new Id key in DB when object created
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[MaxLength(50)]
public string Title { get; set; }
public ICollection<EmployeeCourses> EmployeeCourses { get; set; }
= new List<EmployeeCourses>();
}
}
//From employee Dto
public class EmployeeDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public ICollection<EmployeeCoursesDto> EmployeeCourses { get; set; }
= new List<EmployeeCoursesDto>();
}
}
//Endpoint in controller
[HttpGet()]
public IActionResult GetAllEmployees()
{
var employeeEntities = _employeeInfoRepository.GetEmployees();
var results = Mapper.Map<IEnumerable<EmployeeDto>>(employeeEntities);
return Ok(results);
}
//From Irepository
IEnumerable<Employee> GetEmployees();
//From repository
public IEnumerable<Employee> GetEmployees()
{
return _context.Employees.OrderBy(c => c.Name).ToList();
}
I expected output all employees with all datafileds, including their list of courses.
The output is all fields with data, except the list of courses which is "0" when running with a breakpoint, and in Postman it shows as only:
"id": 2,
"name": "Test Person",
"title": "Bus Driver",
"numberOfCourses": 0,
"employeeCourses": [],
"totalAchievedHoursAuditor": 0,
"totalAchievedHoursAccountant": 51,
"courseBalanceAccountant": null,
"courseBalanceAuditor": null
However, if I try another endpoint only for retrieving a specific course, or a list of courses, the data show correctly. Seems there are an issue with mapping the employees & courses at the same time?
I found the error, not Automapper, but my Linq statement:
return _context.Employees.Include(c => c.EmployeeCourses).ToList();
Please close this thread. Thanks for the reply Lucian Bargaoanu & have a great weekend.

RavenDB: Why do I get null-values for fields in this multi-map/reduce index?

Inspired by Ayende's article https://ayende.com/blog/89089/ravendb-multi-maps-reduce-indexes, I have the following index, that works as such:
public class Posts_WithViewCountByUser : AbstractMultiMapIndexCreationTask<Posts_WithViewCountByUser.Result>
{
public Posts_WithViewCountByUser()
{
AddMap<Post>(posts => from p in posts
select new
{
ViewedByUserId = (string) null,
ViewCount = 0,
Id = p.Id,
PostTitle = p.PostTitle,
});
AddMap<PostView>(postViews => from postView in postViews
select new
{
ViewedByUserId = postView.ViewedByUserId,
ViewCount = 1,
Id = (string) postView.PostId,
PostTitle = (string) null,
});
Reduce = results => from result in results
group result by new
{
result.Id,
result.ViewedByUserId
}
into g
select new Result
{
ViewCount = g.Sum(x => x.ViewCount),
Id = g.Key.Id,
ViewedByUserId = g.Key.ViewedByUserId,
PostTitle = g.Select(x => x.PostTitle).Where(x => x != null).FirstOrDefault(),
};
Store(x => x.PostTitle, FieldStorage.Yes);
}
public class Result
{
public string Id { get; set; }
public string ViewedByUserId { get; set; }
public int ViewCount { get; set; }
public string PostTitle { get; set; }
}
}
I want to query this index like this:
Return all posts including - for a given user - the integer of how many times, the user has viewed the post. The "views" are stored in a separate document type, PostView. Note, that my real document types have been renamed here to match the example from the article (I certainly would not implement "most-viewed" this way).
The result from the query I get is correct - i.e. I always get all the Post documents with the correct view-count for the user. But my problem is, the PostTitle field always is null in the result set (all Post documents have a non-null value in the dataset).
I'm grouping by the combination of userId and (post)Id as my "uniqueness". The way I understand it (and please correct me if I'm wrong), is, that at this point in the reduce, I have a bunch of pseudo-documents with identical userId /postId combination, some of which come from the Post map, others from the PostView map. Now I simply find any single pseudo-document of the ones, that actually have a value for PostTitle - i.e. one that originates from the Post map. These should all obviously have the same value, as it's the same post, just "outer-joined". The .Select(....).Where(....).FirstOrDefault() chain is taken from the very example I used as a base. I then set this ViewCount value for my final document, which I project into the Result.
My question is: how do I get the non-null value for the PostTitle field in the results?
The problem is that you have:
ViewedByUserId = (string) null,
And:
group result by new
{
result.Id,
result.ViewedByUserId
}
into g
In other words, you are actually grouping by null, which I'm assuming that isn't your intent.
It would be much simpler to have a map/reduce index just on PostView and get the PostTitle from an include or via a transformer.
You understanding of what is going on is correct, in the sense that you are creating index results with userId / postId on them.
Buit what you are actually doing is creating results from PostView with userId /postId and from Post with null /postId.
And that is why you don't have the matches that you want.
The grouping in the index is incorrect. With the following sample data:
new Post { Id = "Post-1", PostTitle = "Post Title", AuthorId = "Author-1" }
new PostView { ViewedByUserId = "User-1", PostId = "Post-1" }
new PostView { ViewedByUserId = "User-1", PostId = "Post-1" }
new PostView { ViewedByUserId = "User-2", PostId = "Post-1" }
The index results are like this:
ViewCount | Id | ViewedByUserId | PostTitle
--------- | ------ | -------------- | ----------
0 | Post-1 | null | Post Title
2 | Post-1 | User-1 | null
1 | Post-1 | User-2 | null
The map operation in the index simply creates a common document for all source documents. Thus, the Post-1 document produces one row, the two documents for Post-1 and User-1 produce two rows (which are later reduced to the single row with ViewCount == 2) and the document for Post-1 and User-2 produces the last row.
The reduce operation the groups all the mapped rows and produces the resulting documents in the index. In this case, the Post-sourced document is stored separately from the PostView-sourced documents because the null value in the ViewedByUserId is not grouped with any document from the PostView collection.
If you can change your way of storing data, you can solve this issue by storing the number of views directly in the PostView. It would greatly reduce duplicate data in your database while having almost the same cost when updating the view count.
Complete test (needs xunit and RavenDB.Tests.Helpers nugets):
using Raven.Abstractions.Indexing;
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Tests.Helpers;
using System.Linq;
using Xunit;
namespace SO41559770Answer
{
public class SO41559770 : RavenTestBase
{
[Fact]
public void SO41559770Test()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
new PostViewsIndex().Execute(store);
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Post { Id = "Post-1", PostTitle = "Post Title", AuthorId = "Author-1" });
session.Store(new PostView { Id = "Views-1-1", ViewedByUserId = "User-1", PostId = "Post-1", ViewCount = 2 });
session.Store(new PostView { Id = "Views-1-2", ViewedByUserId = "User-2", PostId = "Post-1", ViewCount = 1 });
session.SaveChanges();
}
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
using (IDocumentSession session = store.OpenSession())
{
var resultsForId1 = session
.Query<PostViewsIndex.Result, PostViewsIndex>()
.ProjectFromIndexFieldsInto<PostViewsIndex.Result>()
.Where(x => x.PostId == "Post-1" && x.UserId == "User-1");
Assert.Equal(2, resultsForId1.First().ViewCount);
Assert.Equal("Post Title", resultsForId1.First().PostTitle);
var resultsForId2 = session
.Query<PostViewsIndex.Result, PostViewsIndex>()
.ProjectFromIndexFieldsInto<PostViewsIndex.Result>()
.Where(x => x.PostId == "Post-1" && x.UserId == "User-2");
Assert.Equal(1, resultsForId2.First().ViewCount);
Assert.Equal("Post Title", resultsForId2.First().PostTitle);
}
}
}
}
public class PostViewsIndex : AbstractIndexCreationTask<PostView, PostViewsIndex.Result>
{
public PostViewsIndex()
{
Map = postViews => from postView in postViews
let post = LoadDocument<Post>(postView.PostId)
select new
{
Id = postView.Id,
PostId = post.Id,
PostTitle = post.PostTitle,
UserId = postView.ViewedByUserId,
ViewCount = postView.ViewCount,
};
StoreAllFields(FieldStorage.Yes);
}
public class Result
{
public string Id { get; set; }
public string PostId { get; set; }
public string PostTitle { get; set; }
public string UserId { get; set; }
public int ViewCount { get; set; }
}
}
public class Post
{
public string Id { get; set; }
public string PostTitle { get; set; }
public string AuthorId { get; set; }
}
public class PostView
{
public string Id { get; set; }
public string ViewedByUserId { get; set; }
public string PostId { get; set; }
public int ViewCount { get; set; }
}
}

dapper map one to one in classmapper

I have 2 simple tables.
create table Owner
(
Id int primary key,
Name nvarchar(100),
);
create table Status
(
Id int primary key,
BrandName nvarchar(50)
OwnerId int foreign key references Owner(Id),
);
In app I map these tables to model classes:
public class Owner
{
public int Id {get;set;}
public string Name{get;set;}
public Status Status {get;set;}
}
public class Status
{
public int Id {get;set;}
public string Brand {get;set;}
public int OwnerId {get;set;}
}
I use dapper and dapper extension.
I would like map relation one to one in dapper in classmapper. It’s possible?
My goal is when I added owner object which has set up also property Status to db via repository
it also insert record do status table.
What is best way to achieve this behavior?
public class OwnerMapper : ClassMapper<Owner>
{
public OwnerMapper()
{
Table("Owner");
Map(p=>p.Id).Column("Id").Key(KeyType.Assigned);
Map(p=>p.Name).Column("Name");
//how map property status
}
}
public class StatusMapper : ClassMapper<Status>
{
public StatusMapper()
{
Table("Status");
Map(p=>p.Id).Column("Id").Key(KeyType.Identity);
Map(p=>p.Brand).Column("BrandName");
Map(p=>OwnerId).Column("OwnerId");
}
}
You can try Dapper's multi-mapping feature:
[Test]
public void MyTest()
{
var connection = new SqlConnection("conn string here");
connection.Open();
const string sql =
"select Id = 1, Name ='Bill Gates', Id = 1, Brand = 'Apple', OwnerId = 1";
var result = connection.Query<Owner, Status, Owner>(sql,
(owner, status) =>
{
owner.Status = status;
return owner;
},
commandType: CommandType.Text
).FirstOrDefault();
Assert.That(result, Is.Not.Null);
Assert.That(result.Status, Is.Not.Null);
Assert.That(result.Status.Brand, Is.EqualTo("Apple"));
connection.Close();
}

using a viewmodel to assign values from a soap web service to a dropdownlist

I need some advice on getting values from a soap web service to display in a dropdownlist using a viewmodel, I currently receive the data for the various dropdownlists from a service class found in a service class library project (n-tier application).
The code for the dropdownlist service follows a similar format to the code below:
public IEnumerable<SelectListItem> getValuesAsSelectItems(string selected)
{
var items = new List<SelectListItem>();
items.Add(new SelectListItem { Text = "Please Select", Value = string.Empty, Selected = (selected == string.Empty) });
foreach (var value in this.getValues())
{
items.Add(new SelectListItem { Text = value.Value, Value = value.Value, Selected = (selected == value.Value) });
}
return new SelectList(items, "Value", "Text");
}
I need a way of passing the values from this service to the viewmodel I have then created 7 controllers for each of the dropdownlist which will all link to partial views that I can reuse throughout the application, dropdownlists include titles, countries, states and others.
An approach you could take is to extract the drop down list values into a viewmodel of their own. So:
Step 1: Create a view model (ItemsViewModel) that encapsulates the drop down list items:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Models
{
public class DropDownListItem
{
public string Text { get; set; }
public string Value { get; set; }
}
public class ItemsViewModel
{
private readonly List<DropDownListItem> _items;
// The selected item:
public string SelectedItem { get; set; }
// The items:
public IEnumerable<SelectListItem> Items
{
get
{
var allItems = _items.Select(i => new SelectListItem
{
Value = i.Value,
Text = i.Text
});
return DefaultItem.Concat(allItems);
}
}
// Default item (i.e. the "select" text) if none selected
public IEnumerable<SelectListItem> DefaultItem
{
get
{
return Enumerable.Repeat(new SelectListItem
{
Value = "-1",
Text = "Select an item"
}, count: 1);
}
}
public ItemsViewModel()
{
}
// Constructor taking in items from service and selected item string:
public ItemsViewModel(List<DropDownListItem> items, string selected)
{
_items = items;
SelectedItem = selected;
}
}
}
Step 2: Create a partial view in the Views folder that binds to the ItemsViewModel:
#model Models.ItemsViewModel
#Html.DropDownListFor(m => m.SelectedItem, Model.Items)
Step 3: In the appropriate controller (e.g. HomeController), place the child action that pulls the data from the service, the view model and the partial view together:
[ChildActionOnly]
public ActionResult DropDownList(string type, string selected)
{
// If you need to call different services based on the type (e.g. Country), then pass through "type" and base the call on that
var items = new ItemsViewModel(
(from g in _service.getTitles() select new DropDownListItem { Text = g.Text, Value = g.Value }).ToList(),
selected);
return PartialView("DropDownPartial", items);
}
Step 4: Drop this line of code into the view where you need the drop down list:
#Html.Action("DropDownList", "Home", new { selected = "2", type = "country" })
Note that selected and type are to be determined whichever way you see fit and are optional.
Hopefully this gives you some inspiration.

How to map and test a "Many to Many Relationship" in NHibernate using Fluent NHibernate

When I test my many to many classes an error occurs:
System.ApplicationException: Actual
count does not equal expected count.
Entities:
public interface IEntity
{
int Id { get; set; }
}
public abstract class Entity : IEntity
{
public virtual int Id { get; set; }
public virtual bool IsPersistent
{
get { return isPersistentObject(); }
}
public override bool Equals(object obj)
{
if (isPersistentObject())
{
var persistentObject = obj as Entity;
return (persistentObject != null) && (Id == persistentObject.Id);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
return isPersistentObject() ? Id.GetHashCode() : base.GetHashCode();
}
private bool isPersistentObject()
{
return (Id != 0);
}
}
public class Team : Entity
{
public virtual string Name { get; set; }
public virtual ISet<Employee> Employees { get; set; }
public Team()
{
Employees = new HashedSet<Employee>();
}
}
public class Employee : Entity
{
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual ISet<Team> Teams { get; set; }
public virtual string EMail { get; set; }
public Employee()
{
Teams = new HashedSet<Team>();
}
}
Mappings:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
// identity mapping
Id(p => p.Id).Column("TeamID");
// column mapping
Map(p => p.Name);
// relationship mapping
HasManyToMany(m => m.Employees)
.Table("EmployeeTeam")
.LazyLoad()
.Cascade.SaveUpdate()
.AsSet()
.ParentKeyColumn("TeamID")
.ChildKeyColumn("EmployeeID");
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
// identifier mapping
Id(p => p.Id).Column("EmployeeID");
// column mapping
Map(p => p.EMail);
Map(p => p.LastName);
Map(p => p.FirstName);
// relationship mapping
HasManyToMany(m => m.Teams).Table("EmployeeTeam")
.Inverse()
.Cascade.SaveUpdate()
.AsSet()
.LazyLoad()
.ParentKeyColumn("EmployeeID")
.ChildKeyColumn("TeamID");
}
}
Test:
[TestMethod]
public void CanCorrectlyMapEmployee()
{
var team = new List<Team> {new Team() {Name = "Team1"}};
new PersistenceSpecification<Employee>(_session)
.CheckProperty(p => p.EMail, "Mail")
.CheckProperty(p => p.FirstName, "Firstname")
.CheckProperty(p => p.Id, 1)
.CheckProperty(p => p.LastName, "Lastname")
.CheckList(p => p.Teams,team )
.VerifyTheMappings();
}
Whether I add an Employee or a Team my EmployeeTeam table is always empty.
I have tested it against SQLLite with FNH and manually against SQL Server 2008.
Does anybody of you have an idea to fix this?
edit:
I was amazed to find out that when I create an Employee and add 2 Teams to the Employee and load the created Employee he has 2 Teams. So it works fine. But when I look in my relationsip table EmployeeTeam then everything is empty. Can someone explain me why?
And does anybody know how I can use Fluent NHibernate to test my many to many relationship?
Thanks in advance!
Employee map has inverse attribute. As you probably know, this means that when you save employee, relationship table (EmployeeTeam) will not be updated. To add/remove new relationship information you have to add employee to the Team and save Team.
So, in your case - don't test many to many on the side of employee, test it on the side of team. (If you'd like NHibernate to add records when you add team to employee, you'll have to invert "Inverse" attributes - give it to team, not employee, but then - same story with Team entity).
Why were you able to load Employee with teams? Because of session-level cache. You've probably saved and loaded Employee in the same ISession - this means that NHibernate returned you exactly reference to the same object, without loading it from the db. Try saving & loading in two different sessions and you'll see no team in Employee.Teams set.
Side note: It is considered a good practice to create methods that will enforce consistency between many-to-many relationships, that is - when you add Team to Employee, Employee is added to the team, sth. like this:
class Employee
{
// ...
public void AddTeam(Team team)
{
// check for null, etc.
Teams.Add(team);
team.Employees.Add(this);
}
}
and very simillar method in the Team class.