Subsonic 3 - Simple repository and creating a foreign key - subsonic

There seems to be hardly any examples out there so here goes:
Here are my three structures but it doesn't seem to create the tables properly and when I call the following line it says Id is not recognised:
IEnumerable<Permission> permissions = _data.Find<RolePermission>(x => x.Role.RoleKey == roleKey).Select(x => x.Permission);
RolePermission:
public class RolePermission
{
[SubSonicPrimaryKey]
public int RolePermissionId { get; set; }
public int RoleId { get; set; }
public int PermissionId { get; set; }
//Foreign Key of Role
public Role Role { get; set; }
//Foreign key of Permission
public Permission Permission { get; set; }
}
Permission:
public class Permission
{
[SubSonicPrimaryKey]
public int Id { get; set; }
[SubSonicLongString]
public string PermissionKey { get; set; }
[SubSonicLongString]
public string PermissionDescription { get; set; }
}
Role:
public class Role
{
[SubSonicPrimaryKey]
public int Id { get; set; }
[SubSonicLongString]
public string RoleKey { get; set; }
[SubSonicLongString]
public string RoleDescription { get; set; }
}

I don't know if this has been fixed in a current release but I remember a silly bug in subsonic's primary key detection.
If your Object contains a property named Id subsonic assumes that is your primary key.
If not you have to tell subsonic with is your PK by decorating a property with the SubSonicPrimaryKey attribute (like you did).
If you have a property called Id and it is also decorated with the attribute (like your Role and Permission class) subsonic finds the property twice and does not check if they both equals. Then it throws an exception because it can't reliably determine which one to take.
Long story short, you should try:
a) Remove the Attribute from your Id column
b) Rename the property to RoleId or PermissionId (which would be more consistend because your RolePermission class has it's PK called RolePermissionId)
If that doesn't help, please provide a stacktrace.

Related

Autmapper nested mapping

I have the following main class:
public class ResearchOutcome
{
public ResearchOutcomeCategory ResearchOutcomeCategory { get; set; }
public string? UniqueIdentifier { get; set; }
}
And the category class is:
public class ResearchOutcomeCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
}
The View models for above classes are:
public class ResearchOutcomeDetailVm : IMapFrom<ResearchOutcome>
{
public int Id { get; set; }
public virtual ResearchOutcomeCategoryDetailVm ResearchOutcomeCategory { get; set; }
}
public class ResearchOutcomeCategoryDetailVm : IMapFrom<ResearchOutcomeCategory>
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Now, I have used the following mapping profile:
// First this one
profile.CreateMap<ResearchOutcomeCategory, ResearchOutcomeCategoryDetailVm>();
profile.CreateMap<ResearchOutcome, ResearchOutcomeDetailVm>();
//Then I tried this one
profile.CreateMap<ResearchOutcome, ResearchOutcomeDetailVm>()
.ForMember(o => o.ResearchOutcomeCategory,
cat => cat.MapFrom( o => o.ResearchOutcomeCategory));
But the ResearchOutcomeCategory is always null. Any help would be appreciated.
After digging more, I identified that I was not "Including" the relevant item in the query, hence, the view model was always empty. Pretty dumb on my part :D
Regarding the mapping, if the properties (even complex ones) have the same names, then the mapper will map them automatically. So simply this line worked
profile.CreateMap<ResearchOutcomeCategory, ResearchOutcomeCategoryDetailVm>();
Hope it helps someone

PATCH in ServiceStack

