How to make an Abp.Dapper multiple-table query? - dapper

I have an entity model Person -> Phones(collection) and the following code
Person
[Table("PbPersons")]
public class Person: FullAuditedEntity, IMustHaveTenant
{
public const int MaxNameLength = 32;
public const int MaxSurnameLength = 32;
public const int MaxEmailAddressLength = 255;
public virtual int TenantId { get; set; }
[Required]
[MaxLength(MaxNameLength)]
public virtual string Name { get; set; }
[Required]
[MaxLength(MaxSurnameLength)]
public virtual string Surname { get; set; }
[MaxLength(MaxEmailAddressLength)]
public virtual string EmailAddress { get; set; }
public virtual ICollection<Phone> Phones { get; set; }
}
Phones
[Table("PbPhones")]
public class Phone : CreationAuditedEntity<long>
{
public const int MaxNumberLength = 16;
[ForeignKey("PersonId")]
public virtual Person Person { get; set; }
public virtual int PersonId { get; set; }
[Required]
public virtual PhoneType Type { get; set; }
[Required]
[MaxLength(MaxNumberLength)]
public virtual string Number { get; set; }
}
Old Application Tier with Abp.EF
This code returns a list of persons including phones
using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.Dapper.Repositories;
using Abp.Domain.Repositories;
using Abp.Extensions;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
public class PersonAppService : WebAppServiceBase, IPersonAppService
{
private readonly IRepository<Person> _personRepository;
private readonly IRepository<Phone, long> _phoneRepository;
public PersonAppService(IRepository<Person> personRepository,
IRepository<Phone, long> phoneRepository)
{
_personRepository = personRepository;
_phoneRepository = phoneRepository;
}
public ListResultDto<PersonListDto> GetPeople(GetPeopleInput input)
{
var persons = _personRepository
.GetAll()
.Include(p => p.Phones)
.WhereIf(
!input.Filter.IsNullOrEmpty(),
p => p.Name.Contains(input.Filter) ||
p.Surname.Contains(input.Filter) ||
p.EmailAddress.Contains(input.Filter)
)
.OrderBy(p => p.Name)
.ThenBy(p => p.Surname)
.ToList();
return new ListResultDto<PersonListDto>(Mapper.Map<List<Person>, List<PersonListDto>>(persons));
}
}
New Application Tier with Abp.Dapper
However when I use Abp.Dapper the GetAll method does not allow to include any method like Include/WhereIf, I know that is because it implements IEnumerable interface. I need that also returns the list of phones for each Person. What should I do?, I tried with linq (include, it is not available) with not success.
using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.Dapper.Repositories;
using Abp.Domain.Repositories;
using Abp.Extensions;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
public class PersonAppService : WebAppServiceBase, IPersonAppService
{
private readonly IDapperRepository<Person> _personRepository;
private readonly IRepository<Phone, long> _phoneRepository;
public PersonAppService(IDapperRepository<Person> personRepository,
IRepository<Phone, long> phoneRepository)
{
_personRepository = personRepository;
_phoneRepository = phoneRepository;
}
public ListResultDto<PersonListDto> GetPeople(GetPeopleInput input)
{
var persons = _personRepository.GetAll()
.OrderBy(p => p.Name)
.ThenBy(p => p.Surname)
.ToList();
return new ListResultDto<PersonListDto>(Mapper.Map<List<Person>, List<PersonListDto>>(persons));
}
}

See the test code that fetches the navigation data as well.
public async Task Should_Include_Navigation_Properties_If_Requested()
{
using (var uow = _uowManager.Begin())
{
var post = await _postRepository.GetAllIncluding(p => p.Blog).FirstAsync();
post.Blog.ShouldNotBeNull();
post.Blog.Name.ShouldBe("test-blog-1");
await uow.CompleteAsync();
}
}
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/test/Abp.EntityFrameworkCore.Tests/Tests/Repository_Tests.cs#L80

Related

Why I couldn't call Model inside my Controller

