SubSonic Simple Repository One-To-Many - subsonic

I made a class like:
public class Video
{
public Guid VideoID { get; set; }
public VideoCategory VideoCategory { get; set; }
public int SortIndex { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string Author { get; set; }
public string Filename { get; set; }
public new void Add()
{
this.VideoID = Guid.NewGuid();
DB.Repository.Add(this);
}
}
And another like
public class VideoCategory
{
public Guid VideoCategoryID { get; set; }
public string Title { get; set; }
public new void Add()
{
this.VideoCategoryID = Guid.NewGuid();
DB.Repository.Add(this);
}
}
I then have code like:
VideoCategory VideoCategory = new VideoCategory();
VideoCategory.Title = "TestTitle";
VideoCategory.Add();
Video Video = new Video();
Video.VideoCategory = VideoCategory;
Video.SortIndex = 1;
Video.Title = "TestTitle";
Video.Body = "TestBody";
Video.Author = "TestAuthor";
Video.Filename = "TestFile.flv";
Video.Add();
It doesn't save the VideoCategory into my database, so obviously i'm missing something. What else is needed to done to save a one-to-many relationship?

You could probably just do the following, you'll probably want to tidy it up but it will ensure your foreign key value gets populated:
public class Video
{
protected VideoCategory videoCategory;
public Guid ID { get; set; }
public VideoCategory VideoCategory
{
get { return videoCategory; }
set
{
videoCategory = value;
VideoCategoryId = value.ID;
}
}
public Guid VideoCategoryId { get; set; }
public int SortIndex { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string Author { get; set; }
public string Filename { get; set; }
}
public class VideoCategory
{
public Guid ID { get; set; }
public string Title { get; set; }
}
SimpleRepository repo = new SimpleRepository(SimpleRepositoryOptions.RunMigrations);
VideoCategory videoCategory = new VideoCategory();
videoCategory.ID = Guid.NewGuid();
videoCategory.Title = "TestTitle";
repo.Add<VideoCategory>(videoCategory);
Video video = new Video();
video.ID = Guid.NewGuid();
video.VideoCategory = videoCategory;
video.SortIndex = 1;
video.Title = "TestTitle";
video.Body = "TestBody";
video.Author = "TestAuthor";
video.Filename = "TestFile.flv";
repo.Add<Video>(video);

You're not missing anything. Simplerepository doesn't support one to many out of the box.

Heres a useful link that shows how to mangage foreign keys yourself in SimpleRepository -
subsonic-3-simplerepository
Have not tried it myself, but looks like it would actually work.
Fluent Nhibernate will do this foriegn key management for you automatically, but it's a LOT more complex.
PS If this was helpful, please vote it up.

Related

How to get the current cache/document (Sales Order/Shipment) outside the context of a graph

I'm currently implementing a new carrier method and would like to access additional information on the Shipment/Sales Order object which is not passed through in the GetRateQuote & Ship functions of the implemented ICarrierService class.
The carrier method implements the ICarrierService interface and subsequently does not have access to a Graph where one would typically be able to access the current (cached?) document, etc.
How could I, for example, access the shipment number for which the Ship function is called?
My ultimate goal is to be able to generate a label for the shipment package, and in order to do so, I need to obtain the Shipment Number.
using PX.Api;
using PX.CarrierService;
using PX.CS.Contracts.Interfaces;
using PX.Data;
using PX.Data.Reports;
using PX.Objects.Common.Extensions;
using PX.Reports;
using PX.Reports.Data;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyCarriers.CollectCarrier
{
public class CollectCarrier : ICarrierService
{
private List<CarrierMethod> methods;
private List<string> attributes;
public IList<string> Attributes => (IList<string>)this.attributes;
public string CarrierID { get; set; }
public string Method { get; set; }
public ReadOnlyCollection<CarrierMethod> AvailableMethods => this.methods.AsReadOnly();
public CollectCarrier()
{
this.methods = new List<CarrierMethod>(1);
this.methods.Add(new CarrierMethod("CLT", "Collect"));
this.attributes = new List<string>(1);
}
[...]
public CarrierResult<ShipResult> Ship(CarrierRequest cr)
{
if (cr.Packages == null || cr.Packages.Count == 0)
{
throw new InvalidOperationException("CarrierRequest.Packages must be contain atleast one Package");
}
CarrierResult<ShipResult> carrierResult;
try
{
CarrierResult<RateQuote> rateQuote = this.GetRateQuote(cr, true);
ShipResult result = new ShipResult(rateQuote.Result);
//Report Parameters
Dictionary<String, String> parameters = new Dictionary<String, String>();
// ************************************************************************************
// At this point, I would like to be able to retrieve the current SOShipment's Shipment Number
// ************************************************************************************
parameters["shipmentNbr"] = "000009"; // Hard-coded this value to get the PDF generated.
//Report Processing
PX.Reports.Controls.Report _report = PXReportTools.LoadReport("SO645000", null);
PXReportTools.InitReportParameters(_report, parameters, SettingsProvider.Instance.Default);
ReportNode reportNode = ReportProcessor.ProcessReport(_report);
//Generation PDF
result.Image = PX.Reports.Mail.Message.GenerateReport(reportNode, ReportProcessor.FilterPdf).First();
result.Format = "pdf";
result.Data.Add(new PackageData(
cr.Packages.FirstOrDefault().RefNbr,
this.RandomString(6),
result.Image,
"pdf"
)
{
TrackingData = this.RandomString(6)
});
carrierResult = new CarrierResult<ShipResult>(result);
}
catch (Exception ex)
{
if (this.LogTrace)
{
this.WriteToLog(ex, this.GetType().Name + ".Ship().Exception");
}
List<Message> messageList = this.HandleException(ex);
messageList?.Insert(0, new Message("", "Failed to generate the collection label: "));
carrierResult = new CarrierResult<ShipResult>(false, null, (IList<Message>)messageList);
}
return carrierResult;
}
[...]
}
}
For reference, the CarrierRequest object that is passed to the functions contain the following information:
public class CarrierRequest
{
public string ThirdPartyAccountID
{
get;
set;
}
public string ThirdPartyPostalCode
{
get;
set;
}
public string ThirdPartyCountryCode
{
get;
set;
}
public IAddressBase Shipper
{
get;
set;
}
public IContactBase ShipperContact
{
get;
set;
}
public IAddressBase Origin
{
get;
set;
}
public IContactBase OriginContact
{
get;
set;
}
public IAddressBase Destination
{
get;
set;
}
public IContactBase DestinationContact
{
get;
set;
}
public IList<CarrierBox> Packages
{
get;
set;
}
public IList<CarrierBoxEx> PackagesEx
{
get;
set;
}
public IList<string> Methods
{
get;
set;
}
public DateTime ShipDate
{
get;
set;
}
public UnitsType Units
{
get;
private set;
}
public bool SaturdayDelivery
{
get;
set;
}
public bool Resedential
{
get;
set;
}
public bool Insurance
{
get;
set;
}
public string CuryID
{
get;
private set;
}
public IList<string> Attributes
{
get;
set;
}
public decimal InvoiceLineTotal
{
get;
set;
}
public string FreightClass
{
get;
set;
}
public bool SkipAddressVerification
{
get;
set;
}
public IList<ISETerritoriesMappingBase> TerritoriesMapping
{
get;
set;
}
public CarrierRequest(UnitsType units, string curyID)
{
if (string.IsNullOrEmpty(curyID))
{
throw new ArgumentNullException("curyID");
}
Units = units;
CuryID = curyID;
}
}
I have seen a similar question here on SO, but I'm not entirely sure that is applicable to my specific request?
Any assistance will be highly appreciated.
See below as an option to loop through your currents and search for the specific current object:
SOShipment ship = null;
for (int i = 0; i < Caches.Currents.Length; i++)
{
if (Caches.Currents[i].GetType() == typeof(SOShipment))
{
ship = (SOShipment)Caches.Currents[i];
break;
}
}

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?

Error 400.0 when returning DTO in Controller and Azure Mobile Client Sync Table

Since the object we need in the Mobile Client needs to access its related/associated objects, we decided to return an objectDTO instead of the object when the GetAllObjects method in the controller is called.
Using Postman to query the Backend Server results to the proper behaviour, the retrieved list has all the properties of the DTO.
Problem arises when using the Mobile Client. According to the logs, an "HTTP Error 400.0 - Bad Request" happened and "The request could not be understood by the server due to malformed syntax." is indicated under "More Information."
I dont know why this error happened. I updated the Object class in the Client App to match the ObjectDTO class in the server. For comparison:
ObjectDTO in Server
public class SaleDto
{
public string Id { get; set; }
public string ProductId { get; set; }
public string PromoterId { get; set; }
public string StoreId { get; set; }
public string PaymentMethodId { get; set; }
public bool CorporateSale { get; set; }
public DateTime? DateSold { get; set; }
public double PriceSold { get; set; }
public int QuantitySold { get; set; }
public string Remarks { get; set; }
public bool Deleted { get; set; }
public DateTimeOffset? CreatedAt { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public byte[] Version { get; set; }
public string ProductSku { get; set; }
public string ProductPartNumber { get; set; }
public string StoreName { get; set; }
public string PaymentMethodName { get; set; }
}
Object Model in Client App
public class Sale
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "productId")]
public string ProductId { get; set; }
[JsonProperty(PropertyName = "promoterId")]
public string PromoterId { get; set; }
[JsonProperty(PropertyName = "storeId")]
public string StoreId { get; set; }
[JsonProperty(PropertyName = "paymentMethodId")]
public string PaymentMethodId { get; set; }
[JsonProperty(PropertyName = "corporateSale")]
public bool CorporateSale { get; set; }
[JsonProperty(PropertyName = "dateSold")]
public DateTime? DateSold { get; set; }
[JsonProperty(PropertyName = "priceSold")]
public double PriceSold { get; set; }
[JsonProperty(PropertyName = "quantitySold")]
public int QuantitySold { get; set; }
[JsonProperty(PropertyName = "remarks")]
public string Remarks { get; set; }
[JsonProperty(PropertyName = "deleted")]
public bool Deleted { get; set; }
[JsonProperty(PropertyName = "createdAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "updatedAt")]
public DateTime UpdatedAt { get; set; }
[JsonProperty(PropertyName = "version")]
public string Version { get; set; }
[JsonProperty(PropertyName = "productSku")]
public string ProductSku { get; set; }
[JsonProperty(PropertyName = "productPartNumber")]
public string ProductPartNumber { get; set; }
[JsonProperty(PropertyName = "storeName")]
public string StoreName { get; set; }
[JsonProperty(PropertyName = "paymentMethodName")]
public string PaymentMethodName { get; set; }
public virtual Product Product { get; set;}
public virtual Store Store { get; set; }
public virtual PaymentMethod PaymentMethod { get; set; }
}
Or it might be because of the Sync Tables? Here's the code that handles syncing (stuff has been omitted for brevity)
public class DataStore
{
private static DataStore _instance;
public MobileServiceClient MobileService { get; set; }
IMobileServiceSyncTable<Sale> saleTable;
public static DataStore Instance
{
get
{
if (_instance == null)
{
_instance = new DataStore();
}
return _instance;
}
}
private DataStore()
{
MobileService = new MobileServiceClient("url");
var store = new MobileServiceSQLiteStore("tabletable.db");
store.DefineTable<Sale>();
MobileService.SyncContext.InitializeAsync(store);
saleTable = MobileService.GetSyncTable<Sale>();
}
public async Task<Sale> AddSaleAsync(Sale sale)
{
await saleTable.InsertAsync(sale);
bool wasPushed = await SyncSalesAsync();
if (wasPushed) return null;
return sale;
}
public async Task<List<Sale>> GetSalesAsync(int take = 20, int skip = 0)
{
IEnumerable<Sale> items = await saleTable
.Where(sale => !sale.Deleted)
.OrderByDescending(sale => sale.CreatedAt)
.Take(take)
.Skip(skip)
.ToEnumerableAsync();
return new List<Sale>(items);
}
public async Task<bool> SyncSalesAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
bool wasPushed = true;
try
{
await MobileService.SyncContext.PushAsync();
await saleTable.PullAsync("allSales", saleTable.CreateQuery());
}
catch (Exception e)
{
Debug.WriteLine(#"/Sale/ Catch all. Sync error: {0}", e.Message);
Debug.WriteLine(e.StackTrace);
}
return wasPushed;
}
}
Any kind of help will be much appreciated.
Having SaleDto extend/implement EntityData solved the problem
public class SaleDto : EntityData

Complex Automapper Configuration

I'm mapping from an existing database to a DTO and back again use Automapper (4.1.1) and I've hit a few small problems.
I have a (simplified) model for the database table:
public class USER_DETAILS
{
[Key]
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
and a DTO object:
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
I've created a profile
public class FromProfile : Profile
{
protected override void Configure()
{
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
this.SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
this.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
this.CreateMap<USER_DETAILS, User>();
}
}
However, it seems that Automapper doesn't like combining this many settings in the config. Even simplifying the models, I can't get
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
to work together, and whilst
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
works ok with the models in the test, it fails when using it against a database.
Is there a way to get all this to work together, or should I fall back to using ForMember(). I was really hoping I could get this working as there are a lot of tables in this system, and I'd rather not have to do each one individually.
You will need to extend this for other types, only tested with strings, I have an extension method that does all the work and looks for unmapped properties.
public class USER_DETAILS
{
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
// public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
//public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination>
CustomPropertyMapper<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
var properties = sourceType.GetProperties();
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
var similarPropertyName =
properties.FirstOrDefault(x => x.Name.Replace("_", "").Replace("UDT", "").ToLower().Contains(property.ToLower()));
if(similarPropertyName == null)
continue;
var myPropInfo = sourceType.GetProperty(similarPropertyName.Name);
expression.ForMember(property, opt => opt.MapFrom<string>(myPropInfo.Name));
}
return expression;
}
}
class Program
{
static void Main(string[] args)
{
InitializeAutomapper();
var userDetails = new USER_DETAILS
{
UDT_LOGIN = "Labi-Login",
UDT_USER_NAME = "Labi-UserName",
UDT_INITIALS = "L"
};
var mapped = Mapper.Map<User>(userDetails);
}
static void InitializeAutomapper()
{
Mapper.CreateMap<USER_DETAILS, User>().CustomPropertyMapper();
}
}
}

