I want to create the table with custom name but I cannot find the sample code. I notice the only way to create table is by generic type like db.CreateTable(). May I know if there is a way to create the table name dynamically instead of using Alias? The reason is because sometime we want to store the same object type into different tables like 2015_january_activity, 2015_february_activity.
Apart from this, the db.Insert also very limited to object type. Is there anyway to insert by passing in the table name?
I think these features are very important as it exists in NoSQL solution for long and it's very flexible. Thanks.
OrmLite is primarily a code-first ORM which uses typed POCO's to create and query the schema of matching RDMBS tables. It also supports executing Custom SQL using the Custom SQL API's.
One option to use a different table name is to change the Alias at runtime as seen in this previous answer where you can create custom extension methods to modify the name of the table, e.g:
public static class GenericTableExtensions
{
static object ExecWithAlias<T>(string table, Func<object> fn)
{
var modelDef = typeof(T).GetModelMetadata();
lock (modelDef) {
var hold = modelDef.Alias;
try {
modelDef.Alias = table;
return fn();
}
finally {
modelDef.Alias = hold;
}
}
}
public static void DropAndCreateTable<T>(this IDbConnection db, string table) {
ExecWithAlias<T>(table, () => { db.DropAndCreateTable<T>(); return null; });
}
public static long Insert<T>(this IDbConnection db, string table, T obj, bool selectIdentity = false) {
return (long)ExecWithAlias<T>(table, () => db.Insert(obj, selectIdentity));
}
public static List<T> Select<T>(this IDbConnection db, string table, Func<SqlExpression<T>, SqlExpression<T>> expression) {
return (List<T>)ExecWithAlias<T>(table, () => db.Select(expression));
}
public static int Update<T>(this IDbConnection db, string table, T item, Expression<Func<T, bool>> where) {
return (int)ExecWithAlias<T>(table, () => db.Update(item, where));
}
}
These extension methods provide additional API's that let you change the name of the table used, e.g:
var tableName = "TableA"'
db.DropAndCreateTable<GenericEntity>(tableName);
db.Insert(tableName, new GenericEntity { Id = 1, ColumnA = "A" });
var rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "A"));
rows.PrintDump();
db.Update(tableName, new GenericEntity { ColumnA = "B" },
where: q => q.ColumnA == "A");
rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "B"));
rows.PrintDump();
This example is also available in the GenericTableExpressions.cs integration test.
Related
I have table table_name(id, cart_token, data , created_at, updated_at ) that wants to associate with shopware cart table using token column (table_name.cart_token = cart.token).
How can we do this association using DAL as long as cart table doesn't have a CartEntity and CartDefinition.
For example: Select * from table_name leftjoin cart on table_name.cart_token=cart.token where cart.token=null.
Without a definition and accompanying entity class you simply won't be able to retrieve the cart as a mapped object using the DAL. You could add your own definition and entity for the cart table but I wouldn't recommend it, as this would just cause problems if multiple extensions got the same idea. I would recommend injecting Doctrine\DBAL\Connection in a service of your plugin and just fetching the cart using raw SQL.
class CartFetcherService
{
private Connection $connection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public function fetchCart(YourCustomEntity $entity): ?array
{
$cart = $this->connection->fetchAssociative(
'SELECT * FROM `cart` WHERE `token` = :token',
[
'token' => $entity->getToken(),
]
);
return $cart ?: null;
}
}
If you want to retrieve the Cart object directly, you could also inject the Shopware\Core\Checkout\Cart\CartPersister service to load the cart.
class CartLoaderService
{
private CartPersister $persister;
public function __construct(CartPersister $persister)
{
$this->persister = $persister;
}
public function getCart(YourCustomEntity $entity, SalesChannelContext $context): ?Cart
{
try {
$cart = $this->persister->load($entity->getToken(), $context)
} catch (\Throwable $exception) {
$cart = null;
}
return $cart;
}
}
Disclaimer: I'm new to C# and Acumatica Framework
I'm looking to implement a database slot however I need to pull data from joined tables. I'm using the PXDatabase.SelectMulti method from the snippet below however, I have been unable to get it to work with joins. I also can't seem to find any examples of the method with joined tables.
Is there a way to join tables with this method or perhaps another way to query the data?
public class DatabaseSlotsExample : IPrefetchable
{
protected List<string> values = new List<string>(); // store your values here
public static List<string> Values
{
get
{
//Get the values from the slot dynamically. By providing table name, you inform system when it should reset the slot.
return PXDatabase.GetSlot<DatabaseSlotsExample>("SlotSuperKey", typeof(YourTable)).values;
}
}
public void Prefetch()
{
//read database here
foreach(PXDataRecord rec in PXDatabase.SelectMulti<YourTable>(
new PXDataField<YourTable.tableField>(), //definition for fields that system should select
new PXDataFieldValue<YourTable.tableKey>("Some Condition") //definition for restriction that you need to apply
))
{
//populate your collection from the database here
values.Add(rec.GetString(0));
}
}
}
Yes, if you use BQL.Fluent as a reference, the BQL query is also simplified. See below used on Service Orders:
foreach (PXResult<FSServiceOrder> res in
SelectFrom<FSServiceOrder>.
InnerJoin<FSAppointment>.On<FSAppointment.soRefNbr.IsEqual<FSServiceOrder.refNbr>>.
InnerJoin<FSWFStage>.On<FSWFStage.wFStageID.IsEqual<FSAppointment.wFStageID>>.
InnerJoin<FSRoom>.On<FSRoom.roomID.IsEqual<FSServiceOrder.roomID>>.
InnerJoin<FSEquipment>.On<FSEquipment.registrationNbr.IsEqual<FSRoom.descr>>.
Where<FSServiceOrder.srvOrdType.IsEqual<P.AsString>.
And<FSWFStage.wFStageCD.IsEqual<P.AsString>>>.
View.Select(this, "TO", "SCHEDULED")
{
FSServiceOrder fsServiceOrder = res.GetItem<FSServiceOrder>();
FSAppointment fsAppointment = res.GetItem<FSAppointment>();
}
Then you can use the below to pull the specific table data:
I'm using Dapper Extensions and have defined my own custom mapper to deal with entities with composite keys.
public class MyClassMapper<T> : ClassMapper<T> where T : class
{
public MyClassMapper()
{
// Manage unmappable attributes
IList<PropertyInfo> toIgnore = typeof(T).GetProperties().Where(x => !x.CanWrite).ToList();
foreach (PropertyInfo propertyInfo in toIgnore.ToList())
{
Map(propertyInfo).Ignore();
}
// Manage keys
IList<PropertyInfo> propsWithId = typeof(T).GetProperties().Where(x => x.Name.EndsWith("Id") || x.Name.EndsWith("ID")).ToList();
PropertyInfo primaryKey = propsWithId.FirstOrDefault(x => string.Equals(x.Name, $"{nameof(T)}Id", StringComparison.CurrentCultureIgnoreCase));
if (primaryKey != null && primaryKey.PropertyType == typeof(int))
{
Map(primaryKey).Key(KeyType.Identity);
}
else if (propsWithId.Any())
{
foreach (PropertyInfo prop in propsWithId)
{
Map(prop).Key(KeyType.Assigned);
}
}
AutoMap();
}
}
I also have this test case to test my mapper:
[Test]
public void TestMyAutoMapper()
{
DapperExtensions.DapperExtensions.DefaultMapper = typeof(MyClassMapper<>);
MySubscribtionEntityWithCompositeKey entity = new MySubscribtionEntityWithCompositeKey
{
SubscriptionID = 145,
CustomerPackageID = 32
};
using (var connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
var result = connection.Insert(entity);
var key1 = result.SubscriptionID;
var key2 = result.CustomerPackageID;
}
}
Note that I set the default mapper in the test case.
The insert fails and I notive that my customer mapper is never called. I have no documentation on the github page on the topic, so I'm not sure if there's anything else I need to do to make dapper extensions use my mapper.
Thanks in advance!
Looking at your question, you are attempting to write your own defalut class mapper derived from the existing one. I never used this approach; so I do not know why it is not working or whether it should work.
I explicitly map the classes as below:
public class Customer
{
public int CustomerID { get; set; }
public string Name { get; set; }
}
public sealed class CustomerMapper : ClassMapper<Customer>
{
public CustomerMapper()
{
Schema("dbo");
Table("Customer");
Map(x => x.CustomerID).Key(KeyType.Identity);
AutoMap();
}
}
The AutoMap() will map rest of the properties based on conventions. Please refer to these two resources for more information about mapping.
Then I call SetMappingAssemblies at the startup of the project as below:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { Assembly.GetExecutingAssembly() });
The GetExecutingAssembly() is used in above code because mapping classes (CustomerMapper and other) are in same assembly which is executing. If those classes are placed in other assembly, provide that assembly instead.
And that's it, it works.
To set the dialect, I call following line just below the SetMappingAssemblies:
DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();
Use your preferred dialect instead of SqlServerDialect.
Apparently, the solution mentioned here may help you achieve what you are actually trying to. But, I cannot be sure, as I said above, I never used it.
Using Entity Framework I can create concrete classes from most of the sprocs in the database of a project I'm working on. However, some of the sprocs use dynamic SQL and as such no metadata is returned for the sproc.
So for a that sproc, I manually created a concrete class and now want to map the sproc output to this class and return a list of this type.
Using the following method I can get a collection of objects:
var results = connection.Query<object>("get_buddies",
new { RecsPerPage = 100,
RecCount = 0,
PageNumber = 0,
OrderBy = "LastestLogin",
ProfileID = profileID,
ASC = 1},
commandType: CommandType.StoredProcedure);
My concrete class contains
[DataContractAttribute(IsReference=true)]
[Serializable()]
public partial class LoggedInMember : ComplexObject
{
/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Int16 RowID
{
get
{
return _RowID;
}
set
{
OnRowIDChanging(value);
ReportPropertyChanging("RowID");
_RowID = StructuralObject.SetValidValue(value);
ReportPropertyChanged("RowID");
OnRowIDChanged();
}
}
private global::System.Int16 _RowID;
partial void OnRowIDChanging(global::System.Int16 value);
partial void OnRowIDChanged();
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String NickName
{
get
{
return _NickName;
}
set
{
OnNickNameChanging(value);
ReportPropertyChanging("NickName");
_NickName = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("NickName");
OnNickNameChanged();
}
}
private global::System.String _NickName;
partial void OnNickNameChanging(global::System.String value);
partial void OnNickNameChanged();
.
.
.
Without having to iterate through the results and add the output parameters to the LoggedInMember object, how do I map these on the fly so I can return a list of them through a WCF service?
If I try var results = connection.Query<LoggedInMember>("sq_mobile_get_buddies_v35", ... I get the following error:
System.Data.DataException: Error parsing column 0 (RowID=1 - Int64)
---> System.InvalidCastException: Specified cast is not valid. at Deserialize...
At a guess your SQL column is a bigint (i.e. Int64 a.k.a. long) but your .Net type has a n Int16 property.
You could play around with the conversion and ignore the stored procedure by doing something like:
var results = connection.Query<LoggedInMember>("select cast(9 as smallint) [RowID] ...");
Where you are just selecting the properties and types you want to return your object. (smallint is the SQL equivalent of Int16)
The solution to this was to create a complex object derived from the sproc with EF:
public ProfileDetailsByID_Result GetAllProfileDetailsByID(int profileID)
{
using (IDbConnection connection = OpenConnection("PrimaryDBConnectionString"))
{
try
{
var profile = connection.Query<ProfileDetailsByID_Result>("sproc_profile_get_by_id",
new { profileid = profileID },
commandType: CommandType.StoredProcedure).FirstOrDefault();
return profile;
}
catch (Exception ex)
{
ErrorLogging.Instance.Fatal(ex); // use singleton for logging
return null;
}
}
}
In this case, ProfileDetailsByID_Result is the object that I manually created using Entity Framework through the Complex Type creation process (right-click on the model diagram, select Add/Complex Type..., or use the Complex Types tree on the RHS).
A WORD OF CAUTION
Because this object's properties are derived from the sproc, EF has no way of knowing if a property is nullable. For any nullable property types, you must manually configure these by selecting the property and setting its it's Nullable property to true.
I've got a method similar to this, which loops through one set of data and uses a value of the first object to find an object in a second set of data:
private void someMethod(IQueryable<RemoteUser> source, IQueryable<LocalUser> targetData) {
// Loop all records in source data
foreach(var u in source) {
// Get keyvalue from source data and use it to find the matching record in targetData
var keyValue = u.id;
var object = from data.Where(o => o.id == keyValue).FirstOrDefault();
...
}
}
I'd like to make it more re-usable by passing in Func or using some other type of lambda and then convert the method to something I can use in a generic manner, i.e.:
private void someMethod<SourceT, TargetT>(IQueryable<SourceT> source, IQueryable<TargetT> targetData) {
....
}
What I'm not exactly sure on is how I can build a Func/Predicate/etc and pass it into the method. Keeping in mind that the "id" property will not be the same across all SourceT & TargetT properties.
To further explain, I'd like something where I can do this:
someMethod(RemoteUsers, LocalUsers, something here to say 'find the user using the userId property');
someMethod(RemoteProducts, LocalProducts, something here to say 'find the user using the productId property');
Here's the most basic implementation of your someMethod routine:
private void someMethod<S, T, P>(
IQueryable<S> source,
IQueryable<T> target,
Func<S, P> sourceSelector,
Func<T, P> targetSelector)
{
foreach(var s in source)
{
var sp = sourceSelector(s);
var #object = target
.Where(t => targetSelector(t).Equals(sp)).FirstOrDefault();
//...
}
}
This implementation keeps the structure of your original code, but this comes at a cost. You are effectively doing source.Count() * target.Count() queries against your database. You need to drop the use of foreach when working with IQueryable<>.
In fact, whenever you start writing code with foreach, you need to ask yourself if you can use a LINQ query to build and filter your data and make the foreach loop only do the "simplest" tasks.
Here's how to make the method work better:
private void someMethod2<S, T, P>(
IQueryable<S> source,
IQueryable<T> target,
Expression<Func<S, P>> sourceSelector,
Expression<Func<T, P>> targetSelector)
{
var query = source
.GroupJoin(
target,
sourceSelector,
targetSelector,
(s, ts) => ts.FirstOrDefault());
foreach(var #object in query)
{
//...
}
}
Note the use of Expression<Func<,>> and not just Func<,>. Also note the GroupJoin method call.