I am trying to patch a object with the following code.
public object Patch(EditBlog request)
{
using (var db = _db.Open())
{
try
{
request.DateUpdated = DateTime.Now;
Db.Update<Blog>(request, x => x.Id == request.Id);
return new BlogResponse { Blog = Blog = Db.Select<Blog>(X=>X.Id == request.Id).SingleOrDefault()};
}
catch (Exception e)
{
return HttpError.Conflict("Something went wrong");
}
}
}
In Postman, I am calling the function like this "api/blog/1?=Title=Test1&Summary=Test&UserId=1".
When debugging I can see that those values has been assigned to the request.
During the Update it throws: "Cannot update identity column 'Id'"
My model looks like this
public class Blog
{
[AutoIncrement]
public int Id { get; set; }
public IUserAuth User { get; set; }
[Required]
public int UserId { get; set; }
[Required]
public string Title { get; set; }
public string Summary { get; set; }
public string CompleteText { get; set; }
[Required]
public DateTime DateAdded { get; set; }
public DateTime DateUpdated { get; set; }
}
And the EditBlog DTO looks like this:
[Route("/api/blog/{id}", "PATCH")]
public class EditBlog : IReturn<BlogResponse>
{
public int Id { get; set; }
public IUserAuth User { get; set; }
public int UserId { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public string CompleteText { get; set; }
public DateTime DateUpdated { get; set; }
}
The error message "Cannot update identity column 'Id'" does not exist anywhere in ServiceStack.OrmLite, it could be an error returned by the RDBMS when you're trying to update the Primary Key which OrmLite wouldn't do when updating a Model annotated with a Primary Key like your Blog class has with its annotated [AutoIncrement] Id Primary Key.
The error is within your Db.Up<T> method that's performing the update, which is not an OrmLite API, so it's likely your own custom extension method or an alternative library.
I would implement a PATCH Request in OrmLite with something like:
var blog = request.ConvertTo<Blog>();
blog.DateUpdated = DateTime.Now;
Db.UpdateNonDefaults(blog);
i.e. using OrmLite's UpdateNonDefaults API to only update non default fields and updating using the Blog Table POCO not the EditBlog Request DTO.
Also you should use the Single APIs when fetching a single record, e.g:
Blog = Db.SingleById<Blog>(request.Id)
or
Blog = Db.Single<Blog>(x => x.Id == request.Id)
Instead of:
Blog = Db.Select<Blog>(X=>X.Id == request.Id).SingleOrDefault()

Automapper - flattening of object property

let's say I have
public class EFObject
{
public int Id { get; set; }
public int NavId { get; set; }
public NavObject Nav { get; set; }
}
public class DTOObject
{
public int Id { get; set; }
public int NavId { get; set; }
public string NavName { get; set; }
}
My expectation was high, and I thought to my self the built-in flattening should handle this, so my mapping is very simple
CreateMap<DTOObject, EFObject>().ReverseMap();
Unfortunately, converting DTOObject to EFObject does not work as expected because EFObject.Nav is null. Since I used the name NavId and NavName I would expect it to create a new NavObject and set the Nav.Id and Nav.Name accordingly.
My Problem : Is there a feature in Automapper that will allow me to achieve the intended result without having to manually write a rule to create an NavObject when mapping the Nav property?.
Unflattening is only configured for ReverseMap. If you want unflattening, you must configure Entity -> Dto then call ReverseMap to create an unflattening type map configuration from the Dto -> Entity.
as noted by Automapper documentation here

Automapper projection results in empty collection for nested Dto

I have a .Net Core 2 webapi in which I am using automapper to map to Dtos. Everything works fine, except I am seeing an unexpected behaviour when I map an object to a Dto, and where the Dto also contains mappings for a collection. E.g
CreateMap<Order, OrderDto>();
CreateMap<Product, ProductDto>();
Where classes are like this
public partial class Order
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products{ get; set; }
public int ProductCount {return Products.Count;}
}
public partial class Product
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The following works as expected;
The class is mapped, and the ProjectCount is correct in the Dto
public partial class OrderDto
{
public int Id { get; set; }
public virtual ICollection<Product> Products{ get; set; }
public int ProductCount{ get; set; }
}
_context.Orders.Include<>(Products).ProjectTo<>(OrderDto)
But doing the following, the productcount is always zero.
E.g. if I do this;
public partial class OrderDto
{
public int Id { get; set; }
public virtual ICollection<ProductDto> Products{ get; set; }
public int ProductCount{ get; set; }
}
public partial class ProductDto
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
}
_context.Orders.Include<>(Products).ProjectTo<>(OrderDto)
Why does this happen, and how can I ensure that it doesnt? This is a real world example where I need a property which references the collection - and I need it in both the base and the Dto. I can do the following which works fine, but it doesnt appear that this should be how it works...
public partial class OrderDto
{
public int Id { get; set; }
public virtual ICollection<ProductDto> Products{ get; set; }
public int ProductCount {return Products.Count;}
}
public partial class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
}
_context.Orders.Include<>(Products).ProjectTo<>(OrderDto)
I profiled the SQL and found that Automapper changes the way the query is formed. Without the nested projection, two queries are made;
[Queries are more complex than this and use joins, but you get the idea]
Select Id from orders
Select Id,Name from products where productid in [select id from orders ]
With the nested projection, are executed for each nested Dto
Select Id from orders
Select Id,Name from products where id=1
Select Id,Name from products where id=2

