Dapper.NET multi mapping TSecond Deserializer is null - c#

I'm trying to perform a very standard multi mapping query using Dapper, and I'm getting the following error. I also get another error occasionally when this seems to work, but I'm unable to reproduce it at the moment. I'll append it to this post if/when the first problem is solved.
Here is the query code:
const string storedProc = "dbo.GetStopsForRouteID";
var stops = conn.Query<RouteStop, MapLocation, RouteStop>(
storedProc, (stop, loc) =>
{
stop.Location = loc;
return stop;
}, new { RouteID = routeId }, commandType: CommandType.StoredProcedure);
In Dapper.cs on line 498:
var deserializer2 = (Func<IDataReader, TSecond>)info.OtherDeserializers[0];
info.OtherDeserializers is null which causes a NullReferenceException.
This is the guts of the stored procedure:
SELECT
RouteStops.StopID,
RouteStops.Name,
RouteStops.Description,
RouteStops.IsInbound,
RouteStops.Location.Lat as Latitude,
RouteStops.Location.Long as Longitude
FROM dbo.Routes
INNER JOIN dbo.StopsOnRoute ON
Routes.RouteID = StopsOnRoute.RouteID
INNER JOIN dbo.RouteStops ON
StopsOnRoute.StopID = RouteStops.StopID
WHERE Routes.RouteID = #RouteID
ORDER BY StopsOnRoute.SequenceNumber
I've had an extensive look at the dapper code but I can't find anything that seems out of place other than that TFirst's deserialiser isn't null, but TSecond's is. Could there be a problem when it creates TSecond's deserializer that leaves it as null?
Here are the types:
public class MapLocation
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class RouteStop {
public int StopID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsInbound { get; set; }
public MapLocation Location { get; set; }
}

Probably the main problem here is that you haven't told it how to "split"; try adding the parameter:
splitOn: "Latitude"
without that, as far as dapper can see there is no second result portion (it splits on Id by default).

Related

AutoMapper Giving Error, While Mapping A Table That Contains A List. C#

TLDR - The error is:
The query has been configured to use 'QuerySplittingBehavior.SplitQuery' and contains a collection in the 'Select' call, which could not be split into separate query. Please remove 'AsSplitQuery' if applied or add 'AsSingleQuery' to the query.
I am developing a backend with EntityFrameworkCore in C#.
My table classes are like this:
public class MainTable : BasicAggregateRoot<int>
{
public MainTable()
{
this.Operations = new HashSet<OperationTable>();
}
public long? RecId { get; set; }
public int FormStatus { get; set; }
public virtual ICollection<OperationTable> Operations { get; set; }
}
public class OperationTable : BasicAggregateRoot<int>
{
public OperationTable()
{
this.Works = new HashSet<Work>(); //Not important things
this.Materials = new HashSet<Material>(); //Not important things
}
public string ServiceType { get; set; }
}
And my DTOs are like this:
public class MainDto : EntityDto<int>
{
public long? RecId { get; set; }
public int FormStatus { get; set; }
public List<OperationDto> Operations { get; set; }
}
public class OperationDto
{
public string ServiceType { get; set; }
}
I created maps this way:
CreateMap<MainTable, MainDto>().ReverseMap();
CreateMap<OperationTable, OperationDto>().ReverseMap();
When I commit the mapping by:
class Service{
IRepository<MainTable, int> _mainTableRepository;
Service(IRepository<MainTable, int> mainTableRepository){
_mainTableRepository = mainTableRepository;
}
List<MainDto> All()
{
var result = mainTableRepository.Include(p => p.Operations)
.ProjectTo<MainDto>(ObjectMapper.GetMapper().ConfigurationProvider) //Here is the problem.
.ToList();
return result;
}
}
I get the error on the top.
When I get rid of the List from mainDto, error does not occur, but I don't have the result that I want either.
What might be the problem? I couldn't find an answer.
In that source you can find the differences between single and split query: https://learn.microsoft.com/en-us/ef/core/querying/single-split-queries
The problem is (I guess) IRepository.Include uses split query by default. But (again I guess) AutoMapper is not configured to use split query, it works with single queries.
We need to change the query type before mapping like this:
var result = mainTableRepository.Include(p => p.Operations)
.AsSingleQuery() //This solved the problem
.ProjectTo<MainDto>(ObjectMapper.GetMapper().ConfigurationProvider)
.ToList();

store complex object in db using dapper