I have one problem and I have no idea what to do. I am try couple of method but nothing works for me. I have model Patient
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace BergClinic.Models
{
public class Patient
{
[Key]
public int Id { get; set; }
[Required]
[Display(Name ="First Name")]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
[MaxLength(50)]
public string LastName { get; set; }
[Required]
[Display(Name = "Date of Birth")]
public DateTime DateOfBirth { get; set; }
[Display(Name = "Address")]
[MaxLength(50)]
public string Address { get; set; }
[Display(Name = "Phone Number")]
public string PhoneNumber { get; set; }
[Display(Name = "Gender")]
public PatientGender Gender { get; set; }
}
public enum PatientGender
{
[Display(Name ="Male")]
Male,
[Display(Name = "Female")]
Female,
Unknown
}
}
And IPatientRepository and PatientRepository which contains following logic:
using BergClinic.DataAccess.Data;
using BergClinic.DataAccess.Repository.IRepository;
using BergClinic.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BergClinic.DataAccess.Repository
{
public class PatientRepository : Repository<Patient>, IPatientRepository
{
private readonly ApplicationDbContext _db;
public PatientRepository(ApplicationDbContext db) : base(db)
{
_db = db;
}
public void Update(Patient patient)
{
var objDb = _db.Patients.FirstOrDefault(s => s.Id == patient.Id);
if (objDb != null)
{
objDb.FirstName = patient.FirstName;
objDb.LastName = patient.LastName;
objDb.DateOfBirth = patient.DateOfBirth;
objDb.Address = patient.Address;
objDb.PhoneNumber = patient.PhoneNumber;
objDb.Gender = patient.Gender;
_db.SaveChanges();
}
}
}
}
And here is IPatientRepository which containt Update method
using BergClinic.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace BergClinic.DataAccess.Repository.IRepository
{
public interface IPatientRepository : IRepository<Patient>
{
void Update(Patient patient);
}
}
Once I creat this in my Admin Area in PatientController I want to initialize object Patient but I couldn't. I want to create Upsert method for Update and Insert Patient but whatever I type It doesn't see my model only what it is see is Patient workspace
'Patient' is a namespace but is used like a type
using BergClinic.DataAccess.Repository.IRepository;
using Microsoft.AspNetCore.Mvc;
using BergClinic.Models;
namespace BergClinic.Areas.Admin.Controllers
{
[Area("Admin")]
public class PatientController : Controller
{
private readonly IUnitOfWork _unitOfWork;
public PatientController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public IActionResult Index()
{
return View();
}
public IActionResult Upsert(int? Id)
{
Patient patient = new Areas.Patient
return View();
}
#region API_CALLS
[HttpGet]
public IActionResult GetAll()
{
var patient = _unitOfWork.Patient.GetAll();
return Json(new { data = patient });
}
#endregion
}
}
Just to notice I use RepositoryPatten and thee way arhitecture. I try to restart Visual Studio but seem nothing happened, I try to remove Project Reference and try to add again, but noting happened againg.
Here is couple of sceen of my ProjectStructures which you can check:

How to had foreign key

