c# LINQ query to select whole object into new? - c#

Not sure if i worded the question correctly, but what im trying to do is return a new viewmodel with one of the parts being a booking:
public class Booking
{
public int BookingId { get; set; }
public int CustomerId { get; set; }
public Guid UniqueId { get; set; }
public string EventId { get; set; }
public bool IsPaid { get; set; }
public double Price { get; set; }
public DateTime BookingDate { get; set; }
public DateTime DateBooked { get; set; }
[JsonIgnore]
public Customer Customer { get; set; }
[JsonIgnore]
public ICollection<BookingService> BookingServices { get; set; }
[NotMapped]
public IEnumerable<Service> Services { get; set; }
}
and my query is:
var customers = _dbContext.Customers
.Select(c => new CustomerBookingsViewModel
{
Customer = c,
Bookings = c.Bookings.Select(b => new Booking
{
BookingId = b.BookingId,
BookingDate = b.BookingDate,
DateBooked = b.DateBooked,
CustomerId = b.CustomerId,
UniqueId = b.UniqueId,
EventId = b.EventId,
IsPaid = b.IsPaid,
Price = b.Price,
Services = b.BookingServices.Select(s => s.Service)
}),
}
)
.ToList();
What I want to know is how to I select all the booking info into the booking without selecting each part, ie:
BookingId = b.BookingId,
BookingDate = b.BookingDate,
DateBooked = b.DateBooked,
CustomerId = b.CustomerId,
UniqueId = b.UniqueId,
EventId = b.EventId,
IsPaid = b.IsPaid,
Price = b.Price,
Can it be done or because the list of services is inside the booking model it cant?
Thanks.

You could implement the IClonable interface on your class.
public class MyClass : ICloneable
{
public int Id { get; set; }
public object Clone() => MemberwiseClone();
}
Usage:
var list1 = new List<MyClass>
{
new MyClass() { Id = 2 },
new MyClass() { Id = 5 }
};
var list2 = list1.Select(x => (MyClass)x.Clone()).ToList();
list2.First().Id = 10; //list1 won't be affected

You should use AutoMapper here to avoid writing each path.
https://automapper.org/
http://docs.automapper.org/en/stable/Getting-started.html

There is no other way, at least it is not related to LINQ or queries.
The question "How to clone an object" has been answered here:
Creating a copy of an object in C#

There is no LINQ way to do this. I would suggest using custom Attribute marking every property you want to copy. This would help if you want not to copy the whole object but some properties. After marking every property you need you can just set the marked props with reflection from one of the objects to the other.

Related

I am unable to convert StringBuilder to string in c# asp.net MVC

My invoice class has a StringBuilder property so that if adjustments are made, the user adds notes as the details of why the adjustment was made. This property is set to a new empty StringBuilder upon creation. Only in edit mode will it get .Append() added to. From what I understand, this is more efficient than using adjustmentNotes += newAdjustmentNotes every time new notes are added.
My Invoice.cs class is as follows.
public class Invoice
{
[Key]
public int InvoiceID { get; set; }
[ForeignKey(nameof(Customer))]
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<Job> Jobs { get; set; }
public double InvoiceAmount { get; set; }
public StringBuilder AdjustmentNotes { get; set; }
public bool Paid { get; set; }
}
When populating my list or details view, I query the database as follows:
using (var ctx = new ApplicationDbContext())
{
var query = ctx.Invoices
.Where(e => e.InvoiceID >= 0)
.Select(e => new InvoiceListItem
{
InvoiceID = e.InvoiceID,
CustomerID = e.CustomerID,
InvoiceAmount = e.InvoiceAmount,
Paid = e.Paid,
AdjustmentNotes = e.AdjustmentNotes.ToString()
});
return query.ToArray();
}
Then I get the following error message.
System.NotSupportedException: 'Values of type 'StringBuilder' can not be converted to string.'
But according to Microsoft documentation, other questions on this forum, and various resources this is the proper way to accomplish this.
I can certainly change it back to a string and use += as needed. There will not be too many notes for this particular property, but can someone please explain what I am doing wrong? Thank you so much!
EDIT: For clarification, here is my InvoiceListItem model class:
public class InvoiceListItem
{
public int InvoiceID { get; set; }
public int CustomerID { get; set; }
public List<int> JobIDs { get; set; }
public double InvoiceAmount { get; set; }
[UIHint("Bool")]
public bool Paid { get; set; }
public string AdjustmentNotes { get; set; }
}
EF Core says in the error message :
I don't know how convert StringBuilder.ToString() to target database language.
The solution is to do the conversion after the query :
var result = ctx.Invoices
.Where(e => e.InvoiceID >= 0)
.ToList() // First execute the query
.Select( // Second convert Invoice to InvoiceListItem
e => new InvoiceListItem {
InvoiceID = e.InvoiceID,
CustomerID = e.CustomerID,
InvoiceAmount = e.InvoiceAmount,
Paid = e.Paid,
AdjustmentNotes = e.AdjustmentNotes.ToString()
}
);
If you want limit the returned properties in the query, you can use intermediate type like :
var query = ctx.Invoices
.Where(e => e.InvoiceID >= 0)
.Select(
e => new {
e.InvoiceID,
e.CustomerID,
e.InvoiceAmount,
e.Paid,
AdjustmentNotes
}
);
var result = query.ToList().
.Select(
e => new InvoiceListItem {
InvoiceID = e.InvoiceID,
CustomerID = e.CustomerID,
InvoiceAmount = e.InvoiceAmount,
Paid = e.Paid,
AdjustmentNotes = e.AdjustmentNotes.ToString()
}
);
PS :
I found no information about EF Core support StringBuilder type, but
your context's model is build with success then EF Core seems to support StringBuilder type...
If there is support, it seems very limited (see the error in your question). Maybe you can consider use String instead of StringBuilder in entity class.