So I'm trying to store a record in the databse using dapper. I'm passing an object to the method where I have my query to store the recorde. Let me be more clear. Below is my model :
public class Foo
{
public long FooId { get; set; }
public Guid Foo2ID { get; set; }
public string Status { get; set; }
public Person Person { get; set; } = new Person();
}
public class Person
{
public string Type { get; set; }
public string Character { get; set; }
public DateTime Test { get; set; }
}
And this is my query :
public async Task<ActionResult> Create(Foo f)
{
using (var connection = _dbAccess.CreateConnection())
{
var sqlStatement = #"
INSERT INTO ReportRequests
(FooId
,Foo2Id
,Person
,Status)
VALUES
(#FooId
#,Foo2Id
#,Person
#,Status)";
await connection.ExecuteAsync(sqlStatement, f);
};
return Ok();
}
I'm trying to save a json in the Person column in the database. But I get this error :
The member x of type x cannot be used as a parameter value
Can anyone please give me an idea on how I can approach to this problem. It would be very helpful.
Thank you a lot :)
enter code hereFirst of all, you should consider whether you can use LINQ-like queries with dapper. It makes it both more readable and avoids having issues like that.
Back to your problem, from the code you posted it looks like you've misplaced the comas after the # symbol #,Foo2Id :
(#FooId
#,Foo2Id
#,Person
#,Status)
It should be:
(#FooId
#Foo2Id,
#Person,
#Status)

Querying for object based on property of inside object

I have some objects stored in a LiteDB database. I'm trying to get a result of all CostBasisTradeSessionObjects that include Marked objects with a particular name, MarkedNameString. I find the Marked object easily enough, but I dont now how to query for object in object.
public string Marked
{
public ObjectId MarkedId { get; set; }
public String Name { get; set; }
}
public class CostBasisTradeSessionObject
{
public ObjectId CostBasisTradeSessionId { get; set; }
public bool IsActive { get; set; }
public DateTime SessionStarted { get; set; }
public DateTime SessionClosed { get; set; }
public Marked Marked { get; set; }
}
using (var db = new LiteDatabase(#"CostBasesTradeSessionsDatabase.db"))
{
var costBasisTradeSessionObjects = db.GetCollection("costBasisTradeSessionObjects");
Marked marked = db.GetCollection<Marked>("markeds").Find(Query.EQ("Name", "<MarkedNameString>")).Single();
}
So I try to get an result with CostBasisTradeSessionObject objects that includes the marked object returned in var marked.
So I tried a couple of things
var cb = costBasisTradeSessionObjects.Include(x => x.Marked).Equals(marked);
and justing jusing the MarkedNameString directory
var results = costBasisTradeSessionObjects.(Query.("Marked.name", "MarkedNameString"));
or
var results = costBasisTradeSessionObjects.Find(x => x.Marked.Name.Equals("MarkedNameString"));
but all the things I tried return an empty result or dont work.
Regards
I believe you're looking for the Where() method. You can filter your search by your Name property, and return an IEnumerable of CostBasisTradeSessionObject.
var results = costBasisTradeSessionObjects
.Where(x => x.Marked.Name == "MarkedNameString");

Automapper object to object

UPDATE 2:
It seems to be a problem with the containing value in Format.
The Debugger shows me "{{MinValue: 6, MaxValue:44}}" for property Format.
When I change this to .Format = new MyFormat(6,44) it works fine.. so maybe its a problem with the web api 2 I'm using ... but anyway it should just push the source-object to the destination-object ;)
UPDATE:
I removed the originial post and added some real code here. the other classes were only dummies to explain my problem.
By the way: The Problem only occurrs when I add the "Format" Property of type object to the class. otherwise it works fine...
I tried to create an simple example to keep things easy ;) but here are parts of my real code
private void InitAutoMapper()
{
if (_mappingInitialized) //static init
return;
//will prevent Mapper to try to map constructor-parameters. With Copy-Constructors otherwise this would cause infiniteloops and stackoverflowexceptions.
Mapper.Configuration.DisableConstructorMapping();
//From Client to Server
...
Mapper.CreateMap<SDK.Model.Form.Field, Field>();
//From Server to Client
...
Mapper.CreateMap<Field, SDK.Model.Form.Field>();
...
//checks if the configuration was correct.
Mapper.AssertConfigurationIsValid();
_mappingInitialized = true;
}
and this is the call
public string CreateEntityField(SDK.Model.Form.Field field)
{
var mappedField = Mapper.Map<Field>(field);
...
}
my Field-class looks like this (source and destination classes look exactly the same. they are just separated in two different namespaces to have the possibility to add different properties in the future.
public class Field : IEntityRelatedEntity, IModificationTrackObject
{
public string Id { get; set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
public int Status { get; set; }
public int SubStatus { get; set; }
...many more fields
public FieldType Type { get; set; }
public object Format { get; set; }
public FieldRequirement Required { get; set; }
public Field()
{
Name = null;
Description = string.Empty;
DisplayName = null;
Type = FieldType.Text;
Required = FieldRequirement.Optional;
CreatedOn = new DateTime();
ModifiedOn = new DateTime();
}
public Field(Field copy)
{
Id = copy.Id;
Format = copy.Format;
...
}
}
and this is the exception I get (sorry parts of this exception are in german but it means that the source and destination-Type of Format are not the same)
exception =
{
Mapping types:\r\nJObject -> JObject
Newtonsoft.Json.Linq.JObject -> Newtonsoft.Json.Linq.JObject
Destination path:
Field.Format.Format
Source value:
{
"MinValue": "2",
"MaxValue": "100"
}
}
The Mapper should just copy the source to the destination for property "Format" and shouldn't care about whats in there...
one more thing: when i Ignore the Format-Property everything works fine, too.
Mapper.CreateMap<SDK.Model.Form.Field, Field>().ForMember(m => m.Format, opt => opt.Ignore());

Reuse index transformer expressions fails on Id

I'm looking to be able to reuse some of the transform expressions from indexes so I can perform identical transformations in my service layer when the document is already available.
For example, whether it's by a query or by transforming an existing document at the service layer, I want to produce a ViewModel object with this shape:
public class ClientBrief
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
// ellided
}
From this document model:
public class Client
{
public int Id { get; private set; }
public CompleteName Name { get; private set; }
public Dictionary<EmailAddressKey, EmailAddress> Emails { get; private set; }
// ellided
}
public class CompleteName
{
public string Title { get; set; }
public string GivenName { get; set; }
public string MiddleName { get; set; }
public string Initials { get; set; }
public string Surname { get; set; }
public string Suffix { get; set; }
public string FullName { get; set; }
}
public enum EmailAddressKey
{
EmailAddress1,
EmailAddress2,
EmailAddress3
}
public class EmailAddress
{
public string Address { get; set; }
public string Name { get; set; }
public string RoutingType { get; set; }
}
I have an expression to transform a full Client document to a ClientBrief view model:
static Expression<Func<IClientSideDatabase, Client, ClientBrief>> ClientBrief = (db, client) =>
new ClientBrief
{
Id = client.Id,
FullName = client.Name.FullName,
Email = client.Emails.Select(x => x.Value.Address).FirstOrDefault()
// ellided
};
This expression is then manipulated using an expression visitor so it can be used as the TransformResults property of an index (Client_Search) which, once it has been generated at application startup, has the following definition in Raven Studio:
Map:
docs.Clients.Select(client => new {
Query = new object[] {
client.Name.FullName,
client.Emails.SelectMany(x => x.Value.Address.Split(new char[] {
'#'
})) // ellided
}
})
(The Query field is analysed.)
Transform:
results.Select(result => new {
result = result,
client = Database.Load(result.Id.ToString())
}).Select(this0 => new {
Id = this0.client.__document_id,
FullName = this0.client.Name.FullName,
Email = DynamicEnumerable.FirstOrDefault(this0.client.Emails.Select(x => x.Value.Address))
})
However, the transformation expression used to create the index can then also be used in the service layer locally when I already have a Client document:
var brief = ClientBrief.Compile().Invoke(null, client);
It allows me to only have to have one piece of code that understands the mapping from Client to ClientBrief, whether that code is running in the database or the client app. It all seems to work ok, except the query results all have an Id of 0.
How can I get the Id property (integer) properly populated in the query?
I've read a number of similar questions here but none of the suggested answers seem to work. (Changing the Ids to strings from integers is not an option.)
I have a hard time following your sample fully, Really the best way to dig in to this would be with a failing self-contained unit test.
Nonetheless, let's see if I can pull out the important bits.
In the transform, you have two areas where you are working with the id:
...
client = Database.Load(result.Id.ToString())
...
Id = this0.client.__document_id,
...
The result.Id in the first line and the Id = in the second line are expected to be integers.
The Database.Load() expects a string document key and that is also what you see in __document_id.
The confusion comes from Raven's documentation, code, and examples all use the terms id and key interchangeably, but this is only true when you use string identifiers. When you use non-string identifiers, such as ints or guids, the id may be 123, but the document key is still clients/123.
So try changing your transform so it translates:
...
client = Database.Load("clients/" + result.Id)
...
Id = int.Parse(this0.client.__document_id.Split("/")[1]),
...
... or whatever the c# equivalent linq form would be.

Categories