I have a database which stores different locations. Based on the type of the location, the unique details are stored in a different table than the common properties (i.e. Name, Coordinates, Description are stored in the Locations table, while Population is stored in the CityDetails table).
The tables are connected via FK in the "specific details" table.
Since there are more than one "specific details" table I'm scratching my head how to query those multiple tables.
My Location object looks like this:
[Table("Locations")]
public class LocationData
{
public Guid Id { get; set; }
// multiple different properties here.
public string Type { get; set; }
public DetailsData { get; set; }
}
based on the Type property I can see which details table I have to query. A details table looks at least like this:
public abstract class DetailsData
{
[Key, ForeignKey("Location")]
public Guid { get; set; }
public LocationData Location { get; set; }
}
Every table that represents a location type then inherits from the DetailsData class and is configured to use the "Table-per-concrete-class" inheritance hierarchy.
Here is an example of what a "specific details" table might look like:
[Table("CityDetails")]
public class CityDetailsData : DetailsData
{
public string Planet { get; set; }
public int Population { get; set; }
}
How can I populate the DetailsData property in the Location class. Here is my current LINQ query:
from l in Locations
select new
{
Id = l.Id,
Name = l.Name,
Description = l.Description,
Coordinates = l.Coordinates,
Type = l.Type,
DetailsData = CityDetails // Here lies the problem. How can I populate this property properly?
}
Okay, I'm working on the assumption that your details tables all have different sets of columns, that you want to be able to use IEnumerable<LocationData> instances, and that you want to be able to directly access the properties of the instances of whatever details table object you return.
That's a toughie. There are basically three ways you can write code for an object of unknown type:
Give it a type of Object.
Make all "details" tables inherit from a common interface, and make CityDetails an object that implements that interface.
Use a generic type, which means you do ultimately specify the type, but you can push that decision up the chain to a declarer of your implemented type.
Using System.Object means that you either need to cast all your DetailsData objects to the required type (or use Reflection to access their properties), whereas using an interface means you can access some properties directly, but probably not everything you want.
I think a generic type might be your best, hybrid, solution.
I would do something like this:
Accept that there isn't a perfect solution.
Instead of creating an anonymous type, as you are doing in your example code, define a class like this (akin to a ViewModel, if you're using ASP.NET MVC):
public class LocationViewModel<TDetailsData>
{
public Guid Id { get; set; }
// multiple different properties here.
public string Type { get; set; }
public TDetailsData DetailsData { get; set; }
}
Define a helper method that takes a type parameter and returns a value of type TDetailsData. Use that to populate your DetailsData property on LocationViewModel.
TDetailsData GetLocationDetails<TDetailsData>(string type, Guid ID)
{
// get the appropriate data based on the type.
}
If you need to keep an IEnumerable of these objects, define an interface ILocationDataViewModel and another of ILocationDataViewModel<TDetailsData>, make LocationViewModel implement both of them, and use IEnumerable<ILocationDataViewModel> or IEnumerable<ILocationDataViewModel<CityDetailsData>>, etc., as called for by the situation.
If I understood correctly, you would like to dynamically determine the type and deal with it on the fly. In that case, you can go with dynamic. I have not tested this code, but you can think in that line
public DetailsData GetDetailsData(Type t)
{
dynamic typVal = new ExpandoObject();
if(t == typeof(CityDetails))
{
typVal = new CityDetails();
}
// add other types
return (DetailsData)PopulateDetailsData(typVal);
}
public DetailsData PopulateDetailsData(CityDetails cd)
{
cd.Planet = new CityDetails().Planet;
return cd;
}
// Add other type related methods with same
// signature with input parameter differentiated by types
// for example
public DetailsData PopulateDetailsData(TownDetails td)
{
td.Income = new TownDetails().Income;
return td;
}
in query, you can have something like below
from l in Locations
select new
{
Id = l.Id,
Name = l.Name,
Description = l.Description,
Coordinates = l.Coordinates,
Type = l.Type,
DetailsData = GetDetailsData(l.Type)
}
Related
I need to read a CSV file with FileHelpers based on type, automatically generated by my MVC model. The model looks like this:
public partial class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
The last field is obviously generated by a foreign key in database, referring to table MerchantCategories.
Then I attempt to create an instance of FileHelperEngine with this type:
var engine = new FileHelperEngine<Merchant>();
And get the following exception:
The field: 'k__BackingField' has the type: MerchantCategory that is not a system type, so this field need a CustomConverter ( Please Check the docs for more Info).
Actually I don't need this field at all for my import, so I tried to ignore it in derived class:
[DelimitedRecord(",")]
public class MerchantForImport : Merchant {
[FieldHidden]
new public MerchantCategory MerchantCategory;
}
var engine = new FileHelperEngine<MerchantForImport>();
And still the same error. I don't need this field at all, I don't want to implement any FieldConverter for it, I never asked for this k__BackingField and it's nowhere to be found in my code!
I can't call FileHelperEngine.Options.RemoveField() because the exception is thrown by the constructor.
Where does that come from? How do I get rid of it?
From a design perspective, I think you are going about it the wrong way. You are trying to use the Merchant class for two incompatible uses. Instead you should have two separate classes.
FileHelpers is a library for describing csv files so that you can import them easily. You should have a MerchantFileSpec for describing your file. It's really not a proper C# class - it may have: dummy fields to represent unused columns; lots of attributes [FieldNullValue], [FieldQuoted], [FieldConverter]; etc. It works best with public fields (a FileHelpers limitation which is not C# best practice), etc. It is a convenience syntax for describing the import file. It should not include any business logic or special constructors, or backing fields. Keep it as simple as possible.
Then you can have your MVC-generated Merchant class which is separate. Its purpose is to describe the merchant as required by the MVC framework, with foreign keys, ids, whatever.
Then you use a FileHelperEngine<MerchantFileSpec> to read the records into an array and map it to an enumerable of Merchant (via Linq or a library like AutoMapper).
Something like:
/// Your MVC-generated class. Add methods, getters, setters, whatever.
/// FileHelpers doesn't use this class.
class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
/// This is the class FileHelpers will use
/// This class describes the CSV file only. Stick to whatever
/// syntax conventions are required by FileHelpers.
[DelimitedRecord(";")]
class ProductMerchantFileSpec
{
[FieldQuoted(QuoteMode.OptionalForRead)]
public long Id;
[FieldQuoted(QuoteMode.OptionalForRead)]
public string Name;
[FieldQuoted(QuoteMode.OptionalForRead)]
// Handle non-US formats such as , decimal points
// convert from inches to centimetres?
// you get the idea...
[FieldConverter(MyCustomizedCategoryConverter)] // you get the idea
public int Category;
}
class Program
{
static void Main(string[] args)
{
var engine = new FileHelperEngine<ProductMerchantFileSpec>();
var productMerchantRecords = engine.ReadFile(filePath);
var productMerchants = productMerchantRecords
.Select(x => new Merchant() { Id = x.Id, Name = x.Name, Category = x.Category });
}
}
I received this error specifically because my object (i.e. Merchant) was missing a column that existed in the source file. I was able to work around the issue prior to realizing the missing column by adding a new property to my object class public string[] MyProperty { get; set; }. This work-around help me realize a column was missing.
i.e..
public partial class Merchant
{
public long id { get; set; }
..
..
..
public string[] MyProperty { get; set; }
}
I want to create a key value table in my database along the lines of
public class KeyValue {
public string Id { get; set; }
public dynamic Value {get; set; }
}
Using a slightly modified SqlProvider I have no problems getting CreateTable<KeyValue>() to generate varchar(1024) Id, varchar(max) Value.
I have no issues saving objects to it. The problem is when I load the objects
var content = dbConn.GetById<KeyValue>("about");
content.Value at this point is a string.
Looking at the database record, the text for value does not appear to store any type information.
Is there really anything I can do better other than manually invoking ServiceStack.Text and call deserialize with the appropriate type information?
I do not need absolute dynamic, my actual use case is for polymorphism with a base class instead of dynamic. So I don't really care what type Value is whether it's the base class, dynamic, object, etc. Regardless other than using the class
public class KeyValue {
public string Id { get; set; }
public MySpecificChildType Value {get; set; }
}
I haven't been able to get anything other than a string back for Value. Can I tell OrmLite to serialize the type information to be able to correctly deserialize my objects or do I just have to do it manually?
Edit: some further information. OrmLite is using the Jsv serializer defined by ServiceStack.Text.TypeSerializer and is in no way pluggable in the BSD version. If I add a Type property to my KeyValue class with the dynamic Value I can do
var value = content.Value as string;
MySpecificChildType strongType =
TypeSerializer.DeserializeFromString(content, content.Type);
I just really want a better way to do this, I really don't like an object of 1 type going into the db coming back out with a different type (string).
I haven't worked much with the JsvSerializer but with the JsonSerializer you can achieve this (in a few different ways) and as of ServiceStack 4.0.11 you can opt to use the JsonSerializer instead, see https://github.com/ServiceStack/ServiceStack/blob/master/release-notes.md#v4011-release-notes.
Example
public abstract class BaseClass {
//Used for second example of custom type lookup
public abstract string Type { get; set; }
}
public class ChildA : BaseClass {
//Used for second example of custom type lookup
public override string Type { get; set; }
public string PropA { get; set; }
}
And then in your init/bootstrap class you can configure the serializer to emit the type information needed for proper deserialization:
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
}
}
If you wish to use something other that the default "__type" attribute that ServiceStack uses (if you for example want to have a friendly name identifying the type rather then namespace/assembly) you can also configure your own custom type lookup as such
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
ServiceStack.Text.JsConfig.TypeAttr = "type";
ServiceStack.Text.JsConfig.TypeFinder = type =>
{
if ("CustomTypeName".Equals(type, StringComparison.OrdinalIgnoreCase))
{
return typeof(ChildA);
}
return typeof(BaseClass);
}
}
}
I have a mongo model like this:
class ObjectA {
[BsonId(IdGenerator = typeof(BsonObjectIdGenerator))]
public BsonObjectId Id;
[BsonElement("number")]
public int Number { get; set; }
[BsonElement("b")]
public List<ObjectB> objectB { get; set; }
}
class ObjectB {
[BsonElement("someProperty")]
public string SomeProperty { get; set; }
}
My problem is when I aggregate the collection with {$unwind:objectB}. The result documencts have a unique object on the property objectB (not a list).
So the cast failes with the exception:
An error occurred while deserializing the ObjectB property of class
ObjectA: Expected element name to be '_t', not
'number'.
Do I have to create a new model for this or is there a easier way to solve it?
You could also choose to work with BsonDocument directly (but that is not strongly typed and more cumbersome to work with), e.g. (I'm using the simple Posts/Tags example here)
var aggregationResults = db.GetCollection("Posts").Aggregate().ResultDocuments;
foreach (var document in aggregationResults)
{
var tag = document.GetValue("Tags").AsString;
}
Unlike the normal query and projection operators, the aggregation framework may change the structure of your document. As you already pointed out, $unwind transforms a document that contains an array into a number of documents that each have a single value of the same name.
Another approach this is to indeed create a new type for this, so
class Post {
public List<string> Tags { get; set; }
...
would become
class PostAggregationResult {
public string Tags { get; set; }
...
That is very easy to work with, but if you have very various aggregation queries, you need a large number of classes which can be annoying.
I have a situation that needs to assign a LINQ result to a Button.Tag property.
and when that button clicks, iterate throughout that LINQ result placed in the Button.Tag
HINT : LINQ result is type of List<anonymousType>. for some reason, i don't what to return List<KnownType>
any idea?
EDIT : As you all suggested, i reconsider problem and decided to create a specific class type and put DataTableRowId in the class instead of whole DataTableRow thing.
therefore anonymous Type Like new {Class1=c1, Class2=c2, DataTableRow3=dr3} changed to
class of type:
public class CustomClass
{
public Class1 c1 { get; set; }
public Class c2 { get; set; }
public int DataTableRow3Id dr3 { get; set; }
}
You can not access anonymous types this way, You can make a custom class and create the result of linq to that custom type. Assign this object to tag and later type cast it back to your custom type.
Is it possible to have a HasMany relationship of a basic type such as String, on an ActiveRecord class, without the need for creating another entity such as (TodoListItem) to hold the value.
[ActiveRecord]
public class TodoList
{
[PrimaryKey]
public int Id
{
get { return _id; }
set { _id = value; }
}
[HasMany(typeof(string)]
public IList<string> Items
{
get { return _items; }
set { _items= value; }
}
}
Can anyone help?
Yes, you can do this. You can map a one-to-many relation to a built-in or simple type (value type or string) rather than a persisted type.
You'll need to specify the ColumnKey, Table and Element params in the HasMany attribute declaration to get it to wire up properly. You have to have a surrogate key column so the AR can handle updates and cascades, and then Element tells AR which column in the table holds the simple value it will use to make the list.
[HasMany(typeof(string), Table="ToDoList_Items",
ColumnKey = "ListItemID", Element = "Item")]
public IList<string> Items { get; set; }
(or something similar - I haven't got a compiler handy on this box to check it; but per the API docs it ought to work.)
Speaking of which, if you haven't already had a look, http://api.castleproject.org is kinda indispensible for any work with the Castle stack.
In ActiveRecord, your types map to a record in a table (by default). It seems like you are confusing how this type should map to your table.
The MyClass type should have a definition something like this (excluding the PK settings):
[ActiveRecord(Table = "MyTable")]
public class MyClass : ActiveRecordBase<MyClass>
{
[Property]
public int Id { get; set; }
[Property]
public int MyClassId { get; set; }
[Property]
public string ListItem { get; set; }
}
Then, to load the list:
public void LoadMyClasses()
{
MyClass[] results = MyClass.FindAll();
}
I'd suggest you spend some time with the ActiveRecord documentation (or tutorial) as that should also help clear up any confusion.