Entity Framework add local data to list from database

I am pretty new to Entity Framework and I am using this method in order to query through my database:
var _context = new StudioEntities();
var results = _context.tblStudios.Select(u => new
{
u.Standort,
u.Name,
u.Id
}).ToList();
Now my goal is to add local data as well which isn't present in the database. I tried it with this code but it didn't work:
results.Add(new tblStudio { Id = 0, Name = "Gesamt" });
Can someone help me with this? Thanks
Edit:
My table class looks like this:
public partial class tblStudio
{
public int Id { get; set; }
public string Name { get; set; }
public string Standort { get; set; }
public Nullable<int> Plz { get; set; }
}
The result is not a List of tblStudios, it is a List of Anonymous Type. So if you want to add an item to the result you should do like this:
var results = _context.tblStudios.Select(u => new tblStudiosDTO()
{
Standort = u.Standort,
Name = u.Name,
Id = u.Id
}).ToList();
results.Add(new tblStudiosDTO() { Id = "0", Name = "Gesamt" });
But because you cannot project onto a mapped entity then you need to create a DTO class like tblStudiosDTO with needed properties from the tblStudios entity.
public class tblStudiosDTO
{
public string Standort { get; set; }
public string Name { get; set; }
public string Id { get; set; }
}

LINQ merge a collection

I need to flatten it to one item in a new collection.
The input will be an IEnumerable collection. The base class looks like this:
public class ConversionsResult
{
public int SiteId { get; set; }
public int TotalLeads { get; set; }
public int TotalCalls { get; set; }
public int TotalEmails { get; set; }
public int UniqueVisits { get; set; }
public int MetricYear { get; set; }
public int MetricMonth { get; set; }
public string DeviceCategory { get; set; }
}
The flattened class will look like this:
SiteId, MetricMonth, MetricYear SUM(TotalLeads), SUM(TotalEmails), SUM(UniqueVisits), CONCATENATE(DeviceCategory).
NOTES:
SiteId, MetricMonth, MetricYear will have the same value flattened as in the original collection
The total properties need to be added
DeviceCategory will have three different values in it, comma separated, e.g. "desktop,mobile,tablet".
I could accomplish this another route but I wanted to use LINQ. I tried hacking a few solutions I found but it was a total mess so I've excluded the noise I tried.
You need to group by (Enumerable.GroupBy) SiteId, MetricMonth and MetricYear and then select results like:
var query = list.GroupBy(r => new { r.SiteId, r.MetricMonth, r.MetricYear })
.Select(grp => new
{
SiteId = grp.Key.SiteId,
MetricMonth = grp.Key.MetricMonth,
MetricYear = grp.Key.MetricYear,
TotalLeads = grp.Sum(r=> r.TotalLeads),
TotalEmails = grp.Sum(r=> r.TotalEmails),
UniqueVisits = grp.Sum(r=> r.UniqueVisits),
DeviceCategory = String.Join(",", grp.Select(r=> r.DeviceCategory)),
});

How to query a flatten sub collection in RavenDb? Index needed?

