Should I give up on Integer Ids for RavenDB? - c#-4.0

Update: Here's a gist that more fully demonstrates the issue https://gist.github.com/pauldambra/5051550
Ah, more update... If I make the Id property on the Mailing class a string then it all works. Should I just give up on integer ids?
I have 2 models
public class Mailing
{
public int Id { get; set; }
public string Sender { get; set; }
public string Subject { get; set; }
public DateTime Created { get; set; }
}
public class Recipient
{
public Recipient()
{
Status = RecipientStatus.Pending;
}
public RecipientStatus Status { get; set; }
public int MailingId { get; set; }
}
On my home page I want to grab the last 10 mailings. With a count of their recipients (eventually with a count of different status recipients but...)
I have made the following index
public class MailingWithRecipientCount : AbstractMultiMapIndexCreationTask<MailingWithRecipientCount.Result>
{
public class Result
{
public int MailingId { get; set; }
public string MailingSubject { get; set; }
public string MailingSender { get; set; }
public int RecipientCount { get; set; }
}
public MailingWithRecipientCount()
{
AddMap<Mailing>(mailings => from mailing in mailings
select new
{
MailingId = mailing.Id,
MailingSender = mailing.Sender,
MailingSubject = mailing.Subject,
RecipientCount = 0
});
AddMap<Recipient>(recipients => from recipient in recipients
select new
{
recipient.MailingId,
MailingSender = (string) null,
MailingSubject = (string)null,
RecipientCount = 1
});
Reduce = results => from result in results
group result by result.MailingId
into g
select new
{
MailingId = g.Key,
MailingSender = g.Select(m => m.MailingSender)
.FirstOrDefault(m => m != null),
MailingSubject = g.Select(m => m.MailingSubject)
.FirstOrDefault(m => m != null),
RecipientCount = g.Sum(r => r.RecipientCount)
};
}
}
I query using
public ActionResult Index()
{
return View(RavenSession
.Query<RavenIndexes.MailingWithRecipientCount.Result, RavenIndexes.MailingWithRecipientCount>()
.OrderByDescending(m => m.MailingId)
.Take(10)
.ToList());
}
And I get:
System.FormatException: System.FormatException : Input string was not
in a correct format. at System.Number.StringToNumber(String str,
NumberStyles options, NumberBuffer& number, NumberFormatInfo info,
Boolean parseDecimal)
Any help appreciated

Yes, integer ids are a pain. This is mainly because Raven always stores a full string document key, and you have to think about when you are using the key or your own id and translate appropriately. When reducing, you also need to align the int and string data types.
The minimum to get your test to pass is:
// in the "mailings" map
MailingId = mailing.Id.ToString().Split('/')[1],
// in the reduce
MailingId = g.Key.ToString(),
However - you could make your index a whole lot smaller and perform better by taking the sender and subject strings out of it. You can just put them in with a transform.
Here is a simplified complete index that does the same thing.
public class MailingWithRecipientCount : AbstractIndexCreationTask<Recipient, MailingWithRecipientCount.Result>
{
public class Result
{
public int MailingId { get; set; }
public string MailingSubject { get; set; }
public string MailingSender { get; set; }
public int RecipientCount { get; set; }
}
public MailingWithRecipientCount()
{
Map = recipients => from recipient in recipients
select new
{
recipient.MailingId,
RecipientCount = 1
};
Reduce = results => from result in results
group result by result.MailingId
into g
select new
{
MailingId = g.Key,
RecipientCount = g.Sum(r => r.RecipientCount)
};
TransformResults = (database, results) =>
from result in results
let mailing = database.Load<Mailing>("mailings/" + result.MailingId)
select new
{
result.MailingId,
MailingSubject = mailing.Subject,
MailingSender = mailing.Sender,
result.RecipientCount
};
}
}
As an aside, did you know about the RavenDB.Tests.Helpers package? It provides a simple base class RavenTestBase that you can inherit from that does most all of the legwork for you.
using (var store = NewDocumentStore())
{
// now you have an initialized, in-memory, embedded document store.
}
Also - you probably shouldn't scan the assembly for indexes in a unit test. You might introduce indexes that weren't part of what you were testing. The better route is to create the index indvidually, like this:
documentStore.ExecuteIndex(new MailingWithRecipientCount());