(lol).
For who I know I been working (for training) on a pokemon database.
I have a problem with my database creation.
I have 18 different types :
"Acier"
"Combat"
"Dragon"
"Eau"
"Electrik"
"Fee"
"Feu"
"Glace"
"Insecte"
"Normal"
"Plante"
"Poison"
"Psy"
"Roche"
"Sol"
"Spectre"
"Tenebre"
"Vol"
A pokemon can have one OR two types :
Example :
Pickachu type Electrik.
Bulbizarre type Plante and Poison.
I try to use foreign key for create my database but I don't know how to do it please help.
I have a Pokemon model class:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Pokemon.Models
{
[Table("Pokemons")]
public class Pokemon
{
[Key]
public int PokemonId { get; set; }
[StringLength(30)]
public string PokemonName { get; set; }
[StringLength(30)]
public string PokemonUsName { get; set; }
[StringLength(30)]
public string PokemonDeName { get; set; }
[StringLength(30)]
public string PokemonJpName { get; set; }
public string PokemonDescription { get; set; }
public int PokemonRate { get; set; }
public string PokemonImage { get; set; }
public int PokemonTypeId { get; set; }
[ForeignKey("PokemonType")]
public virtual PokemonType PokemonType { get; set; }
}
}
And a PokemonType model class:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace Pokemon.Models
{
[Table("PokemonTypes")]
public class PokemonType
{
[Key]
public int TypeId { get; set; }
[StringLength(8)]
public string Type { get; set; }
public virtual ICollection<Pokemon> Pokemons { get; set; }
}
}
I hope I asked my question clearly.
I want to make a pokemon "Bulbizarre" whit Type1Id = 7 //7for plante
And type2Id = 24 //24 for poison.
But some of them have only one type : type1Id 13 //13 for Electrik
and Type2Id = null. //because no second type.
Table PokemonTypes screen from databse
I made it !
I made databse first and scaffold it!
Database diagram
this make me 3 model class with everythings correct inside
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DataModels.Models
{
public partial class Pokemons
{
public Pokemons()
{
PokemonsPokemonTypes = new HashSet<PokemonsPokemonTypes>();
}
public int Id { get; set; }
public int Num { get; set; }
[Required]
[StringLength(30)]
public string Name { get; set; }
[StringLength(30)]
public string UsName { get; set; }
[StringLength(30)]
public string JpName { get; set; }
public int? Rate { get; set; }
public string Description { get; set; }
public string Image { get; set; }
[StringLength(50)]
public string Kind { get; set; }
public double? Height { get; set; }
public double? Weight { get; set; }
[InverseProperty("Pokemon")]
public virtual ICollection<PokemonsPokemonTypes> PokemonsPokemonTypes { get; set; }
}
}
using System.ComponentModel.DataAnnotations.Schema;
namespace DataModels.Models
{
public partial class PokemonsPokemonTypes
{
public int PokemonId { get; set; }
public int TypeId { get; set; }
[ForeignKey("PokemonId")]
[InverseProperty("PokemonsPokemonTypes")]
public virtual Pokemons Pokemon { get; set; }
[ForeignKey("TypeId")]
[InverseProperty("PokemonsPokemonTypes")]
public virtual PokemonTypes Type { get; set; }
}
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DataModels.Models
{
public partial class PokemonTypes
{
public PokemonTypes()
{
PokemonsPokemonTypes = new HashSet<PokemonsPokemonTypes>();
}
public int Id { get; set; }
[StringLength(8)]
public string Type { get; set; }
[InverseProperty("Type")]
public virtual ICollection<PokemonsPokemonTypes> PokemonsPokemonTypes { get; set; }
}
}
This works pretty well!
One Product (Pokemon) can have Multipke Categories (Type).
There is one bug in your code listing, The [ForeignKey] attribute can be put on the foreign key property or the navigation property, but if on the Navigation property it needs to be set to point to the FK field name. (PokemonTypeId)
Two options to support more than one pokemon type:
If a Pokemon can have 1 or 2, but at max 2 types, then you can add two PokemonType references to the Pokemon. For example:
public int PrimaryPokemonTypeId { get; set; }
[ForeignKey("PrimaryPokemonTypeId")]
public virtual PokemonType PrimaryPokemonType { get; set; }
public int? SecondaryPokemonTypeId { get; set; }
[ForeignKey("SecondaryPokemonTypeId")]
public virtual PokemonType SecondaryPokemonType { get; set; }
If you can possibly refer to more than 2 types then you can adopt a many-to-many relationship rather than a 1 to many relationship. You can restrict the # of related types in code to two if you wish, but the data schema will support any number of types on a Pokemon.
A Pokemon would hold a collection of types. This means introducing a linking table. I.e. A PokemonTypePokemon.
If this is EF Core then you need to define the linking table:
public class Pokemon
{
// ... Pokemon fields...
public virtual ICollection<PokemonTypePokemon> PokemonTypes { get; set; } = new List<PokemonTypePokemon>();
}
public class PokemonType
{
// Classifications...
public virtual ICollection<PokemonTypePokemon> Pokemon { get; set; } = new List<PokemonTypePokemon>();
}
public class PokemonTypePokemon
{
public virtual Pokemon { get; set; }
public virtual PokemonType { get; set; }
}
This will require a little wiring up in the DbContext to associate the linking table. In the DbContext's OnModelCreating handler...
// EF Core
modelBuilder.Entity<Pokemon>()
.HasMany(x => x.PokemonTypes)
.WithOne(x => x.Pokemon)
.HasForeignKey("PokemonId");
modelBuilder.Entity<PokemonType>()
.HasMany(x => x.Pokemon)
.WithOne(x => x.PokemonType)
.HasForeignKey("PokemonTypeId");
You will likely need to configure the PK for the PokemonTypePokemon table as well to use a composite key on PokemonId & PokemonTypeId.
With EF6 you can map a HasMany().WithMany(), without needing the map the joining table in many cases. This should give you some idea where to start with the mapping change and joining table. Do a bit of Google-fu on EF Many-to-Many to refine.

Dapper custom SqlMapper.TypeHandler Parse method not called

I created a SqlMapper.TypeHandler to map a Customer object into a CreditAccount class as follows:
public class CustomerTypeHandler : SqlMapper.TypeHandler<Customer>
{
public override Customer Parse(object value)
{
throw new NotImplementedException();
}
public override void SetValue(IDbDataParameter parameter, Customer
value)
{
throw new NotImplementedException();
}
}
public class CreditAccount
{
public int AccountId { get; set; }
public Customer Customer{ get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
When I connect to the DB and call a sproc, the CustomerTypeHandler Parse method is never called and my CreditAccount object is populated with the AccountId only. The Customer object is null.
I am calling it as follows:
public async Task<CreditAccount> GetCreditAccount(int accountId)
{
var sql = "MY PROC NAME HERE";
var parameters = new DynamicParameters();
parameters.Add("#AccountId", accountId);
SqlMapper.AddTypeHandler(new CustomerTypeHandler());
using (IDbConnection connection = Connection)
{
connection.Open();
var account = await connection.QueryFirstAsync<CreditAccount>(sql, parameters, commandType: CommandType.StoredProcedure);
return account;
}
}
}
I placed a breakpoint in the Parse method and it is never called.
The database connection works, and I am getting the AccountId.
My environment;
.NET Core 2.2
Dapper 1.50.5
The code is simple enough. I get not exceptions. Any ideas?
A year has passed and now there is no this error in Dapper 2.0.30.
I checked it on jsonb columns in Postgres.
using Dapper;
using Newtonsoft.Json;
using Npgsql;
using System;
using System.Data;
public class CreditAccount
{
public int AccountId { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class CustomerJsonObjectTypeHandler : SqlMapper.TypeHandler<Customer>
{
public override void SetValue(IDbDataParameter parameter, Customer value)
{
parameter.Value = (value == null)
? (object)DBNull.Value
: JsonConvert.SerializeObject(value);
parameter.DbType = DbType.String;
}
public override Customer Parse(object value)
{
return JsonConvert.DeserializeObject<Customer>(value.ToString());
}
}
Example using this classes - all work fine.
static void Main(string[] args)
{
using (var connection = GetDefaultConnection())
{
connection.Open();
var customer = new Customer
{
FirstName = "Gaday",
LastName = "Ivanova",
MiddleName = "Petrovich"
};
var jsonData = JsonConvert.SerializeObject(customer);
var strQuery = $"SELECT 10500 as AccountId,'{jsonData}'::jsonb as Customer";
SqlMapper.AddTypeHandler(new CustomerJsonObjectTypeHandler());
try
{
var data = connection.QueryFirst<CreditAccount>(strQuery);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}

MVC Invalid object name 'dbo.Staffs' error

I got the error Invalid object name 'dbo.Staffs'. but I'm not sure why. I actually deleted and recreated my database with EF because previously I had other errors. But I'm quite sure I recreated it correctly because I've done it in the same way for other programs and it works fine.
.edmx database diagram
Controller
private StaffPortalDBEntities1 db = new StaffPortalDBEntities1();
SqlConnection cnn = new SqlConnection(ConfigurationManager.ConnectionStrings["StaffPortalDBConnectionString"].ConnectionString);
[Authorize]
public ActionResult Index()
{
var userEmail = User.Identity.Name;
var model = db.Staffs.Where(i => i.Email == userEmail).Include("Histories").Include("CurrentApplications").FirstOrDefault();
return View(model);
}
I got the error is for the line var model = db.Staffs.Where(i => i.Email == userEmail).Include("Histories").Include("CurrentApplications").FirstOrDefault();
Generated Staff class
public partial class Staff
{
public Staff()
{
this.Histories = new HashSet<History>();
this.CurrentApplications = new HashSet<CurrentApplication>();
}
public int StaffID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Nullable<decimal> AllocatedLeave { get; set; }
public Nullable<int> BalanceLeave { get; set; }
public virtual ICollection<History> Histories { get; set; }
public virtual ICollection<CurrentApplication> CurrentApplications { get; set; }
}
Try this:
var model = db.Staffs.Where(i => i.Email == userEmail).Include(x=>x.Histories).Include(x=>x.CurrentApplications).FirstOrDefault();

Master/Detail DataGridViews not populating Details DataGridView

I'm using the following technologies: WinForms, Entity Framework 4.4 (5.0 on .NET 4.0), DBContext
I have (what I think is) a very simple Master/Details form, that actually worked just fine before I upgraded from EF 4.0 to EF 4.4. Now, for some reason, the Details DataGridView simply doesn't populate with any data!
Here's my auto-generated schema code:
public partial class RoadMapping
{
public RoadMapping()
{
this.RoadCharacteristics = new HashSet<RoadCharacteristic>();
}
public int RoadMappingID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public virtual ICollection<RoadCharacteristic> RoadCharacteristics { get; set; }
}
public partial class RoadCharacteristic
{
public RoadCharacteristic()
{
}
public int RoadCharacteristicID { get; set; }
public int RoadMappingID { get; set; }
public string Value { get; set; }
public string Description { get; set; }
public virtual RoadMapping RoadMapping { get; set; }
}
Here's my code that was working with EF 4.0:
SATContext = new SafetyAssessmentToolEntities();
dataGridViewMappings.DataSource = bindingSourceMappings;
dataGridViewDetails.DataSource = bindingSourceDetails;
bindingSourceMappings.DataSource = SATContext.RoadMappings;
bindingSourceDetails.DataSource = bindingSourceMappings;
bindingSourceDetails.DataMember = "RoadCharacteristics";
Here's the code that isn't working with EF 4.4:
SATContext = new SafetyAssessmentToolEntities();
SATContext.RoadMappings.Load();
SATContext.RoadCharacteristics.Load();
dataGridViewMappings.DataSource = bindingSourceMappings;
dataGridViewDetails.DataSource = bindingSourceDetails;
bindingSourceMappings.DataSource = SATContext.RoadMappings.Local.ToBindingList();
bindingSourceDetails.DataSource = bindingSourceMappings;
bindingSourceDetails.DataMember = "RoadCharacteristics";
Please note that bindingSourceMappings and bindingSourceDetails are declared by the form designer.
I know there are a lot of more advanced and code-intensive ways to make this work, but I can't understand why this very simple way of doing it won't work anymore.
Any suggestions?
public partial class SafetyAssessmentToolEntities : DbContext
{
public SafetyAssessmentToolEntities()
: base("name=SafetyAssessmentToolEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<RoadCharacteristic> RoadCharacteristics { get; set; }
public DbSet<RoadMapping> RoadMappings { get; set; }
}

Resources