I am using RavenDb in C# web project. I have an object that I need to query its child collection with 1 row per child object and some of the root/parent object properties.
Note: This is not the actual design, just simplified for this question.
public class OrderLine
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime? ShipDate { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public DateTime OrderDate { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
The order with the orderlines is one single document. ShipDate will be updated on each line because not all products are always in stock.
I need to be able to create a list of the last 10 products sent with the following columns:
OrderId
Customer
ProductName
ShipDate
This doesn't work because SelectMany is not supported:
var query = from helper in RavenSession.Query<Order>()
.SelectMany(l => l.OrderLines, (order, orderline) =>
new { order, orderline })
select new
{
helper.order.OrderId,
helper.order.CustomerName,
helper.orderline.ProductName,
helper.orderline.ShipDate
};
var result = query.Where(x => x.ShipDate.HasValue)
.OrderByDescending(x => x.ShipDate.Value).Take(10);
I believe the right thing to do isto create an Index that will flatten out the list but I haven't had any success. I don't believe a Map-Reduce situation will work because as I understand it will effectively does a group by which Reduces the number of documents to less rows (in the index). But in this case, I am trying to expand the number of documents to more rows (in the index).
I would rather not put each OrderLine in a separate document but I do not know what my options are.
Since you want to filter and sort by fields in the subclass, you'll need to make sure all the fields you want are indexed and stored.
public class ShippedItemsIndex
: AbstractIndexCreationTask<Order, ShippedItemsIndex.Result>
{
public class Result
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime ShipDate { get; set; }
}
public ShippedItemsIndex()
{
Map = orders =>
from order in orders
from line in order.OrderLines
where line.ShipDate != null
select new
{
order.OrderId,
order.CustomerName,
line.ProductName,
line.Quantity,
line.ShipDate
};
StoreAllFields(FieldStorage.Yes);
}
}
Then you can project from the index into your results.
var query = session.Query<Order, ShippedItemsIndex>()
.ProjectFromIndexFieldsInto<ShippedItemsIndex.Result>()
.OrderByDescending(x => x.ShipDate)
.Take(10);
var results = query.ToList();
Here is a complete test demonstrating.

Retrieve navigation properties's data in a recently added record

I have 3 entities in my case. Invoice, InvoiceDetail and Item.
Invoice has a collection of InvoiceDetail.And each InvoiceDetail has an Item.
Please see the code below:
var ctx = new TestEntities();
var newInvoice = new Invoice
{
CreationDate = DateTime.Now,
UserId = 14
};
newInvoice.InvoiceDetails.Add(new InvoiceDetail
{
ItemId = 345,
ItemCount = 10
});
newInvoice.InvoiceDetails.Add(new InvoiceDetail
{
ItemId = 534,
ItemCount = 10
});
ctx.Invoices.Add(newInvoice);
ctx.SaveChanges();
// workaround
// ctx.Items.ToList();
foreach (var i in newInvoice.InvoiceDetails)
{
// In this line I get NullReferenceException
Console.WriteLine(i.Item.Title);
}
I get NullReferenceException when I want to retrieve each InvoiceDetail's Item data.
The problem is solved when I uncomment, commented part of the code. (ctx.Items.ToList())
UPDATE 1 :
And also this is the Item class:
public partial class Item
{
public Item()
{
this.InvoiceDetails = new HashSet<InvoiceDetail>();
}
public long Id { get; set; }
public string Title { get; set; }
public virtual ICollection<InvoiceDetail> InvoiceDetails { get; set; }
}
UPDATE 2:
public partial class InvoiceDetail
{
public long Id { get; set; }
public long InvoiceId { get; set; }
public long ItemId { get; set; }
public int ItemCount { get; set; }
public virtual Invoice Invoice { get; set; }
public virtual Item Item { get; set; }
}
[NOTE: I am assuming EF5]
The problem could be related to the way you create instances of Invoice and InvoiceDetail. You are newing up instances so they are not EF proxies with all of the necessary components for lazy loading.
I suggest you try using the DbSet.Create() method instead of new
var newInvoice = ctx.Set<Invoice>().Create();
newInvoice.CreationDate = DateTime.Now;
newInvoice.UserId = 14;
var detail1 = ctx.Set<InvoiceDetail>().Create();
detail1.ItemId = 345;
detail1.ItemCount = 10;
newInvoice.InvoiceDetails.Add(detail1);
//...
I can't promise this will fix your problem as EF is such an intricate and varied beast but it is worth giving this a try ...

Categories

Resources