How do I format a query to fetch an aggregate root with just related ids - npoco

Let's say I have the following object in my domain.
[TableName("work_space")]
public class WorkSpace
{
public long Id { get; set; }
[Column(Name="owner_id")]
public long OwnerId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<int> OrgIds { get; set; }
public IEnumerable<int> SettingIds { get; set; }
public IEnumerable<int> UserIds { get; set; }
public IEnumerable<long> WorkViewIds { get; set; }
}
This is one way to fetch the data that I need.
SELECT ws.*, wsu.user_id as UserId, wss.setting_id as SettingId, wso.org_id as OrgId, wv.id as WorkViewId
FROM work_space ws
LEFT OUTER JOIN work_space_user wsu ON ws.id = wsu.work_space_id
LEFT OUTER JOIN work_space_setting wss ON ws.id = wss.work_space_id
LEFT OUTER JOIN work_space_org wso ON ws.id = wso.work_space_id
LEFT OUTER JOIN work_view wv ON ws.id = wv.work_space_id
WHERE ws.id = #0
How is this usually done in NPoco? Would I use a multi Result set fetch? Some sort of Fetch one to Many? Do I flag my ID collections as Result or Ignore Columns? I'm just not finding any examples like this in the docs.

This is the most intuitive way to do this I have found to do this so far. It works ok for a single entity, but when I want to fetch collections of them, I'm having n+1 problems as go back to the database for each object in the collection to fetch the relations.
public WorkSpace GetWorkSpace(int id)
{
using (Database db = DbFactory.VSurveyDbFactory.GetDatabase())
{
WorkSpace workspace = db.SingleById<WorkSpace>(id);
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.Append(#"SELECT * FROM work_space_user WHERE work_space_id = #0;");
sqlBuilder.Append(#"SELECT * FROM work_space_setting WHERE work_space_id = #0;");
sqlBuilder.Append(#"SELECT * FROM work_space_org WHERE work_space_id = #0;");
sqlBuilder.Append(#"SELECT * FROM work_view WHERE work_space_id = #0;");
Tuple<List<int>, List<int>, List<int>, List<long>> results = db.FetchMultiple<int, int, int, long>(sqlBuilder.ToString(), id);
workspace.UserIds = results.Item1;
workspace.SettingIds = results.Item2;
workspace.OrgIds = results.Item3;
workspace.WorkViewIds = results.Item4;
return workspace;
}
}

Related

MVC inserting values into a ViewModel after Model to ViewModel mapping

I am working with two different databases. I am passing the Model collection (SQL Server) to a ViewModel collection. The ViewModel has extra properties which I access out of a Visual Fox Pro database. I am able to map the existing properties, but the ViewModel does not save the data after passing the values to it.
The WoCust and the Lname fields return null, but the rest of the properties which come from the original Model pass to the properties in the ViewModel fine.
When I debug at the rdr for the OleDbCommand, it shows that the ViewModel is receiving a value for both rdr[WoCust] and rdr[Lname].
How do I make it so the ViewModel saves the new values?
WOSchedule.cs...
public partial class WOSchedule
{
public long Id { get; set; }
public string WoNo { get; set; }
public Nullable<long> QuoteTypeId { get; set; }
public Nullable<int> PriorityNo { get; set; }
public bool Active { get; set; }
public Nullable<System.DateTime> WoDate { get; set; }
public Nullable<long> QuoteID { get; set; }
public Nullable<System.DateTime> WoDone { get; set; }
public Nullable<long> WOScheduleListId { get; set; }
public string StorageLocation { get; set; }
public virtual QuoteType QuoteType { get; set; }
public virtual Quote Quote { get; set; }
public virtual WOScheduleList WOScheduleList { get; set; }
}
WoWcheduleVM.cs...
public partial class WoScheduleVM
{
public long Id { get; set; }
public string WoNo { get; set; }
public Nullable<long> QuoteTypeId { get; set; }
public Nullable<int> PriorityNo { get; set; }
public bool Active { get; set; }
public DateTime? WoDate { get; set; }
public Nullable<long> QuoteID { get; set; }
public DateTime? WoDone { get; set; }
public Nullable<long> WOScheduleListId { get; set; }
public string StorageLocation { get; set; }
public string WoCust { get; set; } // extra property
public string Lname { get; set; } // extra property
public virtual QuoteType QuoteType { get; set; }
public virtual Quote Quote { get; set; }
public virtual WOScheduleList WOScheduleList { get; set; }
}
WOSchedulesController.cs
string cs = ConfigurationManager.ConnectionStrings["foxproTables"].ConnectionString;
OleDbConnection cn = new OleDbConnection(cs);
var wOSchedules = db.WOSchedules.Where(w => w.WoDone == null).Include(w => w.QuoteType);
var wOSchedulesVM = wOSchedules.Select(s => new ViewModels.WoScheduleVM()
{
Id = s.Id,
WoNo = s.WoNo,
QuoteTypeId = s.QuoteTypeId,
PriorityNo = s.PriorityNo,
Active = s.Active,
WoDate = s.WoDate,
QuoteID = s.QuoteID,
WoDone = s.WoDone,
WOScheduleListId = s.WOScheduleListId,
StorageLocation = s.StorageLocation
});
cn.Open();
foreach (var sch in wOSchedulesVM)
{
string conn = #"SELECT wo_cust, lname FROM womast INNER JOIN custmast ON womast.wo_cust = custmast.cust_id WHERE wo_no = '" + sch.WoNo + "'";
OleDbCommand cmdWO = new OleDbCommand(conn, cn);
OleDbDataReader rdr = cmdWO.ExecuteReader();
while (rdr.Read())
{
sch.WoCust = ((string)rdr["wo_cust"]).Trim();
sch.Lname = ((string)rdr["lname"]).Trim();
}
}
cn.Close();
return View(wOSchedulesVM.OrderByDescending(d => d.WoDate).ToList());
The problem is you're using foreach loop for iterating wOSchedulesVM collection, which renders the source collection immutable during iteration. The older documentation version explicitly explains that behavior:
The foreach statement is used to iterate through the collection to get
the information that you want, but can not be used to add or remove
items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop.
Therefore, you should use for loop to be able to modify property values inside that collection, as shown in example below:
using (var OleDbConnection cn = new OleDbConnection(cs))
{
cn.Open();
string cmd = #"SELECT wo_cust, lname FROM womast INNER JOIN custmast ON womast.wo_cust = custmast.cust_id WHERE wo_no = #WoNo";
// not sure if it's 'Count' property or 'Count()' method, depending on collection type
for (int i = 0; i < wOSchedulesVM.Count; i++)
{
var sch = wOSchedulesVM[i];
using (OleDbCommand cmdWO = new OleDbCommand(conn, cn))
{
cmd.Parameters.AddWithValue("#WoNo", sch.WoNo)
OleDbDataReader rdr = cmdWO.ExecuteReader();
if (rdr.HasRows)
{
while (rdr.Read())
{
sch.WoCust = (!rdr.IsDbNull(0)) ? rdr.GetString(0).Trim() : string.Empty;
sch.Lname = (!rdr.IsDbNull(1)) ? rdr.GetString(1).Trim() : string.Empty;
}
}
}
}
}
Note: This example includes 3 additional aspects, i.e. parameterized query, checking row existence with HasRows property and checking against DBNull.Value with IsDbNull().
Related issue: What is the best way to modify a list in a 'foreach' loop?

SQLite.net: Query one single column

I have a model of the following form:
public class LanguageText
{
[PrimaryKey]
public int Id { get; set; }
public string de { get; set; }
public string en { get; set; }
public string ru { get; set; }
}
How can I query just one column by Id? I tried this:
SQL = "SELECT [de] from [LanguageText] WHERE [Id] = \"1\""
var p = App.Database.QueryAsync<LanguageText>(SQL).Result.First();
This will return one whole row of LanguageText in p, while I want the contents of the [de] row as string only.
How do I accomplish this?
As we found out
App.Database.ExecuteScalarAsync<string>(SQL).Result

Entity Framework Codefirst approach

Am Using EntityFramework codefirst approach.my coding is
class Blog
{
[Key]
public int BlobId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
class Post
{
[Key]
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlobId { get; set; }
public virtual Blog Blob { get; set; }
}
class BlogContext:DbContext
{
public BlogContext() : base("constr") { }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var db = new BlogContext())
{
Console.WriteLine("Enter a name for a new blob:");
var name = Console.ReadLine();
var b = new Blog { Name = name };
db.Blogs.Add(b);
db.SaveChanges();
Till this step i created two tables(Blogs and Posts)in my SQlserver.The BlobId is primary key in Blogs table.and foreign key in Posts table.and Blogid in blog table is auto incremented.postid in posts table is also auto incremented
class Program
{
static void Main(string[] args)
{
using (var db = new BlogContext())
{
Console.WriteLine("Enter a name for a new blob:");
var name = Console.ReadLine();
var b = new Blog { Name = name };
db.Blogs.Add(b);
db.SaveChanges();
Here i added name in the blogtable
var id1 = from val in db.Blogs
where val.Name == name
select val.BlobId;
Now by using Name am obtaining the blogid of blogs table
Console.WriteLine("Enter Title:");
var title = Console.ReadLine();
Console.WriteLine("Enter Content");
var content = Console.ReadLine();
var c = new Post { Title = title, Content = content, BlobId = id1};
db.Posts.Add(c);
db.SaveChanges();
here am reading the data for title,content.Then adding the title,content and blogid(which i obtained from another table) into Posts table
I getting error at BlobId = id1
Am getting Cannot implicitly convert type 'System.Linq.IQueryable' to 'int' this error
}
Console.ReadLine();
}
}
Can you help me to solve this.If you did not understand what i explained please reply me
The following query is a sequence of elements, not a scalar value, even though you believe that there is only one result, it is still a collection with one element when the results of the query are iterated over:
var id1 = from val in db.Blogs
where val.Name == name
select val.BlobId;
Change this to:
int id1 = (from val in db.Blogs
where val.Name == name
select val.BlobId).First();
This query will execute immediately and return the first element in the sequence. It will throw an exception if there is no match, so you may want to use FirstOrDefault and assign to a nullable int instead.

Should I give up on Integer Ids for RavenDB?

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());

IDictionary<string, IDictionary<string, ICollection<string>>> To LINQ

I'm have been trying to convert the foreach statement below to a LINQ statement but can't seem to wrap my head around it. Any help would be welcome.
IDictionary<string, IDictionary<string, ICollection<string>>> Highlights { get; set; }
foreach (var r in matchingProducts.Highlights)
{
string szKey = r.Key;
var ar = r.Value.ToArray();
foreach (var s in ar)
{
string szCat = s.Key;
var sr = s.Value.ToArray();
foreach (var t in sr)
{
string szName = t;
//todo: update the corresponding matchingProduct records
// using the return values from szKey, szCat, szName
}
}
}
matchingProducts
public class Product {
[SolrUniqueKey("id")]
public string Id { get; set; }
[SolrField("sku")]
public string SKU { get; set; }
[SolrField("name")]
public string Name { get; set; }
[SolrField("manu_exact")]
public string Manufacturer { get; set; }
[SolrField("cat")]
public ICollection<string> Categories { get; set; }
[SolrField("features")]
public ICollection<string> Features { get; set; }
[SolrField("price")]
public decimal Price { get; set; }
[SolrField("popularity")]
public int Popularity { get; set; }
[SolrField("inStock")]
public bool InStock { get; set; }
[SolrField("timestamp")]
public DateTime Timestamp { get; set; }
[SolrField("weight")]
public double? Weight { get; set;}
}
You could just enumerate the following LINQ query
var query = from r in Highlights
let szKey = r.Key
let szValue = r.Value
from s in szValue
let szCat = s.Key
let sr = s.Value
from t in sr
let szText = t
select new { Key = szKey, Category = szCat, Text = szText };
// or, also, you can use this small query
var query = from r in Highlights
from s in r.Value
from t in s.Value
select new {Key = r.Key, Category = s.Key, Text = t};
foreach(var element in query)
{
ProcessUpdate(element.Key, element.Category, element.Text);
}
You can flatten the dictionary to the items in the innermost collection by writing
dict.Values.SelectMany(d => d.Values).SelectMany(c => c)
In these lambda expressions, d is the inner dictionary, and c is the innermost ICollection.
You can get the outer keys like this:
dict.SelectMany(kvpOuter =>
kvpOuter.Value.SelectMany(kvpInner =>
kvpInner.Value.Select(item =>
new {
OuterKey = kvpOuter.Key,
InnerKey = kvpInner.Key,
Item = item,
}
)
)
)

Resources