EntityFramework : Invalid column name *_ID1

I am trying to implement DbContext for couple of tables called 'Employee' and 'Department'
Relationship between Employee and Department is many to one. i.e. department can have many employees.
Below are the EntityFramework classes I designed ( CodeFirst approach )
[Table("Employee")]
public class Employee
{
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("Name")]
public string Name { get; set; }
[Column("Department_ID")]
public int Department_ID { get; set; }
public virtual Department Department { get; set; }
}
[Table("Department")]
public class Department
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Column("Name")]
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
While adding Employee record I am getting below exception
"Invalid column name 'Department_ID1'."
I am not sure why EF is referring to Department_ID1. Do I need to add configuration in OnModelCreating method of DbContext?
I am using EF version 6.1.1
I've also gotten this problem in my EF one-many deals where the one has a List of the many property and my mapping didn't specify that property. For example take:
public class Notification
{
public long ID { get; set; }
public IList<NotificationRecipient> Recipients { get; set; }
}
then
public class NotificationRecipient
{
public long ID { get; set; }
public long NotificationID { get; set; }
public Notification Notification { get; set; }
}
Then in my mapping, the way that caused the Exception (the incorrect way):
builder.HasOne(x => x.Notification).WithMany()
.HasForeignKey(x => x.NotificationID);
What fixed it (the correct way) was specifying the WithMany property:
builder.HasOne(x => x.Notification).WithMany(x => x.Recipients)
.HasForeignKey(x => x.NotificationID);
Hi After spending some time I could fix this problem by using ForeignKey attribute on public virtual Department Department { get; set; } property of Employee class.
Please see below code.
[Table("Employee")]
public class Employee
{
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("Name")]
public string Name { get; set; }
[Column("Department_ID")]
public int Department_ID { get; set; }
[ForeignKey("Department_ID")]
public virtual Department Department { get; set; }
}
This fixed my problem. Are there any other solution to fix this? Using fluent API?
For me, the issue was resolved by removing a (duplicate?) virtual property.
Using the OP's example:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Department_ID { get; set; }
public virtual Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Turns into:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Department_ID { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
In my case I added a virtual property on top of the auto generated property
I fixed it by adding the NotMapped attribute to my property, or you could configure with fluent api
public partial class Control
{
[NotMapped]
public virtual ICollection<Control> Children { get => this.InverseParent; set => this.InverseParent = value; }
}
I had the same error, my issue was the FK was a long but I had it as an int in the model. EF generated a new column because it didn't match types on the FK so it assumed they weren't the same and went ahead with making another one but putting 1 at the end because there was already one with the proper name. Making sure the types matched resolved the issue for me.
This can be fixed simply by putting [NotMapped] annotation on your virtual properties.
public class Employee
{
[ForeignKey("Department")]
public int Department_ID
[NotMapped]
public virtual Department Department { get; set; }
}
And in you modelBuilder:
modelBuilder.Entity<Employee>(entity =>
{
entity.HasOne(e => e.Department);
});
Just flip this around if you want to call by Department.
We use the [NotMapped] annotation so that EF Core will disregard it when looking at your database.

Resources