Related

How can i execute filter from our JSON filter JSON?

I have a vue3 datagrid and I want to fill the data in this grid with filter by API. At the same time, I want to send the filter fields in the grid to the API as JSON and execute them according to this filter on the API side. How can I do this with AutoQuery?
[Route("/GetConnectors", "POST")]
public class GetConnectors : QueryDb<Connector>
{
public string Id { get; set; }
public string PageParameterJson { get; set; }
}
public class Connector
{
[PrimaryKey]
[AutoIncrement]
public long PKey { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class PageParameters
{
public string Field { get; set; }
public string Operand { get; set; }
public string Value { get; set; }
public string Type { get; set; }
}
It's an example PageParameter JSON;
[
{
"Field":"Name",
"Operand":"cn"//Contains
"Value":"test",
"Type":"string"
},
{
"Field":"Id",
"Operand":"eq"//Equal
"Value":"2",
"Type":"string"
}
]
public async Task<object> Any(GetConnectors query)
{
using var db = AutoQuery.GetDb(query, base.Request);
var filters = query.PageParameters.FromJson<List<PageParameter>>();
//How can I execute query with for CreateQuery?
var q = AutoQuery.CreateQuery(query, Request, db);
var sql = q.PointToSqlString();
return await AutoQuery.ExecuteAsync(query, q, base.Request, dbConnection);
}
Best Regards
i can't execute dynamic filters from server-side datagrid
The AutoQuery.CreateQuery returns OrmLite's Typed SqlExpression which has a number of filtering options inc .Where(), .And(), .Or(), etc.
So you should be able to populate it with something like:
foreach (var filter in filters)
{
var type = filter.Type switch
{
"string" => typeof(string),
_ => throw new NotSupportedException($"Type {filterType}")
};
var value = filter.Value.ConvertTo(type);
if (filter.Operand == "eq")
{
q.And(filter.Field + " = {0}", value)
}
else if (filter.Operand == "cn")
{
q.And(filter.Field + " LIKE {0}", $"%{value}%")
}
else throw new NotSupportedException(filter.Operand);
}
Note: I've rewritten API to be async as you should never block on async methods.

asp.net core delete everything with certain number

I want to delete all vragen(questions in english) with the classID for example number 5. It is possible to give a vraagID and a classID and the server deletes the question if it matches. But I want to delete all matching questions with only one request. Is this possible?
namespace AspIdentityServer.data
{
public class Vraag :Conversatie
{
public int VraagID { get; set; }
public int classID { get; set; }
public string Titel { get; set; }
public ICollection<Antwoord> Antwoord { get; set; }
public Vak Vak { get; set; }
}
}
--
// DELETE: api/Vraags/1/alles/5
[AllowAnonymous]
[HttpDelete("{id}/alles/{vakID}")]
public async Task<IActionResult> DeleteAlleVragen([FromRoute] int id, int vakID) {
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var vraag = await _context.Vraag.SingleOrDefaultAsync(m => m.VraagID == id);
if (vraag.classID == vakID)
{
_context.Vraag.Remove(vraag);
await _context.SaveChangesAsync();
}
return Ok(vraag);
}
You can always use the ExecuteSqlCommandAsync to execute your custom SQL queries. So write a single delete statements with appropriate where clause which will handle many rows as needed.
Here is a quick example
object[] paramsArray =
{
new SqlParameter
{
ParameterName = "#vraagId",
SqlDbType = SqlDbType.Int,
Value = id
},
new SqlParameter
{
ParameterName = "#classId",
SqlDbType = SqlDbType.Int,
Value = vakID
}
};
const string q = "DELETE FROM VraagWHERE VraagID=#vraagId and ClassId=#classId";
await _context.Database.ExecuteSqlCommandAsync(q, paramsArray);
Update the SQL statemenets and parameters as needed.

Automapper, Mapping one object member type to multiple concrete type

I have this Party class which contains an object data type coming from a service. It can contain two different member types for the Item property.
public class Party
{
public string DMVID {get; set;}
public object Item { get; set; }
}
and this DTO
public class PartyDTO
{
public string DMVID {get; set;}
public BusinessDTO BusinessItem { get; set; }
public IndividualDTO IndividualItem { get; set; }
}
How can I map the output of the Item to BusinessItem or IndividualItem.
I know this one would not work. Mapper.CreateMap<Party, PartyDTO>();
I don't know if conditional mapping can solve this or a resolver like this one.
Hey maybe this will help you out! I tested it, but i am using AutoMapper just for two days!
Allright here are your noted classes!!!
public class Party
{
public string DMVID { get; set; }
public object Item { get; set; }
}
public class PartyDTO
{
public string DMVID { get; set; }
public BuisnessDTO BusinessItem { get; set; }
public IndividualDTO IndividualItem { get; set; }
}
public class BuisnessDTO
{
public int Number
{
get;
set;
}
}
public class IndividualDTO
{
public string Message
{
get;
set;
}
}
and here your is the MapperConfiguration for this current scenario!
// Edit There was no need here for some conditions
AutoMapper.Mapper.CreateMap<Party, PartyDTO>()
.ForMember(dto => dto.BusinessItem, map =>
map.MapFrom(party => party.Item as BuisnessDTO);
)
.ForMember(dto => dto.IndividualItem, map =>
map.MapFrom(party => party.Item as IndividualDTO);
);
// And this is another way to achive the mapping in this scenario
AutoMapper.Mapper.CreateMap<PartyDTO, Party>()
.ForMember(party => party.Item, map => map.MapFrom( dto => (dto.BusinessItem != null) ? (dto.BusinessItem as object) : (dto.IndividualItem as object)));
And i created this sample for it!
Party firstParty = new Party()
{
DMVID = "something",
Item = new BuisnessDTO()
{
Number = 1
}
};
Party secondParty = new Party()
{
DMVID = "something",
Item = new IndividualDTO()
{
Message = "message"
}
};
PartyDTO dtoWithBuisness = AutoMapper.Mapper.Map<PartyDTO>(firstParty);
PartyDTO dtoWithIndividual = AutoMapper.Mapper.Map < PartyDTO>(secondParty);
Party afterParty = AutoMapper.Mapper.Map<Party>(dtoWithBuisness);
afterParty = AutoMapper.Mapper.Map < Party>(dtoWithIndividual);
Of course there are other possibility, but I think thats exactly what you wanted.

AutoMapper failing to map a simple list

I have used automapper for mapping lists in the past, for for some reason it won't work in this case.
public class MyType1 {
public int Id { get; set; }
public string Description { get; set; }
}
public class MyType2 {
public int Id { get; set; }
public string Description { get; set; }
}
public void DoTheMap() {
Mapper.CreateMap<MyType2, MyType1>();
Mapper.AssertConfigurationIsValid();
var theDto1 = new MyType2() { Id = 1, Description = "desc" };
var theDto2 = new MyType2() { Id = 2, Description = "desc2" };
List<MyType2> type2List = new List<MyType2> { theDto1, theDto2 };
List<MyType1> type1List = Mapper.DynamicMap<List<MyType1>>(type2List);
//FAILURE. NO EXCEPTION, BUT ZERO VALUES
List<MyType1> type1List2 =type2List.Select(Mapper.DynamicMap<MyType1>).ToList();
//SUCCESS, WITH LINQ SELECT
}
Change this:
Mapper.DynamicMap<List<MyType1>>(type2List)
To this:
Mapper.Map<List<MyType1>, List<MyType2>>(type2List);
DynamicMap is only if you don't know the type at compile time - for things like anonymous types.

ValueInjecter question

After working with AutoMapper I came across ValueInjecter on this site. I am trying it out but I am stuck on what is probably a very simple scenario.
But before I dig into the code sample, does anyone know if ValueInjecter works in a Medium-Trust web environment? (Like Godaddy?)
Ok, onto the code! I have the following models:
public class NameComponent
{
public string First { get; set; }
public string Last { get; set; }
public string MiddleInitial { get; set; }
}
public class Person
{
public NameComponent Name { get; set; }
}
that I want to map to the following DTO:
public class PersonDTO : BaseDTO
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { NotifyPropertyChanged(() => FirstName, ref _firstName, value); }
}
private string _middleInitial;
public string MiddleInitial
{
get { return _middleInitial; }
set { NotifyPropertyChanged(() => MiddleInitial, ref _middleInitial, value); }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { NotifyPropertyChanged(() => LastName, ref _lastName, value); }
}
}
So when I want to Map from Model to DTO I need a Model.Name.First -> DTO.FirstName
and when going from DTO to Model I need FirstName -> Name.First. From my understanding this is not a simple Flatten/UnFlatten, because the words also reverse themselves, ie: FirstName <--> Name.First. So First and Last names could use the same kind of rule, but what about MiddleInitial? Model.Name.MiddleInitial -> DTO.MiddleInitial.
I see there are some plugins, but none of them seem to do what I would want. Has anyone else come across this scenario?
the basic idea is that I match the Name with the FirstName, I take this as a pivot point, and in the method that usually sets the value to just one (FirstName) property I set it to 3 properties - that's for the FromNameComp
in the ToNameComp i match the same properties but I take the value from 3 and create one and set it
public class SimpleTest
{
[Test]
public void Testit()
{
var p = new Person { Name = new NameComponent { First = "first", Last = "last", MiddleInitial = "midd" } };
var dto = new PersonDTO();
dto.InjectFrom<FromNameComp>(p);
Assert.AreEqual(p.Name.First, dto.FirstName);
Assert.AreEqual(p.Name.Last, dto.LastName);
Assert.AreEqual(p.Name.MiddleInitial, dto.MiddleInitial);
var pp = new Person();
pp.InjectFrom<ToNameComponent>(dto);
Assert.AreEqual(dto.LastName, pp.Name.Last);
Assert.AreEqual(dto.FirstName, pp.Name.First);
Assert.AreEqual(dto.MiddleInitial, pp.Name.MiddleInitial);
}
public class FromNameComp : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == "Name" && c.SourceProp.Type == typeof(NameComponent)
&& c.TargetProp.Name == "FirstName"
&& c.SourceProp.Value != null;
}
protected override object SetValue(ConventionInfo c)
{
dynamic d = c.Target.Value;
var nc = (NameComponent)c.SourceProp.Value;
//d.FirstName = nc.First; return nc.First does this
d.LastName = nc.Last;
d.MiddleInitial = nc.MiddleInitial;
return nc.First;
}
}
public class ToNameComponent : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.TargetProp.Name == "Name" && c.TargetProp.Type == typeof(NameComponent)
&& c.SourceProp.Name == "FirstName";
}
protected override object SetValue(ConventionInfo c)
{
dynamic d = c.Source.Value;
var nc = new NameComponent { First = d.FirstName, Last = d.LastName, MiddleInitial = d.MiddleInitial };
return nc;
}
}
public class NameComponent
{
public string First { get; set; }
public string Last { get; set; }
public string MiddleInitial { get; set; }
}
public class Person
{
public NameComponent Name { get; set; }
}
public class PersonDTO
{
public string FirstName { get; set; }
public string MiddleInitial { get; set; }
public string LastName { get; set; }
}
}
But before I dig into the code sample,
does anyone know if ValueInjecter
works in a Medium-Trust web
environment? (Like Godaddy?)
it doesn't use reflection.emit so it should work

Resources