Deserializing oData to a sane object with ServiceStack

So here's what I'm getting back from the oData service...
{
"odata.metadata":"http://server.ca/Mediasite/Api/v1/$metadata#UserProfiles",
"value":[
{
"odata.id":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')",
"QuotaPolicy#odata.navigationLinkUrl":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/QuotaPolicy",
"#SetQuotaPolicyFromLevel":{
"target":"http://server.ca/Mediasite/Api/v1/UserProfiles('111111111111111')/SetQuotaPolicyFromLevel"
},
"Id":"111111111111111",
"UserName":"testuser",
"DisplayName":"testuser Large",
"Email":"testuser#testuser.ca",
"Activated":true,
"HomeFolderId":"312dcf4890df4b129e248a0c9a57869714",
"ModeratorEmail":"testuser#testuserlarge.ca",
"ModeratorEmailOptOut":false,
"DisablePresentationContentCompleteEmails":false,
"DisablePresentationContentFailedEmails":false,
"DisablePresentationChangeOwnerEmails":false,
"TimeZone":26,
"PresenterFirstName":null,
"PresenterMiddleName":null,
"PresenterLastName":null,
"PresenterEmail":null,
"PresenterPrefix":null,
"PresenterSuffix":null,
"PresenterAdditionalInfo":null,
"PresenterBio":null,
"TrustDirectoryEntry":null
}
]
}
I want to deserialize this into a simple class, like just the important stuff (Id, Username, etc...to the end).
I have my class create, but for the life of me I can't figureout how to throw away all the wrapper objects oData puts around this thing.
Can anyone shed some light?
You can use JsonObject do dynamically traverse the JSON, e.g:
var users = JsonObject.Parse(json).ArrayObjects("value")
.Map(x => new User
{
Id = x.Get<long>("Id"),
UserName = x["UserName"],
DisplayName = x["DisplayName"],
Email = x["Email"],
Activated = x.Get<bool>("Activated"),
});
users.PrintDump();
Or deserialize it into a model that matches the shape of the JSON, e.g:
public class ODataUser
{
public List<User> Value { get; set; }
}
public class User
{
public long Id { get; set; }
public string UserName { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public bool Activated { get; set; }
public string HomeFolderId { get; set; }
public string ModeratorEmail { get; set; }
public bool ModeratorEmailOptOut { get; set; }
public bool DisablePresentationContentCompleteEmails { get; set; }
public bool DisablePresentationContentFailedEmails { get; set; }
public bool DisablePresentationChangeOwnerEmails { get; set; }
public int TimeZone { get; set; }
}
var odata = json.FromJson<ODataUser>();
var user = odata.Value[0];

Resources